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服务端的初步接触,这一节就先到这里啦。大家下周见:)

 

 

---------------------------  我是可爱的分割线  ----------------------------

最后博主借地宣传一下,漳州编程小组招新了,这是一个面向漳州青少年信息学/软件设计的学习小组,有意向的同学点击链接,联系我吧。

posted on 2020-09-02 21:51  咚..咚  阅读(603)  评论(0编辑  收藏  举报

导航