参考自官方文档 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.