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