swoole学习之: 服务端(异步风格)-TCP/UDP服务器 - 事件

参考自官方文档 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
  • 服务器设置的 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可能会相同.
  • 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 , 分配了过大的内存.
      1. 检查 Server setting 设置, 是否 socket_buffer_size 等分配过大
      1. 是否创建了非常大的 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.

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值