概述

这是关于 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服务,数据通讯用一个端口,统计界面用一个端口。

本文欢迎转发,转发请注明作者和出处,谢谢!

Swoole 多协议 多端口 的应用的更多相关文章

  1. Swoole 绑定域名 80 端口 (Nginx 反向代理)

    启动 Swoole 的 http server,可以使用 IP + 端口 进行访问 创建 Nginx 虚拟域名 vim swotp.liuguofeng.com.conf server { liste ...

  2. Swoole 整合成一个小框架

    目录 概述 效果 代码 小结 概述 这是关于 Swoole 学习的第六篇文章:Swoole 整合成一个小框架. 第五篇:Swoole 多协议 多端口 的应用 第四篇:Swoole HTTP 的应用 第 ...

  3. Swoole MySQL 连接池的实现

    目录 概述 代码 扩展 小结 概述 这是关于 Swoole 入门学习的第八篇文章:Swoole MySQL 连接池的实现. 第七篇:Swoole RPC 的实现 第六篇:Swoole 整合成一个小框架 ...

  4. Swoole RPC 的实现

    目录 概述 实现效果 代码 小结 概述 这是关于 Swoole 学习的第七篇文章:Swoole RPC 的实现. 第六篇:Swoole 整合成一个小框架 第五篇:Swoole 多协议 多端口 的应用 ...

  5. Swoole Redis 连接池的实现

    概述 这是关于 Swoole 入门学习的第九篇文章:Swoole Redis 连接池的实现. 第八篇:Swoole MySQL 连接池的实现 第七篇:Swoole RPC 的实现 第六篇:Swoole ...

  6. 压测 swoole_websocket_server 性能

    概述 这是关于 Swoole 入门学习的第十篇文章:压测 swoole_websocket_server 性能. 第九篇:Swoole Redis 连接池的实现 第八篇:Swoole MySQL 连接 ...

  7. virtualbox虚拟机ubuntu操作系统,设置网络互通、访问,能访问虚拟机swoole的http服务

    网络互通 1.设置virtualbox网络连接模式为桥接网卡模式 2.重启虚拟机查看虚拟机IP ifconfig 3.开启window的telnet程序 控制面板->程序->启用或关闭wi ...

  8. LaravelS - 基于Swoole加速Laravel/Lumen

    LaravelS LaravelS是一个胶水项目,用于快速集成Swoole到Laravel或Lumen,然后赋予它们更好的性能.更多可能性.Github 特性 内置Http/WebSocket服务器 ...

  9. 使用Gateway-Worker实现多人分组实时聊天 结合第三方tp

    一.基础知识1.Workerman是一款纯PHP开发的开源高性能的PHP socket 服务器框架.被广泛的用于手机app.移动通讯等领域的开发. 支持TCP长连接,支持Websocket.HTTP等 ...

随机推荐

  1. install_driver(mysql) failed

        安装好了mysql监控神器innotop,正得意,innotoop不可用,其错误提示为install_driver(mysql) failed: Can't load '/usr/lib64/ ...

  2. 【shell】shuf命令,随机排序

    shuf命令主要用来对输入的每一行进行随机排序输出,我们可以利用这个属性,实现在几个文件中随机读取一个的功能 如下,zls.txt文件有三行,我们想要随机从中读取一行. 可以看到,每次读取顺序都不一样 ...

  3. keras: 在构建LSTM模型时,使用变长序列的方法

    众所周知,LSTM的一大优势就是其能够处理变长序列.而在使用keras搭建模型时,如果直接使用LSTM层作为网络输入的第一层,需要指定输入的大小.如果需要使用变长序列,那么,只需要在LSTM层前加一个 ...

  4. 【转载】基于注解的SpringMVC简单介绍

    SpringMVC是一个基于DispatcherServlet的MVC框架,每一个请求最先访问的都是DispatcherServlet,DispatcherServlet负责转发每一个Request请 ...

  5. video codec 学习笔记

    一. H.264 (http://www.baike.com/wiki/H264)  三大标准: AVC(Advanced Video Coding,AVC) H.264,同时也是MPEG-4第十部分 ...

  6. BA模型 第10章

    1.BA模型BA模型就是世界坐标到像素坐标的转换过程.这里多了一个去畸变.因为归一化平面坐标在转成像素坐标的过程中会出现畸变.这里只处理了径向畸变,径向畸变包括桶形失真和枕形失真,都是由于图像放大率随 ...

  7. <关于JSP技术>运行机制及语法概述(附对本次同济校内ACM选拔赛决赛的吐槽)

    (一)JSP运行的机制 JSP是一种建立在Servlet规范功能之上的动态网页技术,它们都是在通常的网页文件中嵌入脚本代码,用于产生动态内容,不过和ASP不同的是JSP文件中嵌入的是Java代码和JS ...

  8. React之jsx语法特性

    jsx 语法,直接可以在js中使用html标签. 还可以通过花括号的形式,在html标签中,写js表达式. <div> { 1 + 2 } hello,world! </div> ...

  9. WebStorm中SVN配置

    近期在使用WebStorm进行网页开发,值得一提的是WebStorm的确是一个不错的IDE,尽管可能内存开销较大,但是在编写JS的时候提供了很多包括自动完成等强大的功能. 好了,步入正题:在实际项目开 ...

  10. Vagrant + Vbox实战 【转】

    原文地址:http://www.cnblogs.com/suihui/p/4362233.html 一.软件下载 1.下载Oracle VM VirtualBox https://www.virtua ...