Swoole从入门到入土(7)——TCP服务器[大杂烩]

这一篇是异步风格的TCP服务器的最后一篇,主要的目的是疏理之前几篇没提到的一些比较有用的属性、配置、函数与事件,当然不是全部。如果想知道全部,请查看官网。

 

1、属性

Swoole\Server->setting:Server::set() 函数所设置的参数会保存到 Server->$setting 属性上。在回调函数中可以访问运行参数的值。

$server = new Swoole\Server('127.0.0.1', 9501);
$server->set(array('worker_num' => 4));
echo $server->setting['worker_num'];

 

Swoole\Server->worker_id: int:得到当前 Worker 进程的编号,包括 Task 进程。

$server->on('workerstart', function ($server, int $workerId){
    echo "workerId:" . $workerId . PHP_EOL;
    echo "worker_id:" . $server->worker_id . PHP_EOL;
});

 

Swoole\Server->taskworker: bool:当前进程是否是 Task 进程。

- true 表示当前的进程是 Task 工作进程
- false 表示当前的进程是 Worker 进程

 

Swoole\Server->connections:TCP 连接迭代器,可以使用 foreach 遍历服务器当前所有的连接,此属性的功能与 Server->getClientList 是一致的,但是更加友好。遍历的元素为单个连接的 fd。

注意:

-$connections 属性是一个迭代器对象,不是 PHP 数组,所以不能用 var_dump 或者数组下标来访问,只能通过 foreach 进行遍历操作。

-SWOOLE_BASE 模式下不支持跨进程操作 TCP 连接,因此在 BASE 模式中,只能在当前进程内使用 $connections 迭代器。

foreach ($server->connections as $fd) {
  var_dump($fd);
}
echo "当前服务器共有 " . count($server->connections) . " 个连接\n";

 

Swoole\Server->ports:监听端口数组,如果服务器监听了多个端口可以遍历 Server::$ports 得到所有 Swoole\Server\Port 对象。其中 swoole_server::$ports[0] 为构造方法所设置的主服务器端口。

$ports = $server->ports;
$ports[0]->set($settings);
$ports[1]->on('Receive', function () {
    //callback
});

 

 

2、配置

编者注:所有的配置都需要通过Swoole\Server->set()函数进行设置,具体用法如果忘了可以看这里 

max_conn:服务器程序,最大允许的连接数。【默认值:ulimit -n】。如 max_connection => 10000, 此参数用来设置 Server 最大允许维持多少个 TCP 连接。超过此数量后,新进入的连接将被拒绝。

注意:

-请勿设置 max_connection 超过 1M。在 4.2.9 或更高版本,当底层检测到 ulimit -n 超过 100000 时将默认设置为 100000,原因是某些系统设置了 ulimit -n 为 100万,需要分配大量内存,导致启动失败。

-此选项设置过小底层会抛出错误,并设置为 ulimit -n 的值。最小值为 (worker_num + task_worker_num) * 2 + 32

 

task_max_request:设置 task 进程的最大任务数。【默认值:0】

说明:设置 task 进程的最大任务数。一个 task 进程在处理完超过此数值的任务后将自动退出。这个参数是为了防止 PHP 进程内存溢出。如果不希望进程自动退出可以设置为 0。

 

daemonize:守护进程化【默认值:0】

注意:

设置 daemonize => 1 时,程序将转入后台作为守护进程运行。长时间运行的服务器端程序必须启用此项。
如果不启用守护进程,当 ssh 终端退出后,程序将被终止运行。

提示:

-启用守护进程后,标准输入和输出会被重定向到 log_file
-如果未设置 log_file,将重定向到 /dev/null,所有打印屏幕的信息都会被丢弃
-启用守护进程后,CWD(当前目录)环境变量的值会发生变更,相对路径的文件读写会出错。PHP 程序中必须使用绝对路径

 

log_file:指定 Swoole 错误日志文件。在 Swoole 运行期发生的异常信息会记录到这个文件中,默认会打印到屏幕。开启守护进程模式后 (daemonize => true),标准输出将会被重定向到 log_file。在 PHP 代码中 echo/var_dump/print 等打印到屏幕的内容会写入到 log_file 文件。

提示:

在日志信息中,进程 ID 前会加一些标号,表示日志产生的线程 / 进程类型:# Master 进程   /   $ Manager 进程   /   * Worker 进程   /   ^ Task 进程

 

log_level:设置 Server 错误日志打印的等级,范围是 0-6。低于 log_level 设置的日志信息不会抛出。【默认值:SWOOLE_LOG_DEBUG】

日志等级:

 注:SWOOLE_LOG_DEBUG 和 SWOOLE_LOG_TRACE 两种日志,必须在编译 swoole 扩展时使用 --enable-debug-log 或 --enable-trace-log 后才可以使用。正常版本中即使设置了 log_level = SWOOLE_LOG_TRACE 也是无法打印此类日志的。

 

backlog:设置 Listen 队列长度。如 backlog => 128,此参数将决定最多同时有多少个等待 accept 的连接。

注意:

-我们知道 TCP 有三次握手的过程,客户端 syn=>服务端 syn+ack=>客户端 ack,当服务器收到客户端的 ack 后会将连接放到一个叫做 accept queue 的队列里面。
-队列的大小由 backlog 参数和配置 somaxconn 的最小值决定,我们可以通过 ss -lt 命令查看最终的 accept queue 队列大小,Swoole 的主进程调用 accept,从 accept queue 里面取走。 当 accept queue 满了之后连接有可能成功,也有可能失败,失败后客户端的表现就是连接被重置。
-或者连接超时,而服务端会记录失败的记录,可以通过 netstat -s|grep 'times the listen queue of a socket overflowed 来查看日志。如果出现了上述现象,你就应该调大该值了。

-幸运的是 Swoole 的 SWOOLE_PROCESS 模式与 PHP-FPM/Apache 等软件不同,并不依赖 backlog 来解决连接排队的问题。所以基本不会遇到上述现象。

 

open_tcp_keepalive:在 TCP 中有一个 Keep-Alive 的机制可以检测死连接,应用层如果对于死链接周期不敏感或者没有实现心跳机制,可以使用操作系统提供的 keepalive 机制来踢掉死链接。 在 Server::set() 配置中增加 open_tcp_keepalive=>1 表示启用 TCP keepalive。 另外,有 3 个选项可以对 keepalive 的细节进行调整:

tcp_keepidle:单位秒,连接在 n 秒内没有数据请求,将开始对此连接进行探测。

tcp_keepcount:探测的次数,超过次数后将 close 此连接。

tcp_keepinterval:探测的间隔时间,单位秒。

$serv = new Swoole\Server("192.168.2.194", 6666, SWOOLE_PROCESS);
$serv->set(array(
    'worker_num' => 1,
    'open_tcp_keepalive' => 1,
    'tcp_keepidle' => 4, //4s没有数据传输就进行检测
    'tcp_keepinterval' => 1, //1s探测一次
    'tcp_keepcount' => 5, //探测的次数,超过5次后还没回包close此连接
));

 

heartbeat_check_interval:启用心跳检测【默认值:false】。此选项表示每隔多久轮循一次,单位为秒。如 heartbeat_check_interval => 60,表示每 60 秒,遍历所有连接,如果该连接在 120 秒内(heartbeat_idle_time 未设置时默认为 interval 的两倍),没有向服务器发送任何数据,此连接将被强制关闭。若未配置,则不会启用心跳,该配置默认关闭。

注意:

-Server 并不会主动向客户端发送心跳包,而是被动等待客户端发送心跳。服务器端的 heartbeat_check 仅仅是检测连接上一次发送数据的时间,如果超过限制,将切断连接。

-被心跳检测切断的连接依然会触发 onClose 事件回调。

-heartbeat_check 仅支持 TCP 连接。

 

heartbeat_idle_time:连接最大允许空闲的时间,需要与 heartbeat_check_interval 配合使用

注意:

-启用 heartbeat_idle_time 后,服务器并不会主动向客户端发送数据包。

-如果只设置了 heartbeat_idle_time 未设置 heartbeat_check_interval 底层将不会创建心跳检测线程,PHP 代码中可以调用 heartbeat 方法手工处理超时的连接。

 

open_http_protocol:启用 HTTP 协议处理。【默认值:false】设置为 false 表示关闭 HTTP 协议处理。

 

open_mqtt_protocol:启用 MQTT 协议处理。【默认值:false】启用后会解析 MQTT 包头,worker 进程 onReceive 每次会返回一个完整的 MQTT 数据包。

 

open_websocket_protocol:启用 WebSocket 协议处理。【默认值:false】设置为 false 表示关闭 websocket 协议处理。

注意:设置 open_websocket_protocol 选项为 true 后,会自动设置 open_http_protocol 协议也为 true。

 

open_cpu_affinity:启用 CPU 亲和性设置。 【默认 false 关闭】在多核的硬件平台中,启用此特性会将 Swoole 的 reactor线程 /worker进程绑定到固定的一个核上。可以避免进程 / 线程的运行时在多个核之间互相切换,提高 CPU Cache 的命中率。

 

 

3、函数

 addListener():增加监听的端口。业务代码中可以通过调用 Server->getClientInfo 来获取某个连接来自于哪个端口。

Swoole\Server->addListener(string $host, int $port, int $sockType = SWOOLE_SOCK_TCP): bool|Swoole\Server\Port

$host:指定监听的 ip 地址;IPv4 使用 127.0.0.1 表示监听本机,0.0.0.0 表示监听所有地址;IPv6 使用::1 表示监听本机,:: (相当于 0:0:0:0:0:0:0:0) 表示监听所有地址

$port:指定监听的端口,如 9501

$sockType:$sockType 值为 UnixSocket Stream/Dgram,此参数将被忽略,如果此端口被占用 server->start 时会失败

注意:

-监听 1024 以下的端口需要 root 权限。
-主服务器是 WebSocket 或 HTTP 协议,新监听的 TCP 端口默认会继承主 Server 的协议设置。必须单独调用 set 方法设置新的协议才会启用新协议

 

stop():使当前 Worker 进程停止运行,并立即触发 onWorkerStop 回调函数。

Swoole\Server->stop(int $workerId = -1, bool $waitEvent = false): bool

$workerId:指定 worker id

$waitEvent:控制退出策略,false 表示立即退出,true 表示等待事件循环为空时再退出

 注意:

-异步 IO 服务器在调用 stop 退出进程时,可能仍然有事件在等待。比如使用了 Swoole\MySQL->query,发送了 SQL 语句,但还在等待 MySQL 服务器返回结果。这时如果进程强制退出,SQL 的执行结果就会丢失了。
-设置 $waitEvent = true 后,底层会使用异步安全重启策略。先通知 Manager 进程,重新启动一个新的 Worker 来处理新的请求。当前旧的 Worker 会等待事件,直到事件循环为空或者超过 max_wait_time 后,退出进程,最大限度的保证异步事件的安全性。

 

tick():添加 tick 定时器,可以自定义回调函数。此函数是 Swoole\Timer::tick 的别名。

Swoole\Server->tick(int $millisecond, mixed $callback): void

编者注:文档这里有错,返回值是int(一个timer的id),并非是void

$millisecond:间隔时间【毫秒】

$callback:回调函数

注意:

-Worker 进程结束运行后,所有定时器都会自动销毁
-tick/after 定时器不能在 Server->start 之前使用

 

after():添加一个一次性定时器,执行完成后就会销毁。此函数是 Swoole\Timer::after 的别名。

Swoole\Server->after(int $millisecond, mixed $callback)

编者注:文档这里有错,返回值是int(一个timer的id)

$millisecond:执行时间【毫秒】,在 Swoole v4.2.10 以下版本最大不得超过 86400000

$callback:回调函数

 

clearTimer():清除 tick/after 定时器,此函数是 Swoole\Timer::clear 的别名。

Swoole\Server->clearTimer(int $timerId): bool

$timerId:指定定时器 id

注:clearTimer 仅可用于清除当前进程的定时器

 

close():关闭客户端连接。

Swoole\Server->close(int $fd, bool $reset = false): bool

$fd:指定关闭的 fd (文件描述符)

$reset:设置为 true 会强制关闭连接,丢弃发送队列中的数据

注意:

-Server 主动 close 连接,也一样会触发 onClose 事件
- 不要在 close 之后写清理逻辑。应当放置到 onClose 回调中处理
-HTTP\Server 的 fd 在上层回调方法的 response 中获取

 

sendfile():发送文件到 TCP 客户端连接。

Swoole\Server->sendfile(int $fd, string $filename, int $offset = 0, int $length = 0): bool

$fd:指定客户端的文件描述符

$filename:要发送的文件路径,如果文件不存在会返回 false

$offset:指定文件偏移量,可以从文件的某个位置起发送数据

$length:指定发送的长度

 

sendwait():同步地向客户端发送数据。

Swoole\Server->sendwait(int $fd, string $data): bool

$fd:指定客户端的文件描述符

$data:指定客户端的文件描述符

注意:

-有一些特殊的场景,Server 需要连续向客户端发送数据,而 Server->send 数据发送接口是纯异步的,大量数据发送会导致内存发送队列塞满。使用 Server->sendwait 就可以解决此问题,Server->sendwait 会等待连接可写。直到数据发送完毕才会返回。

-sendwait 目前仅可用于 SWOOLE_BASE 模式。该函数只用于本机或内网通信,外网连接请勿使用 sendwait,在 enable_coroutine=>true (默认开启) 的时候也不要用这个函数,会卡死其他协程,只有同步阻塞的服务器才可以用。

 

sendMessage():向任意 Worker 进程或者 Task 进程发送消息。在非主进程和管理进程中可调用。收到消息的进程会触发 onPipeMessage 事件。

Swoole\Server->sendMessage(string $message, int $workerId): bool

$message:为发送的消息数据内容,没有长度限制,但超过 8K 时会启动内存临时文件

$workerId: Worker 进程的编号

注意:

-在 Worker 进程内调用 sendMessage 是异步 IO 的,消息会先存到缓冲区,可写时向 unixSocket 发送此消息

-在 Task 进程 内调用 sendMessage 默认是同步 IO,但有些情况会自动转换成异步 IO

-在 User 进程 内调用 sendMessage 和 Task 一样,默认同步阻塞的

-如果 sendMessage() 是异步 IO 的,如果对端进程因为种种原因不接收数据,千万不要一直 sendMessage(),会导致占用大量的内存资源,可以做个应答机制,对端不回应就不要发了;

-MacOS/FreeBSD下超过 2K 就会使用临时文件存储;

-使用 sendMessage 必须注册 onPipeMessage 事件回调函数;

-设置了 task_ipc_mode = 3 将无法使用 sendMessage 向特定的 task 进程发送消息。

$server = new Swoole\Server("0.0.0.0", 9501);

$server->set(array(
    'worker_num'      => 2,
    'task_worker_num' => 2,
));
$server->on('pipeMessage', function ($server, $src_worker_id, $data) {
    echo "#{$server->worker_id} message from #$src_worker_id: $data\n";
});$server->on('receive', function (Swoole\Server $server, $fd, $reactor_id, $data) {
    if (trim($data) == 'task') {
        
    } else {
        $worker_id = 1 - $server->worker_id;
        $server->sendMessage("hello task process", $worker_id);
    }
});

$server->start();

 

exist():检测 fd 对应的连接是否存在。

Swoole\Server->exist(int $fd): bool

$fd:文件描述符

注:此接口是基于共享内存计算,没有任何 IO 操作

 

pause():停止接收数据。

Swoole\Server->pause(int $fd)

$fd:文件描述符

注意:

-调用此函数后会将连接从 EventLoop 中移除,不再接收客户端数据。
-此函数不影响发送队列的处理
-只能在 SWOOLE_PROCESS 模式下,调用 pause 后,可能有部分数据已经到达 Worker 进程,因此仍然可能会触发 onReceive 事件

 

resume():恢复数据接收。与 pause 方法成对使用。

Swoole\Server->resume(int $fd)

$fd:文件描述符

 

getClientInfo():获取连接的信息,别名是 Swoole\Server->connection_info()

Swoole\Server->getClientInfo(int $fd, int $extraData, bool $ignoreError = false): bool|array

$fd:指定文件描述符

$extraData:扩展信息,保留参数,目前无任何效果

$ignoreError: 是否忽略错误,如果设置为 true,即使连接关闭也会返回连接的信息

注意:

-仅在 onConnect 触发的进程中才能获取到证书;格式为 x509 格式,可使用 openssl_x509_parse 函数获取到证书信息;

-当使用 dispatch_mode = 1/3 配置时,考虑到这种数据包分发策略用于无状态服务,当连接断开后相关信息会直接从内存中删除,所以 Server->getClientInfo 是获取不到相关连接信息的。

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)
}

返回值释义:

 

 

 

getClientList():遍历当前 Server 所有的客户端连接,Server::getClientList 方法是基于共享内存的,不存在 IOWait,遍历的速度很快。另外 getClientList 会返回所有 TCP 连接,而不仅仅是当前 Worker 进程的 TCP 连接。别名是 Swoole\Server->connection_list()

Swoole\Server->getClientList(int $start_fd = 0, int $pageSize = 10): bool|array

$start_fd:指定起始 fd

$pageSize:每页取多少条,最大不得超过 100

返回值:调用成功将返回一个数字索引数组,元素是取到的 $fd。数组会按从小到大排序。最后一个 $fd 作为新的 start_fd 再次尝试获取;调用失败返回 false。

注意:
-推荐使用 Server::$connections 迭代器来遍历连接
-getClientList 仅可用于 TCP 客户端,UDP 服务器需要自行保存客户端信息
-SWOOLE_BASE 模式下只能获取当前进程的连接

示例:

$start_fd = 0;
while (true) {
  $conn_list = $server->getClientList($start_fd, 10);
  if ($conn_list === false or count($conn_list) === 0) {
      echo "finish\n";
      break;
  }
  $start_fd = end($conn_list);
  var_dump($conn_list);
  foreach ($conn_list as $fd) {
      $server->send($fd, "broadcast");
  }
}

 

stats():得到当前 Server 的活动 TCP 连接数,启动时间等信息,accept/close(建立连接 / 关闭连接) 的总次数等信息。

Swoole\Server->stats(): array
array(12) {
  ["start_time"]=>
  int(1580610688)
  ["connection_num"]=>
  int(1)
  ["accept_count"]=>
  int(1)
  ["close_count"]=>
  int(0)
  ["worker_num"]=>
  int(1)
  ["idle_worker_num"]=>
  int(1)
  ["tasking_num"]=>
  int(0)
  ["request_count"]=>
  int(1)
  ["worker_request_count"]=>
  int(1)
  ["worker_dispatch_count"]=>
  int(1)
  ["task_idle_worker_num"]=>
  int(1)
  ["coroutine_num"]=>
  int(1)
}

返回值释义:

 

 

 

heartbeat():与 heartbeat_check_interval 的被动检测不同,此方法主动检测服务器所有连接,并找出已经超过约定时间的连接。如果指定 if_close_connection,则自动关闭超时的连接。未指定仅返回连接的 fd 数组。

Swoole\Server->heartbeat(bool $ifCloseConnection = true): bool|array

$ifCloseConnection:是否关闭超时的连接

返回值:调用成功将返回一个连续数组,元素是已关闭的 $fd;调用失败返回 false

 

getLastError():获取最近一次操作错误的错误码。业务代码中可以根据错误码类型执行不同的逻辑。

Swoole\Server->getLastError(): int

返回值:

 

 

 

confirm():确认连接,与 enable_delay_receive 配合使用。当客户端建立连接后,并不监听可读事件,仅触发 onConnect 事件回调,在 onConnect 回调中执行 confirm 确认连接,这时服务器才会监听可读事件,接收来自客户端连接的数据。此方法一般用于保护服务器,避免收到流量过载攻击。当收到客户端连接时 onConnect 函数触发,可判断来源 IP,是否允许向服务器发送数据。

Swoole\Server->confirm(int $fd)

$fd:连接的唯一标识符

返回值:确认成功返回 true;$fd 对应的连接不存在、已关闭或已经处于监听状态时,返回 false,确认失败

示例:

//创建Server对象,监听 127.0.0.1:9501端口
$serv = new Swoole\Server("127.0.0.1", 9501); 
$serv->set([
    'enable_delay_receive' => true,
]);

//监听连接进入事件
$serv->on('Connect', function ($serv, $fd) {  
    //在这里检测这个$fd,没问题再confirm
    $serv->confirm($fd);
});

//监听数据接收事件
$serv->on('Receive', function ($serv, $fd, $from_id, $data) {
    $serv->send($fd, "Server: ".$data);
});

//监听连接关闭事件
$serv->on('Close', function ($serv, $fd) {
    echo "Client: Close.\n";
});

//启动服务器
$serv->start(); 

 

getWorkerId():获取当前 Worker 进程 id(非进程的 PID),和 onWorkerStart 时的 $workerId 一致

Swoole\Server->getWorkerId(): int|false

 

getWorkerPid():获取当前 Worker 进程 PID

Swoole\Server->getWorkerPid(): int|false

 

getWorkerStatus():获取 Worker 进程状态

Swoole\Server->getWorkerStatus(int $worker_id): int|false

$worker_id:Worker 进程 id

返回值:返回 Worker 进程状态值,不是 Worker 进程或者进程不存在返回 false

 

 

getManagerPid():获取当前服务的 Manager 进程 PID

Swoole\Server->getManagerPid(): int

 

getMasterPid():获取当前服务的 Master 进程 PID

Swoole\Server->getMasterPid(): int

 

 

4、事件

onPipeMessage:当工作进程收到由 $server->sendMessage() 发送的 unixSocket 消息时会触发 onPipeMessage 事件。worker/task 进程都可能会触发 onPipeMessage 事件

function onPipeMessage(Swoole\Server $server, int $src_worker_id, mixed $message);

$server:Swoole\Server 对象

$src_worker_id:消息来自哪个 Worker 进程

$message:消息内容,可以是任意 PHP 类型

 

onBeforeReload:Worker 进程 Reload 之前触发此事件,在 Manager 进程中回调

function onBeforeReload(Swoole\Server $server);

$server:Swoole\Server 对象

 

onAfterReload:Worker 进程 Reload 之后触发此事件,在 Manager 进程中回调

function onAfterReload(Swoole\Server $server);

$server:Swoole\Server 对象

 

事件执行顺序说明:

-所有事件回调均在 $server->start 后发生
-服务器关闭程序终止时最后一次事件是 onShutdown
-服务器启动成功后,onStart/onManagerStart/onWorkerStart 会在不同的进程内并发执行
-onReceive/onConnect/onClose 在 Worker 进程中触发
-Worker/Task 进程启动 / 结束时会分别调用一次 onWorkerStart/onWorkerStop
-onTask 事件仅在 task 进程中发生
-onFinish 事件仅在 worker 进程中发生
-onStart/onManagerStart/onWorkerStart 3 个事件的执行顺序是不确定的

 

 

终于整理完了,发现还挺多。以上就是在之前章节中没有提到的,但是使用频率比较高的函数事、事件、属性、配置。异步风络的TCP服务器到这里告一段落。请大家继续关注之后的swoole章节

 

 

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

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

posted on 2020-10-06 17:38  咚..咚  阅读(839)  评论(0编辑  收藏  举报

导航