参考自官方文档 https://wiki.swoole.com/#/server/events , 纯手打
onStart
启动后在主进程(master)的主线程回调此函数 .
function onStart(Swoole\Server $server);
在此事件之前, Server已进行了如下操作:
- 启动创建完成Manager进程
- 启动创建已完成Worker进程
- 监听所有TCP/UDP/unixSocket端口, 但未开始Accept连接和请求
- 监听了定时器 接下来要执行:
- 主Reactor开始接收事件, 客户端可以
connect到Server
onStart 回调中, 仅允许 echo 、打印Log、修改进程名称操作。 不得执行其他操作(如调用Server相关函数操作, 因为服务尚未就绪)。 onWorkerStart 和 onStart 回调是在不同进程中并行执行的, 不存在先后顺序.
可以在 onStart 回调中, 将 $server->master_pid 和 $server->manager_pid 的值保存到一个文件中. 这样可以编写脚本, 向这个两个PID发送信号来实现关闭和重启的操作.
在 SWOOLE_BASE 模式下没有 master 进程,因此不存在 onStart 事件, 如果使用了会抛出warning.
onShutdown
在Server正常结束时回调 .
function onShutdown(Swoole\Server $server);
在此回调之前已进行了如下操作:
- 已关闭所有
Reactor线程、HeartbeatCheck线程、UpdRecv线程 - 已关闭所有
Worker进程、Task进程、User进程 - 已
close所有TCP、UDP、UnixSocket监听端口 - 已关闭主
Reactor
注意: onShutdown 回调时, 已不存在协程环境, 如果需要使用协程相关API需要手动调用 Co\run 来创建协程容器.
onWorkerStart
在Worker/Task进程启动时发生 , 这里创建的对象可以在进程生命周期内使用.
function onWorkerStart(Swoole\Server $server, int $workerId);
注意: int $workerId 是 Worker 进程 id , 不是进程的PID
-
onWorkerStart与onStart是并发执行的, 没有先后之分. - 可以通过
$server->taskworker属性来判断当前是Worker进程还是Task进程 - 设置了
worker_num和task_worker_num是超过1时, 每个进程都会触发一次onWorkerStart回调事件, 可通过判断$worker_id区分不同的工作进程. - 由
worker进程向task进程发送任务,task进程处理完全部任务之后通过onFinish回调函数通知worker进程. 例如, 在后台操作向10w个用户群发邮件通知, 操作完成后操作的状态显示为发送中, 这时可以继续其他操作, 等邮件群发完毕后, 操作的状态自动改位已发送.
下面的示例是给 Worker / Task 进程重命名:
$server->on('workerStart', function(Swoole\Server $server, $worker_id){
global $argv;
//task进程的id是从worker_num开始的
if($worker_id >= $server->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, $server->setting['worker_num'] - 1] -
Task进程id范围是[$server->setting['worker_num'], $server->setting['worker_num'] + $server->setting['task_worker_num'] - 1]
如果想使用 reload 机制实现代码重新载入, 必须在 onWorkerStart 中 require 你的业务文件, 而不是在文件头部, 因为在 onWorkerStart 调用之前已经包含的文件, reload时不会重新载入.
可以将公用的、不易变的php文件放到 onWorkerStart 之前. 这样虽然不能重新载入代码, 但是所有 worker 是共享的, 不需要额外的内存来保存这些数据. onWorkerStart 之后的代码在每个进程都需要在内存中保存一份.
协程支持:
- 在
onWorkerStart回调函数中会自动创建协程, 所以onWorkerStart可以调用协程API
注意: 发生致命错误或者代码中主动调用exit时, Worker / Task 进程会退出, 但是管理进程会重新创建新的进程, 这可能导致死循环,不停地创建又销毁进程....
onWorkerStop
在Worker进程正常终止时发生 , 可以在此函数中回收 Worker 进程申请的各类资源.
function onWorkerStop(Swoole\Server $server, int $workerId);
注意:
- 进程异常结束, 如被强制kill、致命错误、
core dump时不会执行此回调. - 不要在此回调内调用任何异步或协程相关API, 因此在此之前底层已经销毁了所有事件循环设施.
onWorkerExit
仅在开启 reload_async 特性后有效
function onWorkerExist(Swoole\Server $server, int $workerId);
注意:
-
Worker进程未退出,onWorkerExit会持续触发 -
onWorkerExit会在Worker进程内触发,Task进程内如果存在事件循环也会触发 - 在
onWorkerExit中尽可能地移除/关闭异步的Socket连接, 最终底层检测到事件循环中事件监听的句柄数量为0时退出进程 - 当进程没有事件句柄在监听时, 进程结束时将不会回调此函数
- 等待
Worker进程退出后才会执行onWorkerStop事件回调
onConnect
有新的连接进入时,在worker进程中回调 .
function onConnect(Swlloe\Server $server, int $fd, int $reactorId);
注意:
-
onConnect/onClose这2个回调发生在Worker进程内, 而不是主进程内. -
UDP协议下只有onReceive事件, 没有onConnect/onClose事件 - 当
dispatch_mode=1或3时:- 在此模式下
onConnect/onReceive/onClose可能会被投递到不同的进程. 连接相关的PHP对象数据, 无法实现: 在onConnect回调初始化数据,在onClose清理数据. -
onConnect/onReceive/onClose这三种事件可能会并发执行, 可能会带来异常.
- 在此模式下
onReceive
接收到数据时回调此函数, 发生在 worker 进程中 .
function onReceive(Swoole\Server $server, int $fd, int $reactorId, string $data);
关于TCP协议下包完整性 , 参考 TCP数据包边界问题
- 使用底层提供的
open_eof_check/open_length_check/open_http_protocol等配置可以保证数据包的完整性 - 不使用底层的协议处理, 在
onReceive后PHP代码中自行对数据分析, 合并/拆分数据包.
例如, 代码中可以增加一个 $buffer = array() , 使用 $fd 作为 key , 来保存上下文数据. 每次收到数据进行字符串拼接, $buffer[$fd] .= $data ,然后再判断 $buffer[$fd] 字符串是否为一个完整的数据包.
默认情况下, 同一个 fd 会被分配到同一个 worker 中, 所以数据可以拼接起来. 但是 dispatch_mode=3 时, 请求数据是抢占式的, 同一个 fd 发来的数据可能会被分配到不同的进程,所以无法使用上述的数据包拼接方法.
多端口监听 , 参考 多端口监听
当主服务器设置了协议后, 额外监听的端口默认会继承主服务器的设置. 需要显示调用 set 方法来重新设置端口的协议.
$server = new Swoole\Http\Server('127.0.0.1', 9501);//返回Swoole\Server\Port对象
$port2 = $server->listen('127.0.0.1', 9502, SWOOLE_SOCK_TCP);//增加监听的端口, 但仍然是一个HTTP服务
$port2->on('receive', function(Swoole\Server $server, $fd, $reactorId, $data){
echo '[#'.$server->worker_id.'] Client fd='.$fd.': '.trim($data).PHP_EOL;
});
这里虽然调用了 on 方法注册了 onReceive 回调函数, 但是由于没有调用 set 方法覆盖主服务器的协议, 新监听的9502端口依然使用HTTP协议. 使用 telnet 客户端连接9502端口发送字符串时服务器不会触发 onReceive .
注意 :
- 未开启自动协议选项,
onReceive单此收到的数据最大为64k - 开启了自动协议处理选项,
onReceive将收到完整的数据包,最大不超过package_max_length - 支持二进制格式,
$data可能是二进制数据, 比如图像,文件等.
onPacket
接收到 UDP 数据包时回调此函数, 发生在 worker 进程中 .
function onPacket(Swoole\Server $server, string $data, array $clientInfo);
-
array $clientInfo, 客户端信息, 宝库address/port/server_socket等多项客户端信息数据, 参考 UDP服务器
注意 :
- 服务器同时监听
TCP和UDP端口时:- 收到TCP协议的数据会回调
onReceive - 收到UDP数据包会回调
onPacket
- 收到TCP协议的数据会回调
- 服务器设置的
EOF或Length等自动协议处理, 对UDP端口无效, 因为 UDP包本身存在消息边界 , 不需要额外的协议处理.
onClose
TCP 客户端连接关闭后, 在 Worker 进程中回调此函数 .
function onClose(Swoole\Server $server, int $fd, int $reactorId);
-
int $reactorId: 来自哪个reactor线程, 主动close时为负数.
关于 主动关闭 :
- 当服务器主动关闭连接时, 底层会设置参数
$reactorId为-1, 可以通过判断该值$reactorId < 0来分辨关闭是由服务器端还是客户端发起的. - 只有在PHP代码中主动调用close方法, 才被视为主动关闭.
关于 心跳检测 :
- 心跳检测是由心跳检测线程通知关闭的, 关闭时
onClose的$reactorId值不为-1.
注意 :
-
onClose回调函数内如果发生了致命错误, 会导致连接池泄漏. 通过netstat命令会看到大量CLOSE_WAIT状态的TCP连接, 参考 swoole视频教程 . - 无论是由客户端发起
close, 还是服务器端主动调用$server->close()关闭连接, 都会触发此事件. 因此只要连接关闭, 都一定会调用此函数. -
onClose中依然可以调用getClientInfo方法获取到连接信息, 在onClose回调函数执行完毕后才会调用close关闭TCP连接. - 回调
onClose时表示客户端连接已经关闭, 无需再执行$server->close($fd), 否则会抛出PHP错误警告. (这与上面一条是不是冲突????????)
onTask
在 task 进程内被调用 .
-
worker进程可以使用task函数向task_worker进程投递新的任务. - 当前的Task进程在调用
onTask回调函数时会将进程状态切换为忙碌, 这时将不再接受新的Task; - 当
onTask函数返回时会将进程状态切换为空闲, 然后可以继续接收新的Task.
function onTask(Swoole\Server $server, int $task_id, int $src_worker_id, mixed $data);
-
int $task_id: 执行任务的task进程id.- ``$task_id
和$src_worker_id`组合起来才是全局唯一的, 不同的worker进程投递的任务ID可能会相同.
- ``$task_id
-
int $src_worker_id: 投递任务的worker进程id
提示:
- v4.2.12起, 如果开启了
task_enable_coroutine则回调函数原型是:
$server->on('task', function(Swoole\Server $server, Swoole\Server\Task $task){
var_dump($task);
$task->finish([123, 'hello']);//完成任务, 结束并返回数据.
});
- 返回执行结果到
worker进程- 在
onTask函数中return字符串, 表示将此内容返回给worker进程.worker进程中会触发onFinish函数, 表示投递的task任务已完成, 当然你也可以通过Swoole\Server->finish()来触发onFinish函数, 而无手续再return. - return的变量可以是任意非
null的PHP变量
- 在
onFinish
此回调函数在worker进程被调用, 当 worker 进程投递的任务在 task 进程中完成时, task 进程会通过 Swoole\Server->finish() 方法将任务处理的结果发送给 worker 进程 .
function onFinish(Swoole\Server $server, int $task_id, mixed $data);
-
mixed $data: 任务处理的结果内容.
注意 :
-
task进程的onTask事件中没有调用finish方法或者return结果,worker进程不会触发onFinish - 执行
onFinish逻辑的worker进程与洗发task任务的worker进程是同一个进程
onPipeMessage
当工作进程收到由 $server->sendMessage() 发送的 UnixSocket 消息时会触发, worker/task 进程都可能会触发该事件 .
function onPipeMessage(Swoole\Server $server, int $src_worker_id, mixed $message);
-
int $src_worker_id: 消息来自哪个worker进程
onWorkerError
当 Worker/Task 进程发生异常后会在 Manager 进程内回调此函数 .
此函数主要用于报警和监控, 一旦发现worker进程异常退出, 那么很有可能是遇到了致命错误或者进程Core Dump. 通过记录日志或者发送报警的信息来提示开发者进行相应的处理.
function onWorkerError(Swoole\Server $server, int $worker_id, int $worker_pid, int $exit_code, int $signal);
-
int $worker_id: 异常worker进程的id -
int $worker_pid: 异常worker进程的pid -
int $exit_code: 退出的状态码, 范围是0~255 -
int $signal: 进程退出的信号
常见错误 :
-
signal=11: 说明Worker进程发生了segment_fault段错误, 可能触发了底层的BUG, 请收集core dump信息和valgrind内存检测日志, 向swoole开发组反馈此问题. -
exit_code=255: 说明Worker进程发生了Fatal Error致命错误, 请检查PHP的错误日志, 找到存在问题的PHP代码进行解决. -
signal=9: 说明Worker进程被系统强行kill, 请检查是否有人为的kill -9操作, 检查dmesg信息中是否存在OOM(out of memory).` - 如果存在
OOM, 分配了过大的内存.-
- 检查
Server的setting设置, 是否socket_buffer_size等分配过大
- 检查
-
- 是否创建了非常大的
Swoole\Table内存模块.
- 是否创建了非常大的
-
onManagerStart
当管理进程启动时触发此事件 .
function onManagerStart(Swoole\Server $server);
- 在这个回调函数中, 可以修改管理进程的名称
- 在4.2.12以前的版本中:
manager进程中不能添加定时器, 不能投递task任务, 不能用协程 - 在4.2.12或更高版本中:
manager进程可以使用基于信号实现的同步模式定时器 - manager进程中可以调用
sendMessage接口向其他工作进程发送消息
启动顺序 :
-
Task和Worker进程已创建 -
Master进程状态不明, 因为Manager与Master是并行的,onManagerStart回调发生时不能确定Master进程是否已就绪.
BASE 模式:
- 在
SWOOLE_BASE模式下, 如果设置了worker_num、task_worker_num、max_request参数, 底层将创建manager进程来管理工作进程, 因此会触发onManagerStart和onManagerStop事件回调.
onManagerStop
当管理进程结束时触发
function onManagerStop(Swoole\Server $server);
onManagerStop 触发时, 说明 Task 和 Worker 进程已结束运行, 已被 Manager 进程回收.
onBeforeReload
Worker进程 Reload 之前触发此事件, 在Manager进程中回调
function onBeforeReload(Swoole\Server $server);
onAfterReload
Worker进程 Reload 之后触发此事件, 在Manager进程中回调 .
function onAfterReload(Swoole\Server $server);
事件执行顺序
- 所有时间回调均在
$server->start()后发生 - 服务器关闭程序终止时最后一次事件是
onShutdown - 服务器启动成功后,
onStart/onManagerStart/onWorkerStart会在不同的进程内并发执行, 它们的执行顺序是不确定的 -
onReceive/onConnect/onClose在Worker进程中触发 -
Worker/Task进程启动/结束时会分别调用一次onWorkerStart/onWorkerStop -
onTask事件仅在task进程中发生 -
onFinish事件仅在worker进程中发生
回调对象
启用 event_object 后,以下事件回调将使用对象风格
Swoole\Server\Event
-
onConnect,onReceive,onClose
$server->on('connnect', function(Swoole\Server $server, Swoole\Server\Event $object){
var_dump($object);
});
Swoole\Server\Packet
onPacket
$server->on('packet', function(Swoole\Server $server, Swoole\Server\Packet $object){
var_dump($object);
});
Swoole\Server\PipeMessage
onPipeMessage
$server->on('pipeMessage', function(Swoole\Server $server, Swoole\Server\PipeMessage $msg){
var_dump($msg);
$obj = $msg->data;
$server->sendto($object->address, $object->port, $object->data, $obejct->server_socket);
});
Swoole\Server\StatusInfo
onWorkerError
$server->on('workerError', function(Swoole\Server $server, Swoole\Server\StatusInfo $info){
var_dump($info);
});
Swoole\Server\Task
onTask
$server->on('task', function(Swoole\Server $server, Swoole\Server\Task $task){
var_dump($task);
});
Swoole\Server\TaskResult
onFinish
$server->on('finish', function(Swoole\Server $server, Swoole\Server\TaskResult $result){
var_dump($result);
});
Taht's all for the two days' typing.