Swoole从入门到入土(2)——TCP服务器[初步接触]
我们知道Swoole弥补了PHP没办法实现长连接的短板,在接下来的话题中,我们会从TCP服务器、HTTP服务器、WebSocket服务器、协程、管道、中间件等话题,一个个进行讨论。
1、开篇
我们以Swoole一个最简单的例子作为开篇:
//创建Server对象,监听 127.0.0.1:9501 端口 $server = new Swoole\Server('0.0.0.0', 9501); //监听连接进入事件 $server->on('Connect', function ($server, $fd) { echo "Client: Connect.\n"; }); //监听数据接收事件 $server->on('Receive', function ($server, $fd, $from_id, $data) { $server->send($fd, "Server: " . $data); }); //监听连接关闭事件 $server->on('Close', function ($server, $fd) { echo "Client: Close.\n"; }); //启动服务器 $server->start();
把这段代码,保存为文件swoole.php,接着我们用php命令运行:php swoole.php。这时我们就可以看到一个简单的TCP服务器在侦听中。
代码的大致作用是:
1) 服务端发现客户端有连接的时候,就会输出文字“Client:Connect”
2) 一旦收到客户端的消息,就会向客户端发送“Server:消息原文”
3) 连接断开时,输出文字“Client:Close”
2、验证
现在为了让大家更直观的看到连接情况,我们用SockeTool工具进行实验:
1) php swoole.php启动服务端。
2) SocketTool创建TCP客户端,填入服务端对应的IP和端口:
3) 测试发送数据,并查看接收信息:
3、相关函数详解
1) 构造函数
Swoole\Server(string $host = '0.0.0.0', int $port = 0, int $mode = SWOOLE_PROCESS, int $sockType = SWOOLE_SOCK_TCP): \Swoole\Server
$host:监听的IP地址,可支持IPV4也可支持IPV6,0.0.0.0为IPV4的所有地址,::(相当于0:0:0:0:0:0:0:0)为IPV6的所有地址。
$port:监听的端口,如果端口小于1024则需要root权限。
$mode:SWOOLE_PROCESS(默认,多进程模式),SWOOLE_BASE(基要模式)。
$socketType:SWOOLE_SOCK_TCP(默认,IPV4 TCP)、SWOOLE_SOCK_TCP6、SWOOLE_SOCK_UDP、SWOOLE_SOCK_UDP6
2) 函数on:调用事件
Swoole\Server->on(string $event, mixed $callback): void
$event:事件名称,不区分大小写
$callback:回调函数,各个事件的回调函数的参数格式详见事件说明。回调函数可以是函数名的字符串,类静态方法,对象方法数组,匿名函数。
3) 函数start:启动服务器,监听所有指定的端口
Swoole\Server->start(): bool
本函数没有参数,但有以下事项必须了解:
· 启动成功后会创建 worker_num+2 个进程。Master 进程 +Manager 进程 +worker_num 个 Worker 进程。
· 启动失败会立即返回 false
· 启动成功后将进入事件循环,等待客户端连接请求。start 方法之后的代码不会执行
· 服务器关闭后,start 函数返回 true,并继续向下执行
· 设置了 task_worker_num 属生值会增加相应数量的 Task 进程
· 方法列表中 start 之前的方法仅可在 start 调用前使用,在 start 之后的方法仅可在 onWorkerStart、onReceive 等事件回调函数中使用
4) 函数send:向客户端发送数据
Swoole\Server->send(int $fd, string $data, int $serverSocket = -1): bool
$fd:客户端文件描述符,每个客户端分配一个描述符,可以理解为客户端的ID。
$data:发送的数据,TCP 协议最大不得超过 2M,可修改 buffer_output_size 改变允许发送的最大包长度。
$serverSocket:向 UnixSocket DGRAM 对端发送数据时需要此项参数,TCP 客户端不需要填写。
5) 函数getClientInfo:获取连接信息
Swoole\Server->getClientInfo(int $fd, int $extraData, bool $ignoreError = false): bool|array
$fd:客户端文件描述符,每个客户端分配一个描述符,可以理解为客户端的ID。
$extraData:扩展信息,保留参数,目前无任何效果。
$ignoreError:是否忽略错误,如果设置为 true,即使连接关闭也会返回连接的信息。
注意:当使用 dispatch_mode = 1(轮循模式)或3(抢占模式) 配置时,考虑到这种数据包分发策略用于无状态服务,当连接断开后相关信息会直接从内存中删除,所以 Server->getClientInfo 是获取不到相关连接信息的。
/***** 连接信息数据示例 *****/ $fd_info = $server->getClientInfo($fd); var_dump($fd_info); array(7) { ["reactor_id"]=> int(3) ["server_fd"]=> int(14) ["server_port"]=> int(9501) ["remote_port"]=> int(19889) ["remote_ip"]=> string(9) "127.0.0.1" ["connect_time"]=> int(1390212495) ["last_time"]=> int(1390212760) }
------------ 常识科普时间 ------------
讨论到这里,我们先科普一下几个名字:
Master 进程、Reactor 线程、Worker 进程、Task 进程、Manager 进程的区别与联系
Master进程:php swoole.php启动的进程。
Reactor线程:(注意)这是一个线程,是在Master进程中创建的线程,负责处理网络IO,收发数据,不执行任何PHP代码。
Worker进程:接受由Reactor线程投递过来的数据,处理后生成响应数据,再交给Reactor线程。多进程模式运行。可以是异步非阻塞,也可以是同步阻塞。
TaskWorker进程:接受由Worker进程投递的任务(通过Swoole\Server->task/taskwait/taskCo/taskWaitMulti 方法投递),处理完成之后将结果返回给Worker进程(使用使用 Swoole\Server->finish)。以多进程方式运行,完全是同步阻塞模式。
Manager进程:负责创建 / 回收 worker/task 进程。
------------ 常识科普时间结束 ------------
4、相关事件详解
1) 事件onConnect:有新的连接进入时,在 worker 进程中回调。
function onConnect(Swoole\Server $server, int $fd, int $reactorId);
$server:服务器server对象。
$fd:连接(客户端)文件描述符,可理解为标识客户端的ID,每个客户端分配一个唯一的描述符。
$reactorId:所在的reactor线程的ID。
注意:onConnect、onReceive和onClose三个事件有可能会并发执行,从而带来异常。
当dispatch_mode为1(轮循模式)或3(抢占模式)时,数据包可能会被投递到不同的进程。连接相关的 PHP 对象数据,无法实现在 onConnect 回调初始化数据,onClose 清理数据。
(:关于属性以及属性的设置下一篇会进行介绍:)
2) 事件onReceive:接收到数据时回调此函数,发生在 worker 进程中。
function onReceive(Swoole\Server $server, int $fd, int $reactorId, string $data);
$server:服务器server对象。
$fd:连接(客户端)文件描述符,可理解为标识客户端的ID,每个客户端分配一个唯一的描述符。
$reactorId:所在的reactor线程的ID。
$data:收到的数据内容,可以是文本,也可以是二进制。
注意:TCP数据包存在粘包问题,使用底层提供的 open_eof_check/open_length_check/open_http_protocol 等配置可以保证数据包的完整性
不使用底层的协议处理,在 onReceive 后 PHP 代码中自行对数据分析,合并 / 拆分数据包。
(:关于TCP粘包问题的处理,以后的章节会进行介绍:)
3) 事件onClose:TCP 客户端连接关闭后,在 worker 进程中回调此函数。
function onClose(Swoole\Server $server, int $fd, int $reactorId);
$server:服务器server对象。
$fd:连接(客户端)文件描述符,可理解为标识客户端的ID,每个客户端分配一个唯一的描述符。
$reactorId:所在的reactor线程的ID。当服务器主动关闭连接时,底层会设置此参数为 -1,可以通过判断 $reactorId < 0 来分辨关闭是由服务器端还是客户端发起的。只有在 PHP 代码中主动调用 close 方法被视为主动关闭。心跳检测是由心跳检测线程通知关闭的,关闭时 onClose 的 $reactorId 参数不为 -1。
注意:
-onClose 回调函数如果发生了致命错误,会导致连接泄漏。通过 netstat 命令会看到大量 CLOSE_WAIT 状态的 TCP 连接 。无论由客户端发起 close 还是服务器端主动调用 $server->close() 关闭连接,都会触发此事件。因此只要连接关闭,就一定会回调此函数
-onClose 中依然可以调用 getClientInfo 方法获取到连接信息,在 onClose 回调函数执行完毕后才会调用 close 关闭 TCP 连接。这里回调 onClose 时表示客户端连接已经关闭,所以无需执行 $server->close($fd)。代码中执行 $server->close($fd) 会抛出 PHP 错误警告。
(:这一节又有一个问题“心跳检测”,在以后的章节敬请期待啦:)
对于TCP服务端的初步接触,这一节就先到这里啦。大家下周见:)
--------------------------- 我是可爱的分割线 ----------------------------
最后博主借地宣传一下,漳州编程小组招新了,这是一个面向漳州青少年信息学/软件设计的学习小组,有意向的同学点击链接,联系我吧。