workermanjava 守护进程程

点击阅读原文
workerman源码分析 - 前置知识篇
3月24日 发布,来源:
workerman源码分析
@昌维 写的相对路径,所以站内是看不了
页面找不到,链接失效了??
好像不能站内阅读
@church 哦哦
明天提醒我
我要该,理由是:
关闭理由:
删除理由:
忽略理由:
推广(招聘、广告、SEO 等)方面的内容
与已有问题重复(请编辑该提问指向已有相同问题)
答非所问,不符合答题要求
宜作评论而非答案
带有人身攻击、辱骂、仇恨等违反条款的内容
无法获得确切结果的问题
非开发直接相关的问题
非技术提问的讨论型问题
其他原因(请补充说明)
扫扫下载 AppPHP一直以来以草根示人,它简单,易学,被大量应用于web开发,非常可惜的是大部分开发都在简单的增删改查,或者加上pdo,redis等客户端甚至分布式,以及规避语言本身的缺陷。然而这实在太委屈PHP了。记得有一次问walker,PHP能做什么?他说:什么都能做啊!当时我就震惊了,这怎么可能。。。直到后来一直看workerman源码,发现PHP原来有很多不为大家所知的诸多用法,包括多进程(还有线程)、信号处理、namespace等等一大堆特点。而workerman正是这些很少被使用特性(或者说扩展)的集大成者,如果非要说它的缺点,那就是PHP的缺点了,当然PHP的优点它全占了~而且PHP7发布在即,workerman必将得到更多的优化,搭配HHVM更是叼的不行。
版本:3.1.8(linux)
模型:GatewayWorker(Worker模型可与之类比)
注:只贴出讲解部分代码,出处以文件名形式给出,大家可自行查看
workerman最初只开发了Linux版本,win是后来增加的,基于&。
多进程模型
工作进程,Master、Gateway和Worker,Gateway主要用于处理IO事件,保存客户端链接状态,将数据处理请求发送给Worker等工作,Worker则是完全的业务逻辑处理,前者为IO密集型,后者为计算密集型,它们之间通过网络通信,Gateway和Worker两两间注册通信地址,所以非常方便的进行分布式部署,如果业务处理量大可以单纯的增加Worker服务。
它们有一个负责监听的父进程(Master),监听子进程状态,发送 signal 给子进程,接受来自终端的命令、信号等工作。父进程可以说是整个系统启动后的入口。
启动命令解析
既然以命令模式(cli)运行(注意与 fpm 的区别,后者处理来自网页端的请求),就必然有一个启动脚本解析命令,譬如说3.x版本(之前默认为daemon)新增一个 -d 参数,以表示守护进程运行,解析到该参数设置 self::$daemon = true, 随后fork子进程以脱离当前进程组,设置进程组组长等工作。这里有两个非常重要的参数 $argc 和 $argc,前者表示参数个数,后者为一个数组,保存有命令的所有参数,比如:sudo php start.php start -d,$argv就是 array(
[0]=&start.php, [1]=&start, [2]=&-d ),而解析主要用到$argv。
启动主要执行下面步骤:
包含&&,加载各 Application 下启动文件;设置&_appInitPath 根目录;解析,初始化参数,执行相应命令。
下面是具体实现(workerman/worker.php):
public static function parseCommand()
global $argv;
$start_file = $argv[0];
$command = trim($argv[1]);
$command2 = isset($argv[2]) ? $argv[2] : '';
$master_pid = @file_get_contents(self::$pidFile);
$master_is_alive = $master_pid && @posix_kill($master_pid, 0);
if($master_is_alive)
if($command === 'start')
self::log(&Workerman[$start_file] is running&);
elseif($command !== 'start' && $command !== 'restart')
self::log(&Workerman[$start_file] not run&);
switch($command)
case 'start':
if($command2 === '-d')
Worker::$daemonize = true;
case 'status':
case 'restart':
case 'stop':
$master_pid && posix_kill($master_pid, SIGINT);
$timeout = 5;
$start_time = time();
$master_is_alive = $master_pid && posix_kill($master_pid, 0);
if($master_is_alive)
if(time() - $start_time &= $timeout)
self::log(&Workerman[$start_file] stop fail&);
usleep(10000);
self::log(&Workerman[$start_file] stop success&);
if($command === 'stop')
if($command2 === '-d')
Worker::$daemonize = true;
case 'reload':
walker代码注释已经非常详尽,下面有几点细节处:
检查主进程是否存活:17行的逻辑与操作,如果主进程PID存在情况下,向该进程发送信号0,实际上并没有发送任何信息,只是检测该进程(或进程组)是否存活,同时也检测当前用户是否有权限发送系统信号;为什么主进程PID会保存?系统启动后脱离当前terminal运行,如果要执行关闭或者其他命令,此时是以另外的一个进程执行该命令,如果我们连进程PID都不知道,那该向谁发信号呢?!所以主进程PID必须保存起来,而且主进程负责监听其他子进程,所以它是我们继续操作的入口。
Worker::runAll()
php的socket编程其实和C差不多,后者对socket进行了再包裹,并提供接口给php,在php下网络编程步骤大大减少。譬如:&&和&&直接创建了server/client
socke(php有两套socket操作函数)。wm则大量使用了前者,启动过程如下(注释已经非常详尽):
public static function runAll()
self::init();
self::parseCommand();
self::daemonize();
self::initWorkers();
self::installSignal();
self::saveMasterPid();
self::forkWorkers();
self::displayUI();
self::resetStd();
self::monitorWorkers();
下面还是只说该过程的关键点:
初始化环境变量,例如设置主进程名称、日志路径,初始化定时器等等;解析命令行参数,主要用到 $argc 和 $argc 用法同C语言;生成守护进程,以脱离当前终端(两年前大部分认为PHP无法做daemon,其实这是个误区!其实PHP在linux的进程模型很稳定,现在wm在商业的应用已经非常成熟,国内某公司每天处理几亿的连接,用于订单、支付调用,大家可以打消顾虑了);初始化所有worker实例(注意,这里是在主进程做的,只是生成了一堆 server 并没有设置监听,多进程模型是在子进程做的监听,即IO复用);为主进程注册信号处理函数;保存主进程PID,当系统运行后,我们在终端查看系统状态或者执行关闭、重启命令,是通过主进程进行通信,所以需要知道主进程PID,我们知道在终端下敲入一个可执行命令,实则是在当前终端下新建一个子进程来执行,所以我们需要得知主进程PID,以向WM主进程发送SIGNAL,这时信号处理函数捕获该信号,并通过回调方式执行。创建子进程,设置当前进程用户(root)。在多进程模型中,两给子进程,分别监听不同的server地址,我们在主进程只是创建server并没有设置监听,也没有生成指定数目的server,原因在于,我们在一个进程多次创建同一个 socket, woker数目其实就是 socket 数量,也就是该 socket 的子进程数目,否则会报错;在子进程中,将 server socket 注册监听事件,用到一个扩展&&,可以实现IO复用,并注册数据读取回调,同时也可注册socket连接事件回调;输入输出重定向;主进程监听子进程状态,在一个无限循环中调用 pcntl_signal_dispatch() 函数,用于捕获子进程退出状态,该函数会一直阻塞,直到有子进程退出时才触发;
至此,一个完整的启动过程大致处理完成,然后 server 会一直运行,一直等待 socket 连接事件,等待数据可读可写事件,通过事先注册的处理函数,就能完整的处理整个网络过程。
其实网络编程过程大致都差不多,这些都有标准答案,每个语言实现的大致过程基本相同,当然类似 golang 的 goroutine 另说。。。需要了解应用层协议(如果可能,需要手动解包和封包),网络模型,TCP/UDP,进程间通信,IO复用等等,当然最重要的是会 debug。。。自己动手尝试写一个简单的 server 就会遇到很多无法遇见的坑,所以纸上得来终觉浅,绝知此事要躬行。
&&相关文章推荐
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:16010次
排名:千里之外
原创:24篇
转载:43篇
(3)(12)(4)(1)(1)(2)(1)(7)(1)(5)(2)(1)(3)(1)(2)(2)(3)(1)(1)(1)(5)(3)(2)(2)(2)workerman 聊天框架
我的图书馆
workerman 聊天框架
workerman是一个高性能的PHP socket 服务器框架,workerman基于PHP多进程以及libevent事件轮询库,PHP开发者只要实现一两个接口,便可以开发出自己的网络应用,例如Rpc服务、聊天室服务器、手机游戏服务器等。
workerman的目标是让PHP开发者更容易的开发出基于socket的高性能的应用服务,而不用去了解PHP socket以及PHP多进程细节。
workerman本身是一个PHP多进程服务器框架,具有PHP进程管理以及socket通信的模块,所以不依赖php-fpm、nginx或者apache等这些容器便可以独立运行。
支持HHVM使用PHP开发支持PHP多进程/多线程(多线程版本)标准输入输出重定向支持毫秒定时器支持基于事件的异步编程守护进程化支持TCP/UDP支持多端口监听接口上支持各种应用层协议支持libevent事件轮询库,支持高并发支持服务平滑重启支持PHP文件更新检测及自动加载支持PHP长连接支持以指定用户运行子进程支持telnet远程控制高性能提示
workerman只是一个代码包,如果php环境满足要求,下载后即可使用,实际上没有安装过程。
workerman使用的是,与Apahce的PHP或者PHP-FPM不冲突。workerman不依赖Apache/nginx或者PHP-FPM,也不影响他们的运行。
workerman对php环境的要求是:
1、PHP CLI&=5.4,可以运行命令 php -v 查看版本
2、Linux系统要求php安装了posix和pcntl扩展
Linux系统可以在命令中运行 curl -Ss http://www.workerman.net/check.php | php
检测本地环境是否满足workerman全新系统参考以下安装教程http://doc3.workerman.net/preface/README.html&
TA的最新馆藏
喜欢该文的人也喜欢君,已阅读到文档的结尾了呢~~
workerman-manual,workerman,swoole workerman,workerman php,workerman教程,workerman chat,workerman mysql,workerman thinkphp,workerman windows,workerman redis
扫扫二维码,随身浏览文档
手机或平板扫扫即可继续访问
workerman-manual
举报该文档为侵权文档。
举报该文档含有违规或不良信息。
反馈该文档无法正常浏览。
举报该文档为重复文档。
推荐理由:
将文档分享至:
分享完整地址
文档地址:
粘贴到BBS或博客
flash地址:
支持嵌入FLASH地址的网站使用
html代码:
&embed src='/DocinViewer--144.swf' width='100%' height='600' type=application/x-shockwave-flash ALLOWFULLSCREEN='true' ALLOWSCRIPTACCESS='always'&&/embed&
450px*300px480px*400px650px*490px
支持嵌入HTML代码的网站使用
您的内容已经提交成功
您所提交的内容需要审核后才能发布,请您等待!
3秒自动关闭窗口workerman源码分析 | 空空
对于php开发多进程了解不多,由于最近写cli程序比较多,之前的公司也使用过一套php开发的异步server框架,性能如何不是十分了解,当时的文档说性能非常好,所以最近有时间就提出源码来看一下。与此同时,在guthub上闲逛时恰好看到workerman这个东西,核心代码比较少,才1K多行,和上一家公司的server框架代码如出一辙,应该是同一个人所为。东西不多,了解一下原理应该对php的多进程开发会有一定的帮助
核心代码在根目录下的Worker.php文件中,当使用new Worker('Text://0.0.0.0:12345')初始化的时候,到底发生了什么呢?现在来一步一步的分析一下Worker的源码解释流程
首先看构造函数:123456789101112131415161718192021222324public function __construct($socket_name = '', $context_option = array()){
$this-&workerId
= spl_object_hash($this);
self::$_workers[$this-&workerId] = $this;
self::$_pidMap[$this-&workerId]
= array();
= debug_backtrace();
$this-&_autoloadRootPath = dirname($backrace[0]['file']);
if ($socket_name) {
$this-&_socketName = $socket_
if (!isset($context_option['socket']['backlog'])) {
$context_option['socket']['backlog'] = self::DEFAUL_BACKLOG;
$this-&_context = stream_context_create($context_option);
$this-&onMessage = function () {
};}
注意这里self::$_workers[$this-&workerId] = $ 这里给当前对象的静态成员变量$_workers赋值为一个数组,是为了处理多个不同的任务而设置,即此处存放的是多个实例化对象,从而使得单一文件监听多个任务成为可能,只需要在多new几次Worker对象,并监听不同的端口,然后执行Worker::runAll()即可
接下来看看runAll函数做了些什么工作。
self::checkSapiEnv()runAll方法里面全部是调用自身的静态方法,第一个 self::checkSapiEnv(),此方法保证了workerman只能在命令行模式下运行,即不能运用在web环境当中
self::init()初始化worker对象必要的属性,如日志文件的存放位置,pid文件的名称(pid文件的命名是根据文件路径和文件名来命名的,避免运行多个worker任务时候发生冲突),标记worker的运行状态,记录程序开始运行时间,设置主进程名称,使得worker的进程可以在ps命令下友好的显示,初始化定时器等等,initId()函数会生成一个有$count长度的数组,后期的fork出来的子进程的个数就是根据这个数组而来1234foreach (self::$_workers as $worker_id =& $worker) {
self::$_idMap[$worker_id] = array_fill(0, $worker-&count, 0);}
self::parseCommand()解析命令,根据参数进行不同的指令操作,支持start | stop | restart | reload | status
self::daemonize()按照守护进程模式运行程序,前提是使用-d参数,如果不带-d参数此函数直接return了,否则此时使用pcntl_fork函数fork出一个子进程,并退出父进程,此时程序进入子进程并使用posix_setsid()使进程称为会话组长从而摆脱终端控制,实现后台运行。此时再fork一次,并结束子进程,此时程序进入孙进程里,这里fork两次的目的我猜是为了预防僵尸进程,官方的解释是:Fork again avoid SVR4 system regain the control of terminal 避免SVR4系统重新打开控制终端?这里没看明白
self::initWorkers()循环$_workers的实例对象,就是在构造函数里面存储的数组,并设置基本信息,如启动用户,实例名称,获得名称的长度来为之后的美化控制台显示做准备。函数最后执行$worker-&listen().(reusePort属性是作为端口复用而来,目前只有PHP7版本SO_REUSEPORT特性支持,暂时不讲解这部分内容)
接下来看看listen部分的内容123456789101112131415161718192021222324if (!$this-&_socketName || $this-&_mainSocket) {
return;}Autoloader::setRootPath($this-&_autoloadRootPath);...$flags
= $this-&transport === 'udp' ? STREAM_SERVER_BIND : STREAM_SERVER_BIND | STREAM_SERVER_LISTEN;$this-&_mainSocket = stream_socket_server($local_socket, $errno, $errmsg, $flags, $this-&_context);if (function_exists('socket_import_stream') && $this-&transport === 'tcp') {
$socket = socket_import_stream($this-&_mainSocket);
@socket_set_option($socket, SOL_SOCKET, SO_KEEPALIVE, 1);
@socket_set_option($socket, SOL_TCP, TCP_NODELAY, 1);}stream_set_blocking($this-&_mainSocket, 0);
self::installSignal()安装信号处理器,收到信号全部交给signalHandler来处理1234567pcntl_signal(SIGINT, array('\Workerman\Worker', 'signalHandler'), false);pcntl_signal(SIGUSR1, array('\Workerman\Worker', 'signalHandler'), false);pcntl_signal(SIGUSR2, array('\Workerman\Worker', 'signalHandler'), false);pcntl_signal(SIGPIPE, SIG_IGN, false);
举例说明,在执行stop指令的时候,php ***.php stop,回到 parseCommand函数里面,会看到:12345678910111213141516171819202122232425262728293031case 'stop':
self::log("Workerman[$start_file] is stoping ...");
$master_pid && posix_kill($master_pid, SIGINT);
$start_time = time();
while (1) {
$master_is_alive = $master_pid && posix_kill($master_pid, 0);
if ($master_is_alive) {
if (time() - $start_time &= $timeout) {
self::log("Workerman[$start_file] stop fail");
usleep(10000);
self::log("Workerman[$start_file] stop success");
if ($command === 'stop') {
if ($command2 === '-d') {
Worker::$daemonize = true;
posix_kill($master_pid, SIGINT)向进程发送SIGINT信号,此时主进程 (孙子进程)收到信号会进入到signalHandler函数内执行 self::stopAll(); 就是停止所有的进程运行,关于stopAll会在之后继续讲解其原理,此处,为了防止程序不能正常退出,用了一个while循环来不停的退出,直到退出成功为止。。。有点费解啊
self::saveMasterPid()把当前进程id写进pid文件1234self::$_masterPid = posix_getpid();if (false === @file_put_contents(self::$pidFile, self::$_masterPid)) {
throw new Exception('can not save pid to ' . self::$pidFile);}
self::forkWorkers()遍历$_workers来创建子进程,根据$count的值来决定创建多少个子进程123while (count(self::$_pidMap[$worker-&workerId]) & $worker-&count) {
static::forkOneWorker($worker);}
来到 forkOneWorker函数12345678910111213141516171819202122232425262728293031323334353637protected static function forkOneWorker($worker){
$pid = pcntl_fork();
$id = self::getId($worker-&workerId, 0);
if ($pid & 0) {
self::$_pidMap[$worker-&workerId][$pid] = $
self::$_idMap[$worker-&workerId][$id]
elseif (0 === $pid) {
if ($worker-&reusePort) {
$worker-&listen();
if (self::$_status === self::STATUS_STARTING) {
self::resetStd();
self::$_pidMap
= array();
self::$_workers = array($worker-&workerId =& $worker);
Timer::delAll();
self::setProcessTitle('WorkerMan: worker process
' . $worker-&name . ' ' . $worker-&getSocketName());
$worker-&setUserAndGroup();
$worker-&id = $
$worker-&run();
exit(250);
} else {
throw new Exception("forkOneWorker fail");
}}
接下来才是每个子进程的运行实例,使用run函数进行运行。1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950public function run(){
self::$_status = self::STATUS_RUNNING;
register_shutdown_function(array("\\Workerman\\Worker", 'checkErrors'));
Autoloader::setRootPath($this-&_autoloadRootPath);
if (!self::$globalEvent) {
$eventLoopClass
= "\\Workerman\\Events\\" . ucfirst(self::getEventLoopName());
self::$globalEvent = new $eventLoopC
if ($this-&_socketName) {
if ($this-&transport !== 'udp') {
self::$globalEvent-&add($this-&_mainSocket, EventInterface::EV_READ,
array($this, 'acceptConnection'));
} else {
self::$globalEvent-&add($this-&_mainSocket, EventInterface::EV_READ,
array($this, 'acceptUdpConnection'));
self::reinstallSignal();
Timer::init(self::$globalEvent);
if ($this-&onWorkerStart) {
try {
call_user_func($this-&onWorkerStart, $this);
} catch (\Exception $e) {
exit(250);
self::$globalEvent-&loop();}
此时子进程阻塞掉。但主进程回到runAll函数继续向下执行。
当有连接进来的时候,这里用tcp协议为例,查看acceptConnection函数做了什么工作1234567891011121314151617181920212223$new_socket = @stream_socket_accept($socket, 0, $remote_address);$connection
= new TcpConnection($new_socket, $remote_address);$this-&connections[$connection-&id] = $$connection-&worker
= $this;$connection-&protocol
= $this-&$connection-&onMessage
= $this-&onM$connection-&onClose
= $this-&onC$connection-&onError
= $this-&onE$connection-&onBufferDrain
= $this-&onBufferD$connection-&onBufferFull
= $this-&onBufferFif ($this-&onConnect) {
try {
call_user_func($this-&onConnect, $connection);
} catch (\Exception $e) {
exit(250);
}}

我要回帖

更多关于 c 守护进程 的文章

 

随机推荐