workman的学习总结
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
workman的学习总结
我们知道php主要是⽤来做web应⽤的,⽽且平时使⽤的都是都是和其他的web服务器来结合使⽤,⽐如和apache,nginx和apache的时候,是作为apache的⼀个动态模块来加载,和nginx的时候主要是使⽤fpm的形式,现在其他的⼀些语⾔,⽐如python,nodejs,ruby,go 都是单独作为http服务编程的,其实php也是可以的,php⾥⾯有⼀些扩展,我们平时做web应⽤的时候都是很少使⽤到的,⽐如
fcntl,libevent,stream,posix等等,这些扩展主要是对unix的api的⼀些封装,使⽤它们其实和uinx的⽹络编程是⼀样的,只是更加的便捷和容易,还可以使⽤我们熟悉的php语⾔,⽽不是c。
workman其实就是把php的⽹络模块做⼀个整合,为php⽤户提供⼀个更加便捷的⽅式来创建⽹络编程的条件,⽐如实现tcp,udp服务器,实现http的web服务器等等。
主要的⽂件结构
Worker.php 是workman的主要⼊⼝⽂件,这个定义了workman作为⼀个服务器的相关的操作,变量初始化,参数解析,进程管理,请求接受,事件注册处理等等。
WebServer.php 继承了Worker.php主要还是使⽤Worker.php的逻辑,主要区别是使⽤Htpp的协议,以及实现对uri的简单路由分发。
Events 是事件的相关类
Lib 包含Timer.php 以及⼀些常数。
Connection 是对连接的处理
Protocols 是对协议的处理,主要是对Connection接收的数据的处理,⽐如http的时候添加header头等等。
Worker.php的分析
内部函数备注说明
self::checkSapiEnv();//监测环境是否正确,workman只能运⾏在cli模式下⾯
self::init();//环境初始化
self::parseCommand();//解析参数,主要对应⽤start,stop,restart,status, -d的判断
self::daemonize();//master进程编程daemo守护进程
self::initWorkers();//初始化work进程
self::installSignal();// 安装信号量
self::saveMasterPid();// 保存master的进程id,⼀⽂件的形式
self::forkWorkers();//fork⽣成⼦进程,在event上⾯注册请求到达的事件
self::displayUI();//显⽰ui,⽤来在控制台打印输出⼀些内容
self::resetStd(); //重新设置标准输出和输⼊
self::monitorWorkers();// 监控⼦进程
selef::checkSapiEnv
//根据php_sapi_name来做判断
if (php_sapi_name() != "cli") {
exit("only run in command line mode \n");
}
sapi接⼝是php为其他的应⽤提供的抽象层接⼝,不管是cli还是php-fpm,fastCgi都是调⽤这⼀层的接⼝。
在php源代码中
main/SAPI.h 定义了_sapi_module_struct,每⼀个sapi的应⽤都是要传递该结构startup⽤来设置使⽤php的相关参数。
其中php_sapi_name();函数返回的就是char *name对应的值。
struct _sapi_module_struct {
char *name;
char *pretty_name;
int (*startup)(struct _sapi_module_struct *sapi_module);
int (*shutdown)(struct _sapi_module_struct *sapi_module);
int (*activate)(TSRMLS_D);
int (*deactivate)(TSRMLS_D);
int (*ub_write)(const char *str, unsigned int str_length TSRMLS_DC);
void (*flush)(void *server_context);
struct stat *(*get_stat)(TSRMLS_D);
char *(*getenv)(char *name, size_t name_len TSRMLS_DC);
void (*sapi_error)(int type, const char *error_msg, ...);
....
};
self::init();
//更具debug_backtrace();返回最后⼀个调⽤该函数的⽂件,作为启动⽂件$backtrace = debug_backtrace();
self::$_startFile = $backtrace[count($backtrace) - 1]['file'];
// 设置pid的⽂件路名
if (empty(self::$pidFile)) {
self::$pidFile = __DIR__ . "/../" . str_replace('/', '_', self::$_startFile) . ".pid";
}
// 设置workman的⽇志⽂件,并且创建该⽂件
if (empty(self::$logFile)) {
self::$logFile = __DIR__ . '/../workerman.log';
}
$log_file = (string)self::$logFile;
touch($log_file);
chmod($log_file, 0622);
// 初始化运⾏状态
self::$_status = self::STATUS_STARTING;
// 为了统计运⾏状态
self::$_globalStatistics['start_timestamp'] = time();
self::$_statisticsFile = sys_get_temp_dir() . '/workerman.status'; // 设置进程的标题
self::setProcessTitle('WorkerMan: master process start_file=' . self::$_startFile); // 初始化该进程的id,主要是初始化所有work进程的进程id为0
self::initId();
// Timer init.
Timer::init();
parseCommand
这部分的逻辑⽐较简单,主要功能就是解析参数
global $argv;
// 监测参数,最起码要设置⼀个参数start,stop等
$start_file = $argv[0];
if (!isset($argv[1])) {
exit("Usage: php yourfile.php {start|stop|restart|reload|status}\n");
}
// 获取参数,第⼀个是服务器控制,第⼆个是是否是daemon进程
$command = trim($argv[1]);
$command2 = isset($argv[2]) ? $argv[2] : '';
// 启动的时候,判断是不是参数有-d的标志
$mode = '';
if ($command === 'start') {
if ($command2 === '-d' || Worker::$daemonize) {
$mode = 'in DAEMON mode';
} else {
$mode = 'in DEBUG mode';
}
}
self::log("Workerman[$start_file] $command $mode");
// 获取master进程ID,从本地⽂件⾥⾯获取
$master_pid = @file_get_contents(self::$pidFile);
$master_is_alive = $master_pid && @posix_kill($master_pid, 0);
//posix_kill发送0的信号,⽤来检测该进程是不是存在
if ($master_is_alive) {
if ($command === 'start' && posix_getpid() != $master_pid) {
self::log("Workerman[$start_file] already running");
exit;
}
} elseif ($command !== 'start' && $command !== 'restart') {
self::log("Workerman[$start_file] not run");
exit;
}
switch ($command) {
case 'start':
if ($command2 === '-d') {
Worker::$daemonize = true;
}
break;
case 'status':
if (is_file(self::$_statisticsFile)) {
@unlink(self::$_statisticsFile);
}
// 发送status的信号到所有⼦进程
posix_kill($master_pid, SIGUSR2);
// Waiting amoment.
usleep(500000);
// 显⽰数据收集的结果
@readfile(self::$_statisticsFile);
exit(0);
case 'restart':
case 'stop':
self::log("Workerman[$start_file] is stoping ...");
// Send stop signal to master process.
$master_pid && posix_kill($master_pid, SIGINT);
// Timeout.
$timeout = 5;
$start_time = time();
// Check master process is still alive?
while (1) {
$master_is_alive = $master_pid && posix_kill($master_pid, 0);
if ($master_is_alive) {
// Timeout?
if (time() - $start_time >= $timeout) {
self::log("Workerman[$start_file] stop fail");
exit;
}
// Waiting amoment.
usleep(10000);
continue;
}
// Stop success.
self::log("Workerman[$start_file] stop success");
if ($command === 'stop') {
exit(0);
}
if ($command2 === '-d') {
Worker::$daemonize = true;
}
break;
}
break;
case 'reload':
posix_kill($master_pid, SIGUSR1);
self::log("Workerman[$start_file] reload");
exit;
default :
exit("Usage: php yourfile.php {start|stop|restart|reload|status}\n");
}
daemonize
进程变为守护进程,主要是通过两次fork⼦进程(第⼀次主要是设置sid和原来的终端分离,第⼆主要失去重新获取控制终端的能⼒),关闭驻进程的⽅式。
if (!self::$daemonize) {
return;
}
umask(0);
$pid = pcntl_fork();
if (-1 === $pid) {
throw new Exception('fork fail');
} elseif ($pid > 0) {
exit(0);//退出主进程
}
//setsid()调⽤成功后,进程成为新的会话组长和新的进程组长,并与原来的登录会话和进程组脱离但是他还是可以申请重新打开⼀个控制终端,因为他是进程的组长
if (-1 === posix_setsid()) {
throw new Exception("setsid fail");
}
// 可以通过使进程不再成为会话组长来禁⽌进程重新打开控制终端
$pid = pcntl_fork();
if (-1 === $pid) {
throw new Exception("fork fail");
} elseif (0 !== $pid) {
exit(0);
}
initWorkers
初始化worker进程
主要就是worker进程开始监听端⼝,
listen函数的内容如下:
$this->_mainSocket = stream_socket_server($local_socket, $errno, $errmsg, $flags, $this->_context); if (!$this->_mainSocket) {
throw new Exception($errmsg);
}
// 保持长连接,防⽌延迟
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);
installSignal
注册信号事件
pcntl_signal(SIGINT, array('\Workerman\Worker', 'signalHandler'), false);
//reload 重新加载
pcntl_signal(SIGUSR1, array('\Workerman\Worker', 'signalHandler'), false);
//打印当前⼦进程状态
pcntl_signal(SIGUSR2, array('\Workerman\Worker', 'signalHandler'), false);
// 管道关闭
pcntl_signal(SIGPIPE, SIG_IGN, false);
saveMasterPid
把当前的⼦进程保存在⽂件中
self::$_masterPid = posix_getpid();
if (false === @file_put_contents(self::$pidFile, self::$_masterPid)) {
throw new Exception('can not save pid to ' . self::$pidFile);
}
forkWorkers
主要是就是循环self::_workers,根据⾥⾯的count数量⽣成对应的⼦进程。
主要使⽤的⽅法是:
forkOneWorker
///获取空的pid的位置
$id = self::getId($worker->workerId, 0);
if ($id === false) {
return;
}
//fork⼦进程
$pid = pcntl_fork();
// For mast
if ($pid > 0) {
self::$_pidMap[$worker->workerId][$pid] = $pid;
self::$_idMap[$worker->workerId][$id] = $pid;
} // For child processes.
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 = $id;
//work开始运⾏,接收请求
$worker->run();
exit(250);
} else {
throw new Exception("forkOneWorker fail");
}
run函数
//注册进程结束时候的函数
register_shutdown_function(array("\\Workerman\\Worker", 'checkErrors'));Autoloader::setRootPath($this->_autoloadRootPath);
// 创建event.根据eventLoopName来实例话event
if (!self::$globalEvent) {
self::log("create globalEvent");
$eventLoopClass = "\\Workerman\\Events\\" . ucfirst(self::getEventLoopName());
self::$globalEvent = new $eventLoopClass;
// 注册⼀个监听事件,当socket的连接是可读的时候.
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'));
}
}
}
acceptConnection函数
接收请求
$new_socket = @stream_socket_accept($socket, 0, $remote_address);
if (!$new_socket) {
return;
}
// 实例化TcpConnection
$connection = new TcpConnection($new_socket, $remote_address);
$this->connections[$connection->id] = $connection;
$connection->worker = $this;
$connection->protocol = $this->protocol;
//connection的onMessage指向当前类的onMessage⽅法
$connection->onMessage = $this->onMessage;
$connection->onClose = $this->onClose;
$connection->onError = $this->onError; $connection->onBufferDrain = $this->onBufferDrain;
$connection->onBufferFull = $this->onBufferFull;。