Swoole 多协议 多端口 的应用
概述
这是关于 swoole 学习的第五篇文章:swoole 多协议 多端口 的应用。
主要参考官方的这两篇文章,进行实现的 demo。
- 网络通信协议设计:
- 多端口监听的使用:
希望通过我提供的 demo,能够对文档有更加深刻的理解。
网络通信协议设计
为什么需要通信协议?
官方:tcp协议在底层机制上解决了udp协议的顺序和丢包重传问题。但相比udp又带来了新的问题,tcp协议是流式的,数据包没有边界。应用程序使用tcp通信就会面临这些难题。因为tcp通信是流式的,在接收1个大数据包时,可能会被拆分成多个数据包发送。多次send底层也可能会合并成一次进行发送。这里就需要2个操作来解决:分包 和 合包,所以tcp网络通信时需要设定通信协议。
swoole 支持了2种类型的自定义网络通信协议 :eof结束符协议、固定包头+包体协议。
eof结束符协议
先看下,未设置协议的效果:
发送的每条数据长度都是 23,但在 onreceive 接收数据的时候每次接收的长度不一样,并没有按照想象的方式进行分包。
再看下,设置了eof结束符协议的效果:
发送的每条数据长度都是 23,在 onreceive 接收数据的时候每次接收的也是 23 ,完美。
主要设置项如下:
'package_max_length' => '8192', 'open_eof_split' => true, 'package_eof' => "\r\n"
不做解释,官方文档已经写的很清楚。
示例代码如下:
server.php
<?php class server { private $serv; public function __construct() { $this->serv = new swoole_server('0.0.0.0', 9501); $this->serv->set([ 'worker_num' => 2, //开启2个worker进程 'max_request' => 4, //每个worker进程 max_request设置为4次 'dispatch_mode' => 2, //数据包分发策略 - 固定模式 //eof结束符协议 'package_max_length' => '8192', 'open_eof_split' => true, 'package_eof' => "\r\n" ]); $this->serv->on('start', [$this, 'onstart']); $this->serv->on('connect', [$this, 'onconnect']); $this->serv->on("receive", [$this, 'onreceive']); $this->serv->on("close", [$this, 'onclose']); $this->serv->start(); } public function onstart($serv) { echo "#### onstart ####".php_eol; echo "swoole ".swoole_version . " 服务已启动".php_eol; echo "swoole_cpu_num:".swoole_cpu_num().php_eol; echo "master_pid: {$serv->master_pid}".php_eol; echo "manager_pid: {$serv->manager_pid}".php_eol; echo "########".php_eol.php_eol; } public function onconnect($serv, $fd) { echo "#### onconnect ####".php_eol; echo "客户端:".$fd." 已连接".php_eol; echo "########".php_eol.php_eol; } public function onreceive($serv, $fd, $from_id, $data) { echo "#### onreceive ####".php_eol; var_dump($data); } public function onclose($serv, $fd) { echo "client close.".php_eol; } } $server = new server();
client.php
<?php class client { private $client; public function __construct() { $this->client = new swoole_client(swoole_sock_tcp, swoole_sock_async); $this->client->on('connect', [$this, 'onconnect']); $this->client->on('close', [$this, 'onclose']); $this->client->on('error', [$this, 'onerror']); } public function connect() { if(!$fp = $this->client->connect("127.0.0.1", 9501)) { echo "error: {$fp->errmsg}[{$fp->errcode}]".php_eol; return; } } public function onconnect() { fwrite(stdout, "发送测试数据(y or n):"); swoole_event_add(stdin, function() { $msg = trim(fgets(stdin)); if ($msg == 'y') { $this->send(); } fwrite(stdout, "发送测试数据(y or n):"); }); } public function send() { $msg_info = "客户端发信息...\r\n"; $i = 0; while ($i < 50) { var_dump($msg_info); $this->client->send($msg_info); $i++; } } public function onclose() { echo "client close connection".php_eol; } public function onerror() { } } $client = new client(); $client->connect();
固定包头+包体协议
先看下,未设置协议的效果:
很明显,在 onreceive 接收到的数据,是少的。
再看下,设置协议的效果:
主要设置项如下:
'open_length_check' => true, 'package_max_length' => '8192', 'package_length_type' => 'n', 'package_length_offset' => '0', 'package_body_offset' => '4',
不做解释,官方文档已经写的很清楚。
示例代码如下:
server.php
<?php class server { private $serv; public function __construct() { $this->serv = new swoole_server('0.0.0.0', 9501); $this->serv->set([ 'worker_num' => 2, //开启2个worker进程 'max_request' => 4, //每个worker进程 max_request设置为4次 'dispatch_mode' => 2, //数据包分发策略 - 固定模式 //固定包头+包体协议 'open_length_check' => true, 'package_max_length' => '8192', 'package_length_type' => 'n', 'package_length_offset' => '0', 'package_body_offset' => '4', ]); $this->serv->on('start', [$this, 'onstart']); $this->serv->on('connect', [$this, 'onconnect']); $this->serv->on("receive", [$this, 'onreceive']); $this->serv->on("close", [$this, 'onclose']); $this->serv->start(); } public function onstart($serv) { echo "#### onstart ####".php_eol; echo "swoole_cpu_num:".swoole_cpu_num().php_eol; echo "swoole ".swoole_version . " 服务已启动".php_eol; echo "master_pid: {$serv->master_pid}".php_eol; echo "manager_pid: {$serv->manager_pid}".php_eol; echo "########".php_eol.php_eol; } public function onconnect($serv, $fd) { echo "#### onconnect ####".php_eol; echo "客户端:".$fd." 已连接".php_eol; echo "########".php_eol.php_eol; } public function onreceive($serv, $fd, $from_id, $data) { echo "#### onreceive ####".php_eol; $length = unpack('n', $data)[1]; echo "length:".$length.php_eol; $msg = substr($data, -$length); echo "msg:".$msg.php_eol; } public function onclose($serv, $fd) { echo "client close.".php_eol; } } $server = new server();
client.php
<?php class client { private $client; public function __construct() { $this->client = new swoole_client(swoole_sock_tcp, swoole_sock_async); $this->client->on('connect', [$this, 'onconnect']); $this->client->on('close', [$this, 'onclose']); $this->client->on('error', [$this, 'onerror']); } public function connect() { if(!$fp = $this->client->connect("127.0.0.1", 9501, 1)) { echo "error: {$fp->errmsg}[{$fp->errcode}]".php_eol; return; } } public function onconnect() { fwrite(stdout, "发送测试数据(y or n):"); swoole_event_add(stdin, function() { $msg = trim(fgets(stdin)); if ($msg == 'y') { $this->send(); } fwrite(stdout, "发送测试数据(y or n):"); }); } public function send() { $msg = '客户端发的信息...'; $msg_info = pack('n', strlen($msg)).$msg; $i = 0; while ($i < 50) { var_dump($msg_info); $this->client->send($msg_info); $i++; } } public function onclose() { echo "client close connection".php_eol; } public function onerror() { } } $client = new client(); $client->connect();
多端口监听的使用
上图,是示例代码中的端口监听:
- 9501 onmessage 处理 websocket。
- 9501 onrequest 处理 http。
- 9502 onreceive 处理 tcp。
- 9503 onpacket 处理 udp。
不多说,看下效果图:
示例代码如下:
server.php
<?php class server { private $serv; public function __construct() { $this->serv = new swoole_websocket_server("0.0.0.0", 9501); $this->serv->set([ 'worker_num' => 2, //开启2个worker进程 'max_request' => 4, //每个worker进程 max_request设置为4次 'task_worker_num' => 4, //开启4个task进程 'dispatch_mode' => 4, //数据包分发策略 - ip分配 'daemonize' => false, //守护进程(true/false) ]); $this->serv->on('start', [$this, 'onstart']); $this->serv->on('open', [$this, 'onopen']); $this->serv->on("message", [$this, 'onmessage']); $this->serv->on("request", [$this, 'onrequest']); $this->serv->on("close", [$this, 'onclose']); $this->serv->on("task", [$this, 'ontask']); $this->serv->on("finish", [$this, 'onfinish']); //监听 9502 端口 $tcp = $this->serv->listen("0.0.0.0", 9502, swoole_sock_tcp); $tcp->set([ 'worker_num' => 2, //开启2个worker进程 'max_request' => 4, //每个worker进程 max_request设置为4次 'dispatch_mode' => 2, //数据包分发策略 - 固定模式 //固定包头+包体协议 'open_length_check' => true, 'package_max_length' => '8192', 'package_length_type' => 'n', 'package_length_offset' => '0', 'package_body_offset' => '4', ]); $tcp->on("receive", [$this, 'onreceive']); //监听 9503 端口 $udp = $this->serv->listen("0.0.0.0", 9503, swoole_sock_udp); $udp->set([ 'worker_num' => 2, //开启2个worker进程 'max_request' => 4, //每个worker进程 max_request设置为4次 'dispatch_mode' => 2, //数据包分发策略 - 固定模式 ]); $udp->on("packet", [$this, 'onpacket']); $this->serv->start(); } public function onstart($serv) { echo "#### onstart ####".php_eol; echo "swoole ".swoole_version . " 服务已启动".php_eol; echo "master_pid: {$serv->master_pid}".php_eol; echo "manager_pid: {$serv->manager_pid}".php_eol; echo "########".php_eol.php_eol; } public function onopen($serv, $request) { echo "#### onopen ####".php_eol; echo "server: handshake success with fd{$request->fd}".php_eol; $serv->task([ 'type' => 'login' ]); echo "########".php_eol.php_eol; } public function ontask($serv, $task_id, $from_id, $data) { echo "#### ontask ####".php_eol; echo "#{$serv->worker_id} ontask: [pid={$serv->worker_pid}]: task_id={$task_id}".php_eol; $msg = ''; switch ($data['type']) { case 'login': $msg = '我来了...'; break; case 'speak': $msg = $data['msg']; break; } foreach ($serv->connections as $fd) { $connectioninfo = $serv->connection_info($fd); if (isset($connectioninfo['websocket_status']) && $connectioninfo['websocket_status'] == 3) { $serv->push($fd, $msg); //长度最大不得超过2m } } $serv->finish($data); echo "########".php_eol.php_eol; } public function onfinish($serv,$task_id, $data) { echo "#### onfinish ####".php_eol; echo "task {$task_id} 已完成".php_eol; echo "########".php_eol.php_eol; } public function onclose($serv, $fd) { echo "#### onclose ####".php_eol; echo "client {$fd} closed".php_eol; echo "########".php_eol.php_eol; } public function onmessage($serv, $frame) { echo "#### onmessage ####".php_eol; echo "receive from fd{$frame->fd}:{$frame->data},opcode:{$frame->opcode},fin:{$frame->finish}".php_eol; $serv->task(['type' => 'speak', 'msg' => $frame->data]); echo "########".php_eol.php_eol; } public function onrequest($request, $response) { echo "#### onrequest ####".php_eol; $response->header("content-type", "text/html; charset=utf-8"); $server = $request->server; $path_info = $server['path_info']; $request_uri = $server['request_uri']; echo "path_info:".$path_info.php_eol; if ($path_info == '/favicon.ico' || $request_uri == '/favicon.ico') { return $response->end(); } $html = "<h1>你好 swoole.</h1>"; $response->end($html); } public function onreceive($serv, $fd, $from_id, $data) { echo "#### onreceive ####".php_eol; $length = unpack('n', $data)[1]; echo "length:".$length.php_eol; $msg = substr($data, -$length); echo "msg:".$msg.php_eol; } public function onpacket($serv, $data, $clientinfo) { echo "#### onpacket ####".php_eol; $serv->sendto($clientinfo['address'], $clientinfo['port'], "server ".$data); var_dump($clientinfo); } } $server = new server();
4 个客户端连接的代码分别是:
1、9501 onmessage 处理 websocket。可以参考原来文章 swoole websocket 的应用 中的代码即可。
2、9501 onrequest 处理 http。可以参考原来文章 swoole http 的应用 中的代码即可。
3、9502 onreceive 处理 tcp。可以参考原来文章 swoole task 的应用 中的代码即可。
4、9503 onpacket 处理 udp。
示例代码:
netcat -u 10.211.55.4 9503
小结
一、多端口的应用场景是什么?
比如,开发一个直播网站,直播用一个端口,im聊天用一个端口。
比如,开发一个rpc服务,数据通讯用一个端口,统计界面用一个端口。
本文欢迎转发,转发请注明作者和出处,谢谢!
上一篇: Python安装与基本数据类型
下一篇: go语言环境变量设置(mac)