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;
workman的学习总结的更多相关文章
- 20165234 《Java程序设计》第三周学习总结
第三周学习总结 教材学习内容总结 第四章 编程语言的几个发展阶段: 面向机器语言 面向过程语言 面向对象语言 类 1. 类声明: class People { ... } class 动物 { ... ...
- 20175316盛茂淞 2018-2019-2 《Java程序设计》第3周学习总结
20175316盛茂淞 2018-2019-2 <Java程序设计>第3周学习总结 教材学习内容总结 1.1.编程语言的几个发展阶段: 面向机器语言 面向过程语言 面向对象语言 2.1.类 ...
- 20165233 Java第四章学习总结
20165233 2017-2018-2 <Java程序设计>第三周学习总结 教材学习内容总结 基础 类:包括类声明和类体. 其中类声明的变量被称作对象变量,简称对象. 类体中包括两部分: ...
- 20165210 Java第三周学习总结
20165210 Java第三周学习总结 教材学习内容总结 - 第四章学习总结 编程语言的几个发展阶段: 面向机器语言 面向过程语言 面向对象语言 类: 类声明: class People { ... ...
- 从直播编程到直播教育:LiveEdu.tv开启多元化的在线学习直播时代
2015年9月,一个叫Livecoding.tv的网站在互联网上引起了编程界的注意.缘于Pingwest品玩的一位编辑在上网时无意中发现了这个网站,并写了一篇文章<一个比直播睡觉更奇怪的网站:直 ...
- Angular2学习笔记(1)
Angular2学习笔记(1) 1. 写在前面 之前基于Electron写过一个Markdown编辑器.就其功能而言,主要功能已经实现,一些小的不影响使用的功能由于时间关系还没有完成:但就代码而言,之 ...
- ABP入门系列(1)——学习Abp框架之实操演练
作为.Net工地搬砖长工一名,一直致力于挖坑(Bug)填坑(Debug),但技术却不见长进.也曾热情于新技术的学习,憧憬过成为技术大拿.从前端到后端,从bootstrap到javascript,从py ...
- 消息队列——RabbitMQ学习笔记
消息队列--RabbitMQ学习笔记 1. 写在前面 昨天简单学习了一个消息队列项目--RabbitMQ,今天趁热打铁,将学到的东西记录下来. 学习的资料主要是官网给出的6个基本的消息发送/接收模型, ...
- js学习笔记:webpack基础入门(一)
之前听说过webpack,今天想正式的接触一下,先跟着webpack的官方用户指南走: 在这里有: 如何安装webpack 如何使用webpack 如何使用loader 如何使用webpack的开发者 ...
随机推荐
- DRF框架简介(第一天)
1.drf框架全称 djangorestframework 1.如何安装drf框架: pip3 install djangorestframework #drf框架其实就是一个app称之为drf #d ...
- Mysql 日期加减
mysql表中有一些字段是显示日期的.因为各种需要,需要将它时间往后调整1年. mysql 日期增加一年的更新语句更新的语句如下: UPDATE table SET date = DATE_A ...
- java学习--异常
异常的概念 java异常是java提供的用于处理程序中错误的一种机制 所谓的错误是指在程序运行过程中发生的一些异常事件.如除0溢出,数组下标越界,文件不存在 设计良好的程序应该在异常发生时,提供处理这 ...
- maven学习-基本入门用法
一.下载及安装 1.1 下载maven 3.1.1 先到官网http://maven.apache.org/download.cgi 下载最新版本(目前是3.1.1 ),下载完成后,解压到某个目录(本 ...
- Python导出MySQL数据库中表的建表语句到文件
为了做数据对象的版本控制,需要将MySQL数据库中的表结构导出成文件进行版本化管理,试写了一下,可以完整导出数据库中的表结构信息 # -*- coding: utf-8 -*- import os i ...
- HTML 元素大小
1.元素的偏移量 元素的可见大小是由其高度.宽度决定,包括所有的内边距.滚动条和边框大小(不包括外边距). offsetHeight :元素在垂直方向上占用的空间大小,以像素计算.包括元素的高度,水平 ...
- DJango 基础(7)
常用查询字段常用类型与模型关系表 知识点: 常用的查询 Field的常用参数 常用的模型字段类型 常用的查询 获取所有记录:rs = User.objects.all()获取第一条数据:rs = Us ...
- VueJs相关学习网址
麦子学院 http://www.maiziedu.com/course/916/ 慕课网-vue.js入门基础 https://www.imooc.com/learn/694 查阅的网址 ...
- linux服务器系统负载监控-shell脚本
一.监控服务器系统负载情况: 1.用uptime命令查看当前负载情况(1分钟,5分钟,15分钟平均负载情况) # uptime 15:43:59 up 186 days, 20:04, 1 us ...
- python的魔术方法
什么叫魔术方法: 在python中定义以双下划线开头,有一些python自定义的函数,并且以双下划线为结尾的函数叫做魔法函数 class Company(object): def __init__(s ...