Swoole是一个PHP的C扩展,可用来开发PHP的高性能高并发TCP/UDP Server。Swoole的网络IO部分基于epoll/kqueue事件循环,是全异步非阻塞的。 业务逻辑部分使用多进程同步阻塞方式来运行。这样既保证了Server能够应对高并发和大量TCP连接。又保证业务代码仍然可以简单的编写。
可以说swoole的出现为php续上了一命,swoole可谓是所有phper打家劫舍必备的武器…

一、swoole的进程管理模式

swoole进程分为三种角色:reactor线程、worker进程、taskworker进程

  • reactor线程

    负责维护客户端TCP连接、处理网络IO、处理协议、收发数据
    完全是异步非阻塞的模式
    全部为C代码,除Start/Shudown事件回调外,不执行任何PHP代码
    将TCP客户端发来的数据缓冲、拼接、拆分成完整的一个请求数据包
    Reactor以多线程的方式运行

  • Worker进程

    接受由Reactor线程投递的请求数据包,并执行PHP回调函数处理数据
    生成响应数据并发给Reactor线程,由Reactor线程发送给TCP客户端
    可以是异步非阻塞模式,也可以是同步阻塞模式
    Worker以多进程的方式运行

  • TaskWorker进程

    接受由Worker进程通过swoole_server->task/taskwait方法投递的任务
    处理任务,并将结果数据返回(使用swoole_server->finish)给Worker进程
    完全是同步阻塞模式
    TaskWorker以多进程的方式运行

  • 图解swoole进程关系:

swoole中的reactor线程、worker进程、taskworker进程之间的关系类似于nginx与php-fpm之间的关系。

master进程启动后,创建reactor线程和manager进程。

reactore用于监听请求,并将请求下发给worker进程。(类似于nginx)

worker进程将耗时的处理转发给task进程处理。(类似于php-fpm)

manager进程用来管理worker进程和task进程,当worker、task进程处理了一定的连接(max_request)后自动重启,防止内存泄漏。

底层会为Worker进程、TaskWorker进程分配一个唯一的ID
不同的Worker和TaskWorker进程之间可以通过sendMessage接口进行通信

二、服务端

不管是tcp、udp、http,还是websocket、redis服务端,都是继承自swoole\server

  • 创建server对象

    $serv = new Server(string $host, int $port = 0, int $mode = SWOOLE_PROCESS,int $sock_type = SWOOLE_SOCK_TCP);
    

    $host参数用来指定监听的ip地址,如127.0.0.1,或者外网地址,或者0.0.0.0监听全部地址。当采用127.0.0.1或本机的外网地址时,表示只监听来自本机的客户端请求,适用于单体项目。
    IPv4使用 127.0.0.1表示监听本机,0.0.0.0表示监听所有地址
    IPv6使用::1表示监听本机,:: (相当于0:0:0:0:0:0:0:0) 表示监听所有地址

    $port监听的端口,如9501
    如果$sock_type为UnixSocket Stream/Dgram,此参数将被忽略
    监听小于1024端口需要root权限
    如果此端口被占用server->start时会失败
    $port参数可以设置为0,操作系统会随机分配一个可用的端口,进行监听。可以通过读取$server->port得到分配到的端口号。

    $mode运行的模式
    SWOOLE_PROCESS多进程模式(默认)
    SWOOLE_BASE基本模式

    $sock_type指定Socket的类型,支持TCP、UDP、TCP6、UDP6、UnixSocket Stream/Dgram 6种

  • 设置运行时的各项参数

    $serv->set(array('reactor_num' => 2, //reactor thread num'worker_num' => 4,    //worker process num'backlog' => 128,   //listen backlog'max_request' => 50,'dispatch_mode' => 1,
    ));
    

    各项参数参考官方文档

  • 注册server的事件回调

    Swoole\Server是事件驱动模式,所有的业务逻辑代码必须写在事件回调函数中。当特定的网络事件发生后,底层会主动回调指定的PHP函数。

    • 事件执行顺序

    1、所有事件回调均在$server->start后发生
    2、服务器关闭程序终止时最后一次事件是onShutdown
    3、服务器启动成功后,onStart/onManagerStart/onWorkerStart会在不同的进程内并发执行,但3个事件的执行顺序是不确定的
    4、onReceive/onConnect/onClose在Worker进程中触发
    5、Worker/Task进程启动/结束时会分别调用一次onWorkerStart/onWorkerStop
    6、onTask事件仅在task进程中发生、onFinish事件仅在worker进程中发生

    • onStart

    函数原型:

    function onStart(Server $server);
    

    在此事件之前Server已进行了如下操作:

    1、已创建了manager进程
    2、已创建了worker子进程
    3、已监听所有TCP/UDP/UnixSocket端口,但未开始Accept连接和请求
    4、已监听了定时器

    接下来要执行:

    主Reactor开始接收事件,客户端可以connect到Server

    notice

    1、onStart回调中,仅允许echo、打印Log、修改进程名称。不得执行其他操作。onWorkerStart和onStart回调是在不同进程中并行执行的,不存在先后顺序。
    2、在onStart回调中可以使用异步和协程的API,但需要注意这可能会与dispatch_func和package_length_func存在冲突。请勿同时使用。
    3、onStart回调在return之前服务器程序不会接受任何客户端连接,因此可以安全地使用CURL等PHP提供的同步IO函数。

    可以在onStart回调中,将$serv->master_pid和$serv->manager_pid的值保存到一个文件中。这样可以编写脚本,向这两个PID发送信号来实现关闭和重启的操作。

    BASE模式下没有master进程,因此不存在onStart事件。请不要在BASE模式中使用使用onStart回调函数

  • onWorkerStart

    函数原型:

    function onWorkerStart(swoole_server $server, int $worker_id);
    

    此事件在Worker进程/Task进程启动时发生。这里创建的对象可以在进程生命周期内使用。

    onWorkerStart/onStart是并发执行的,没有先后顺序
    可以通过$server->taskworker属性来判断当前是Worker进程还是Task进程
    设置了worker_num和task_worker_num超过1时,每个进程都会触发一次onWorkerStart事件,可通过判断$worker_id区分不同的工作进程
    由 worker 进程向 task 进程发送任务,task 进程处理完全部任务之后通过onFinish回调函数通知 worker 进程。例如,我们在后台操作向十万个用户群发通知邮件,操作完成后操作的状态显示为发送中,这时我们可以继续其他操作。等邮件群发完毕后,操作的状态自动改为已发送。

    发生致命错误或者代码中主动调用exit时,Worker/Task进程会退出,管理进程会重新创建新的进程。这可能导致死循环,不停地创建销毁进程。
    如果想使用Reload机制实现代码重载入,必须在onWorkerStart中require你的业务文件,而不是在文件头部。在onWorkerStart调用之前已包含的文件,不会重新载入代码。
    可以将公用的、不易变的php文件放置到onWorkerStart之前。这样虽然不能重载入代码,但所有Worker是共享的,不需要额外的内存来保存这些数据。
    onWorkerStart之后的代码每个进程都需要在内存中保存一份。

    为Worker进程/Task进程重命名:

    $serv->on('WorkerStart', function ($serv, $worker_id){global $argv;if($worker_id >= $serv->setting['worker_num']) {swoole_set_process_name("php {$argv[0]} task worker");} else {swoole_set_process_name("php {$argv[0]} event worker");}
    });
    

    $worker_id是一个从[0-$worker_num)区间内的数字,表示这个Worker进程的ID
    $worker_id和进程PID没有任何关系,可使用posix_getpid函数获取PID

  • onManagerStart

    函数原型:

    void onManagerStart(swoole_server $serv);
    

    onManagerStart触发时,说明:

    Task和Worker进程已创建
    Master进程状态不明,因为Manager与Master是并行的,onManagerStart回调发生是不能确定Master进程是否已就绪
    在BASE模式下,如果设置了worker_num、max_request、task_worker_num参数,底层将创建manager进程来管理工作进程。因此会触发onManagerStart和onManagerStop事件回调。

  • onConnect

    函数原型:

    function onConnect(swoole_server $server, int $fd, int $reactorId);
    

    $server是Swoole\Server对象
    $fd是连接的文件标识符,发送数据/关闭连接时需要此参数
    $reactorId来自哪个Reactor线程

    onConnect/onClose这2个回调发生在worker进程内,而不是主进程。
    UDP协议下只有onReceive事件,没有onConnect/onClose事件

    有新的连接进入时,在worker进程中回调onConnect方法

  • onReceive

    函数原型:

    function onReceive(swoole_server $server, int $fd, int $reactor_id, string $data);
    

    $server,swoole_server对象
    $fd,TCP客户端连接的唯一标识符
    $reactor_id,TCP连接所在的Reactor线程ID
    $data,收到的数据内容,可能是文本或者二进制内容

    未开启swoole的自动协议选项,onReceive回调函数单次收到的数据最大为64K
    Swoole支持二进制格式,$data可能是二进制数据

    UDP协议,onReceive可以保证总是收到一个完整的包,最大长度不超过64K
    UDP协议下,$fd参数是对应客户端的IP,$reactor_id是客户端的端口和来源端口; 客户端ip等于long2ip(unpack(‘N’,pack(‘L’,$fd))[1]);
    TCP协议是流式的,onReceive无法保证数据包的完整性,可能会同时收到多个请求包,也可能只收到一个请求包的一部分数据
    swoole只负责底层通信,$data是通过网络接收到的原始数据。对数据解包打包需要在PHP代码中自行实现
    如果开启了eof_check/length_check/http_protocol,$data的长度可能会超过64K,但最大不超过$server->setting[‘package_max_length’]

  • onPacket

    函数原型:

    function onPacket(swoole_server $server, string $data, array $client_info);
    

    $server,swoole_server对象
    $data,收到的数据内容,可能是文本或者二进制内容
    $client_info,客户端信息包括address/port/server_socket 3项数据

    接收到UDP数据包时回调此函数,发生在worker进程中。

    服务器同时监听TCP/UDP端口时,收到TCP协议的数据会回调onReceive,收到UDP数据包回调onPacket,服务器设置的EOF或Length协议对UDP端口是不生效的,因为UDP包本身存在消息边界,不需要额外的协议处理。
    如果未设置onPacket回调函数,收到UDP数据包默认会回调onReceive函数

    onPacket回调可以通过计算得到onReceive的$fd和$reactor_id参数值。计算方法如下:

    $fd = unpack('L', pack('N', ip2long($addr['address'])))[1];
    $reactor_id = ($addr['server_socket'] << 16) + $addr['port'];
    
  • onClose

    函数原型:

    function onClose(swoole_server $server, int $fd, int $reactorId);
    

    $server 是swoole_server对象
    $fd 是连接的文件描述符
    $reactorId 来自那个reactor线程

    onClose回调函数如果发生了致命错误,会导致连接泄漏。通过netstat命令会看到大量CLOSE_WAIT状态的TCP连接。
    无论由客户端发起close还是服务器端主动调用$serv->close()关闭连接,都会触发此事件。因此只要连接关闭,就一定会回调此函数。
    这里回调onClose时表示客户端连接已经关闭,所以无需执行$server->close($fd)。代码中执行$serv->close($fd)会抛出PHP错误告警。
    swoole-1.9.7版本修改了$reactorId参数,当服务器主动关闭连接时,底层会设置此参数为-1,可以通过判断$reactorId < 0来分辨关闭是由服务器端还是客户端发起的。

  • onBufferFull

    函数原型:

    function onBufferFull(Swoole\Server $serv, int $fd);
    

    设置server->buffer_high_watermark选项来控制缓存区高水位线,单位为字节
    触发onBufferFull表明此连接$fd的发送队列已触顶即将塞满,这时不应当再向此$fd发送数据

    $server->set(['buffer_high_watermark' => 8 * 1024 * 1024,
    ]);$server->on('onBufferFull', function ($serv, $fd) {});
    
  • onBufferEmpty

    函数原型:

    function onBufferEmpty(Swoole\Server $serv, int $fd);
    

    设置server->buffer_low_watermark来控制缓存区低水位线
    触发此事件后,表明当前的$fd发送队列中的数据已被发出,可以继续向此连接发送数据了

  • onTask

    函数原型:

    function onTask(swoole_server $serv, int $task_id, int $src_worker_id, mixed $data);
    

    $task_id是任务ID,由swoole扩展内自动生成,用于区分不同的任务。$task_id和$src_worker_id组合起来才是全局唯一的,不同的worker进程投递的任务ID可能会有相同
    $src_worker_id来自于哪个worker进程
    $data 是任务的内容

    在onTask函数中 return字符串,表示将此内容返回给worker进程。worker进程中会触发onFinish函数,表示投递的task已完成。return的变量可以是任意非null的PHP变量。

    onTask函数执行时遇到致命错误退出,或者被外部进程强制kill,当前的任务会被丢弃,但不会影响其他正在排队的Task。

    4.2.12版本起,如果开启了 task_enable_coroutine 则回调函数原型是:

    $server->on('Task', function ($serv, Swoole\Server\Task $task) {//来自哪个`Worker`进程$task->workerId;//任务的编号$task->id;//任务的类型,taskwait, task, taskCo, taskWaitMulti 可能使用不同的 flags$task->flags;//任务的数据$task->data;//协程 APIco::sleep(0.2);//完成任务,结束并返回数据$task->finish([123, 'hello']);
    });
    
  • onFInish

    函数原型:

    void onFinish(swoole_server $serv, int $task_id, string $data)
    

    $task_id是任务的ID
    $data是任务处理的结果内容

    task进程的onTask事件中没有调用finish方法或者return结果,worker进程不会触发onFinish

    执行onFinish逻辑的worker进程与下发task任务的worker进程是同一个进程

  • onPipeMessage

    函数原型:

    void onPipeMessage(swoole_server $server, int $src_worker_id, mixed $message);
    

    当工作进程收到由 sendMessage 发送的管道消息时会触发onPipeMessage事件。worker/task进程都可能会触发onPipeMessage事件。

    $src_worker_id消息来自哪个Worker进程
    $message消息内容,可以是任意PHP类型

  • onWorkerError

    函数原型:

    void onWorkerError(swoole_server $serv, int $worker_id, int $worker_pid, int $exit_code, int $signal);
    

    $worker_id 是异常进程的编号
    $worker_pid 是异常进程的ID
    $exit_code 退出的状态码,范围是 0~255
    $signal 进程退出的信号

    当Worker/Task进程发生异常后会在Manager进程内回调此函数。

    此函数主要用于报警和监控,一旦发现Worker进程异常退出,那么很有可能是遇到了致命错误或者进程CoreDump。通过记录日志或者发送报警的信息来提示开发者进行相应的处理。

    signal = 11:说明Worker进程发生了segment fault段错误,可能触发了底层的BUG,请收集core dump信息和valgrind内存检测日志,向我们反馈此问题
    exit_code = 255:说明Worker进程发生了Fatal Error致命错误,请检查PHP的错误日志,找到存在问题的PHP代码,进行解决
    signal = 9:说明Worker被系统强行Kill,请检查是否有人为的kill -9操作,检查dmesg信息中是否存在OOM(Out of memory)
    如果存在OOM,分配了过大的内存。检查Server的setting配置,是否创建了非常大的Swoole\Table、Swoole\Buffer等内存模块

  • onManagerStop

    函数原型:

    void onManagerStop(swoole_server $serv);
    

    当管理进程结束时调用它,onManagerStop触发时,说明Task和Worker进程已结束运行,已被Manager进程回收。

  • onWorkerStop

    函数原型:

    function onWorkerStop(swoole_server $server, int $worker_id);
    

    此事件在worker进程终止时发生。在此函数中可以回收worker进程申请的各类资源。

    进程异常结束,如被强制kill、致命错误、core dump 时无法执行onWorkerStop回调函数

  • onWorkerExit

    函数原型:

    function onWorkerExit(swoole_server $server, int $worker_id);
    

    仅在开启reload_async特性后有效。异步重启特性,会先创建新的Worker进程处理新请求,旧的Worker进程自行退出。

    Worker进程未退出,onWorkerExit会持续触发
    onWorkerExit仅在Worker进程内触发,Task进程不执行onWorkerExit

    旧的Worker进程,在退出时先会执行一次onWorkerStop事件回调,然后会在事件循环的每个周期结束时调用onWorkerExit通知Worker进程退出。
    在onWorkerExit中尽可能地移除/关闭异步的Socket连接,最终底层检测到Reactor中事件监听的句柄数量为0时退出进程。

  • onShutdown

    函数原型:

    function onShutdown(swoole_server $server);
    

    在此之前Swoole\Server已进行了如下操作:

    已关闭所有Reactor线程、HeartbeatCheck线程、UdpRecv线程
    已关闭所有Worker进程、Task进程、User进程
    已close所有TCP/UDP/UnixSocket监听端口
    已关闭主Reactor

    强制kill进程不会回调onShutdown,如kill -9
    需要使用kill -15来发送SIGTREM信号到主进程才能按照正常的流程终止
    在命令行中使用Ctrl+C中断程序会立即停止,底层不会回调onShutdown


  • 本主主要参考swoole官方文档,结合网上其他资料进行了一些整理和归纳,仅供参考。

从头学习swoole相关推荐

  1. 从头学习计算机网络_我如何通过从头开始构建网络爬虫来自动进行求职

    从头学习计算机网络 它是如何开始的故事 (The story of how it began) It was midnight on a Friday, my friends were out hav ...

  2. 从头学习计算机网络_如何从头开始构建三层神经网络

    从头学习计算机网络 by Daphne Cornelisse 达芙妮·康妮莉丝(Daphne Cornelisse) 如何从头开始构建三层神经网络 (How to build a three-laye ...

  3. Swoole学习-Swoole入门指南

    初识Swoole Swoole官网:https://www.swoole.com/ Swoole官方文档:https://wiki.swoole.com/ 预备相关知识素材推荐 入门书籍:<tc ...

  4. 从头学习DirectDraw

    在开始学习DirectDraw编程之前,有一些题外话要说明,以下内容均是个人的心得和体会,如果其中有什么谬误之处,敬请谅解,同时个人不对可能造成的后果负责.. 以下几点是在编制DirectX应用程序时 ...

  5. 基于Jupyter Notebook从头学习机器学习 | 入门资料分享

    乾明 编译整理  量子位 报道 | 公众号 QbitAI 热心分享机器学习入门资料的人越来越多了. 今天跟大家介绍的是一个名为ZekeLabs的机构推出的机器学习入门资料. ZekeLabs是一个位于 ...

  6. linux从头学习笔记-基础命令和简单知识(1)

    [1] ubuntu windows mac | | | linux NT unix 内核 . Linux的文件系统中名字不能随便起 bin 二进制文件夹 boot 启动文件夹 dev 设备文件夹 h ...

  7. 编程小白从头学习ACM竞赛入门打卡 day 1

    今天学习(复习)到了一些简单的算术表达式.比如计算输出1+2; 在比如一些小数之间的除法运算. 比如整数用%d,浮点数用%f,如果用%f输出实数,则会像图示这样出现错误,但是我不知道为什么会出现这个数 ...

  8. 一起从头学习 vue(未完待整理)

    文章目录 Vue指令 Vue 事件 Vue中key属性的作用 (考点) 数组更新检测 对象更新检测 Class 与 Style 绑定 表单输入绑定 计算属性和侦听器 组件注册 Prop传递数据 自定义 ...

  9. 从头学习爬虫(三十)实战篇----动漫之家漫画(分析)

    本文主要分析爬取流程. 点击打开漫画 我下的漫画是食灵 一 列表页 请求和浏览器所获得的页面大致一样,所以照着写xpath,拿到列表页链接 .xpath("//div[@class='car ...

最新文章

  1. 计算机莫名其妙的游戏网页弹出,自动弹出游戏网页怎么办 自动弹出游戏网页解决方法【详解】...
  2. fastJson,gosn使用小结
  3. 在pcduino上实现图像识别的程序
  4. 利用函数来得到所有子节点号 利用函数来取得最高级的节点号
  5. SpringMVC接收json数组对象
  6. php mcript(),PHP基于mcript扩展实现对称加密功能
  7. 第一章:AJAX与jQuery
  8. eclipse调试报错,无法进入类的解决办法
  9. 怎么在mysql创建数据库怎么加入学号_数据库怎么创建学生信息表
  10. ArcGIS实验教程——实验三十三:ArcScan自动矢量化完整案例教程
  11. c语言int32u的作用,求c语言大神 帮我解释一下这段说的都是啥?
  12. DevExpress控件XtraGrid的Master-Detail用法 z
  13. 创建私服maven服务
  14. 李航:做本质的、严谨的、有意思的研究,纪念我的导师长尾真教授
  15. 能源管理模式数字化,引导未来能源服务新方向!
  16. pytorch实践(改造属于自己的resnet网络结构并训练二分类网络)
  17. [转帖]揭秘太湖之光
  18. iQOONeo6SE和iQOONeo5SE区别 哪个好 iQOONeo6SE和iQOONeo5SE哪个值得买 两者配置对比
  19. 【Java基础】包、类、接口、常量、方法、变量的命名规则
  20. 〖全域运营实战白宝书 - 运营角色认知篇⑦〗- 运营人的能力模型

热门文章

  1. 海店湾健康养生专家提醒你:想有一口美白的牙齿?吃这些就够了!
  2. 基于QML2.0的View之PathView
  3. 外呼销售运用销售工具,提升销售速度
  4. java可视化日历_Java实现可视化日历程序
  5. 官方名人堂?腾讯推出声望值系统,记录玩家的游戏历程
  6. 天猫魔盒屏蔽自动升级
  7. Jquery+ajax上传文件
  8. html+css网页练习二
  9. linux查看文件位置
  10. 程序员8小时以外的挣钱路子【转】