手写简化版workerman,剖析其原理
前言
workerman
的最大特色就是基础功能不依赖额外扩展,使得这个框架可以在windows
和linux
系统中完美运行。其次就是workerman
的学习成本极低,只需要会用php写回调就能灵活操控这个框架,对进程和网络相关的知识捆绑不是很高。
项目
https://github.com/d2gin/socketman
原理
解读workerman
首先是他的执行流程
- Worker::runAll() 程序入口。
- Worker::init() 初始化一些基本信息,比如进程号、进程名、定时器、日志文件等。
- Worker::lock() 以当前执行文件路径为进程标识,对进程文件进行上锁。
- Worker::parseCommand() 解析命令行参数,如
-d
、-g
、start
、stop
、restart
等。 - Worker::daemonize() 守护进程 只在linux下有效。
- Worker::initWorkers() 初始化所有子服务,只在linux下执行,没有太多重要操作。
- Worker::installSignal() 安装进程信号监听 只在linux下有效。
- Worker::saveMasterPid() 保存进程id到进程文件,只在linux有效。
- Worker::lock(\LOCK_UN) 解锁当前进程文件。
- Worker::forkWorkers() 这里是新建进程的操作,分为linux和windows部分,流程基本类似(如果多进程环境,那么这一步是运行在子进程的):
- Worker::listen() 监听端口,并向全局事件调度器注册一个接听客户端连接的任务。
- Worker::run() 获取事件轮询器,并向定时器(
Timer
)注册事件轮询(EventLoop
)的机制。开始轮询。
- Worker::resetStd() 重定向标准输入输出,守护进程需要用到,所以只在linux有效。
- Worker::monitorWorkers() 监视子进程,只在主进程执行。
关于事件轮询
- 默认情况下
workerman
使用的是while(true);
做的轮询,即Workerman\Events\Select
,理论上这种做法依然是堵塞运行的,只要有一个事件卡住那么后面的事件也会得不到运行。 定时器
也是使用时间轮询实现的,如果轮询不支持异步的话,事件执行过程中也会出现堵塞的可能。- 要解决这个堵塞问题需要使用
libevent
或者event
扩展,这样可以为workerman
带来不小的性能提升。 - 好消息是
workerman
已经预设了多个轮询机制,如果安装了libevent
或者event
会自动选择对应的轮询类,优先级:event/libevent。 - 当然也可以将事件轮询交给
swoole
处理:
- 默认情况下
关于进程监视
- linux中收到子进程退出信号时,父进程会对运行过程中退出的子进程重新复刻(fork),即重启。如果你不希望某个子进程被重启:
- windows中是重新执行一遍当前的入口文件,没有太多复杂操作。
fread的8192问题
- PHP官方文档
if the stream is read buffered and it does not represent a plain file, at most one read of up to a number of bytes equal to the chunk size (usually 8192) is made; depending on the previously buffered data, the size of the returned data may be larger than the chunk size.
,大概意思是如果fread读取的是网络资源,那么数据会按8192
字节大小进行分片。所以这个时候int $length
参数就形同虚设了。 - 如何解决:使用
stream_set_read_buffer($socket, 0)
来设置资源数据不再缓冲。
- PHP官方文档
手写简化版workerman,剖析其原理
http://blog.icy8.cn/posts/419/