热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

详解SwooleTCP流数据边界问题解决方案

本文主要介绍了SwooleTCP流数据边界问题解决方案,对Swoole感兴趣

1. 数据发送过程

首先由客户端将数据发往缓冲区 (服务端并不是直接收到的), 对于客户端来说,这次的数据即是发送成功了, 对于服务端是否真正的收到他是不知道的, 然后再由服务端从缓冲区中读取数据。图解:

2. 什么是数据边界

因为 TCP 是流式传输,对于服务端来说并不知道此时在缓冲区内的数据是一次请求还是两次请求的,所以在服务端接收数据时需要根据指定字符或约定长度来对数据进行分包,这个分包的标志即是数据边界。否则可能会出现一次读取两条或多条数据,造成读取、解析数据出错。

2.1 代码演示

可以用代码实现一下,假设客户端死循环往缓冲区不停输入 “1”,即相当于每次的报文内容都是 1, 那么在服务端读取时收到的数据就是随机长度的。

客户端代码:

$client = new SwooleClient(SWOOLE_SOCK_TCP);
if ($client->connect("127.0.0.1", 9501, -1)) {
    while(true) {
        $client->send(1);        
    }
}
$client->close();

服务端代码:

$server = new SwooleServer("127.0.0.1", 9501);
$server->on("connect", function($server, $fd){
    echo "client : ".$fd." connect";
});

$server->on("receive", function($server, $fd, $from_id, $data){
    echo "receive:". $data.PHP_EOL;
});

$server->on("close", function($server){

});

运行结果

可以看到运行结果,服务端获取到的数据完全是随机的,有长有短,那么接下来我们说下如何解决这个问题。

3.EOF 解决方案

第一种解决方案类似于我们 http 请求头的分隔符,在每次发送的数据包结尾处使用 (可以配置) 来结尾, 当服务端从缓冲区中读取数据, 根据指定字符来分割数据包,EOF 有两种配置方案:

3.1 open_eof_check

首先放出配置方式:

$server->set([
    "open_eof_check" => true,
    "package_eof" => "
"
]);

这种配置方式会对客户端发来的数据包进行检测, 当发现结尾是 时,才会投递给 worker 进程, 也就是我们的 onReceive 回调,否则会一直拼接数据包,直到超出缓冲区或者超时才终止。 但此方法有一个问题是可能会一次性收到多个数据包,因为他是从数据包的结尾处来进行检查的,在数据内容中存在 时程序并不会发现,需要我们自己在应用代码中再次使用 来拆分数据包。

客户端运行代码

$client = new SwooleClient(SWOOLE_SOCK_TCP);

if ($client->connect("127.0.0.1", 9501, -1)) {

    while(true) {
        $send2 = "Hello World 
";
        $client->send($send2);        
    }
}

$client->close();

服务端代码

$server = new SwooleServer("127.0.0.1", 9501);
$server->set([
    "open_eof_check" => true,
    "package_eof" => "
"
]);

$server->on("connect", function($server, $fd){
    echo "client : ".$fd." connect";
});

$server->on("receive", function($server, $fd, $from_id, $data){
    echo "receive:". $data;
});

$server->on("close", function($server){

});

$server->start();

运行结果

3.2 open_eof_split

配置方式:

$server->set([
    "open_eof_split" => true,
    "package_eof" => "
"
]);

这种配置方式,服务端会对客户端发来的数据逐个字符进行检查,遇到 就发送给 worker 进程,可以有效实现分包,但缺点是性能比较差。

运行结果:可以看到每次接收到一个 Hello World(代码我就不贴了, 只把服务端 set 配置改一下, 其他都一样)

3.3 open_eof_check 和 open_eof_split 差异

open_eof_check 只检查接收数据的末尾是否为 EOF,因此它的性能最好,几乎没有消耗

open_eof_check 无法解决多个数据包合并的问题,比如同时发送两条带有 EOF 的数据,底层可能会一次全部返回

open_eof_split 会从左到右对数据进行逐字节对比,查找数据中的 EOF 进行分包,性能较差。但是每次只会返回一个数据包

4. 固定包头 + 包体解决方案

引用一段官方文档的描述:

包长检测提供了固定包头 + 包体这种格式协议的解析。启用后,可以保证 Worker 进程 onReceive 每次都会收到一个完整的数据包。

长度检测协议,只需要计算一次长度,数据处理仅进行指针偏移,性能非常高,推荐使用。

可见官方是推荐使用这种方式的,就是配置比其他方案要复杂一些, 首先贴一下配置:

$server->set([
// 打开包长检测特性
"package_length_check" => true,
// 包头中某个字段作为包长度的值,底层支持了 10 种长度类型。可参考 pack() 方法
"package_length_type" => "N",
// length 长度值在包头的第几个字节。
"package_length_offset" => 8,
// 从第几个字节开始计算长度,一般有 2 种情况:
//length 的值包含了整个包(包头 + 包体),package_body_offset 为 0
//包头长度为 N 字节,length 的值不包含包头,仅包含包体,package_body_offset 设置为 N
"package_body_offset" => 16,
// 设置最大数据包尺寸,单位为字节
"package_max_length" => 81920
]);

下面是一个数据包结构例子,可以很好的体现了字段含义。

以上通信协议的设计中,包头长度为 4 个整型,16 字节,length 长度值在第 3 个整型处。因此 package_length_offset 设置为 8,0-3 字节为 type,4-7 字节为 uid,8-11 字节为 length,12-15 字节为 serid。

下面来说一下代码实现:

客户端代码:

$client = new SwooleClient(SWOOLE_SOCK_TCP);

$data = "123456789012345678901234567890";
$type = 0x30;
$uid = 0x123;
$length = strlen($data);
$serid = 0x15;
$head = pack("N4", $type, $uid, $length, $serid);
$body = pack("a{$length}", $data);
$message = $head.$body;


if ($client->connect("127.0.0.1", 9502, -1)) {
    $client->send($message);
    echo $client->recv();
}

$client->close();

服务端代码:

$serv = new SwooleServer("127.0.0.1", 9502);
$serv->set([
    "open_length_check"     => true,
      "package_max_length"    => 81920,
      "package_length_type"   => "N",
      "package_length_offset" => 8,
      "package_body_offset"   => 16,    
]);

$serv->on("connect", function($server, $fd){
    echo $fd. " Connect !".PHP_EOL;
});

$serv->on("receive", function($server, $fd, $from_id, $data){
    var_dump($data);            // 源数据
    $tmp = unpack("Ntype/Nuid/Nlength", $data);
    $unpacking = unpack("Ntype/Nuid/Nlength/Nserid/a{$tmp["length"]}body", $data);
    var_dump($unpacking);        // 解包后数据
    $server->send($fd, " Server Receive Data: ". $unpacking["body"]);
});


$serv->on("close", function($server){

});

$serv->start();

客户端运行结果

服务端运行结果

可以看到 客户端成功的把发送的数据回显, 服务端也打印出了接收到的所有数据, 其中有些字段在发送时是 16 进制的, 所以服务端在接收到之后需要进行进制转换, 我这里没有进行转换, 所以显示的数据是 10 进制的。

5. 总结

通过对比可以看出使用固定包头 + 包体的方式是效率最高的一种, 因为他是按照固定长度去读取的。期间专门去了解了 pack 函数的使用方法,但也不确定这么写到底对不对,如果有其他了解的仁兄可以慷慨解答一下,网上相关资料有点少,官方文档上也只给出了几个字段的释义。

6. 扩展知识

6.1 字节序

计算机硬件有两种储存数据的方式:大端字节序(big endian)和小端字节序(little endian)。

举例来说,数值 0x2211 使用两个字节储存:高位字节是 0x22,低位字节是 0x11。

  • 大端字节序:高位字节在前,低位字节在后,这是人类读写数值的方法。
  • 小端字节序:低位字节在前,高位字节在后,即以 0x1122 形式储存。

这个前和后指的是内存地址,计算机处理字节时是不知道高低字节之分的,它只知道按顺序读取字节,先读第一个字节,再读第二个字节。

例如: 0x1234567 的读取顺序:

以上就是详解Swoole TCP流数据边界问题解决方案的详细内容,更多关于Swoole TCP流数据边界问题解决方案的资料请关注编程笔记其它相关文章!


推荐阅读
  • 本文介绍了在Windows环境下如何配置php+apache环境,包括下载php7和apache2.4、安装vc2015运行时环境、启动php7和apache2.4等步骤。希望对需要搭建php7环境的读者有一定的参考价值。摘要长度为169字。 ... [详细]
  • Voicewo在线语音识别转换jQuery插件的特点和示例
    本文介绍了一款名为Voicewo的在线语音识别转换jQuery插件,该插件具有快速、架构、风格、扩展和兼容等特点,适合在互联网应用中使用。同时还提供了一个快速示例供开发人员参考。 ... [详细]
  • 本文介绍了通过ABAP开发往外网发邮件的需求,并提供了配置和代码整理的资料。其中包括了配置SAP邮件服务器的步骤和ABAP写发送邮件代码的过程。通过RZ10配置参数和icm/server_port_1的设定,可以实现向Sap User和外部邮件发送邮件的功能。希望对需要的开发人员有帮助。摘要长度:184字。 ... [详细]
  • 本文介绍了在mac环境下使用nginx配置nodejs代理服务器的步骤,包括安装nginx、创建目录和文件、配置代理的域名和日志记录等。 ... [详细]
  • 本文介绍了Python爬虫技术基础篇面向对象高级编程(中)中的多重继承概念。通过继承,子类可以扩展父类的功能。文章以动物类层次的设计为例,讨论了按照不同分类方式设计类层次的复杂性和多重继承的优势。最后给出了哺乳动物和鸟类的设计示例,以及能跑、能飞、宠物类和非宠物类的增加对类数量的影响。 ... [详细]
  • CEPH LIO iSCSI Gateway及其使用参考文档
    本文介绍了CEPH LIO iSCSI Gateway以及使用该网关的参考文档,包括Ceph Block Device、CEPH ISCSI GATEWAY、USING AN ISCSI GATEWAY等。同时提供了多个参考链接,详细介绍了CEPH LIO iSCSI Gateway的配置和使用方法。 ... [详细]
  • 上一章讲了如何制作数据集,接下来我们使用mmcls来实现多标签分类。 ... [详细]
  • PHP socket服务端与客户端的简易通信
    今天学习socket通信的同时,顺便整理了下以前初识socket的知识。现在关于php的socket通信,有些框架已经十分成熟了,比如swoole和workerman,这两个大家可以学习学 ... [详细]
  • Swoole在PHP-fpmapache中如何使用task功能?很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人 ... [详细]
  • php在哪里好找工作(php学到什么程度可以找到工作)
    导读:本篇文章编程笔记来给大家介绍有关php在哪里好找工作的相关内容,希望对大家有所帮助,一起来看看吧。本文目录一览:1、php去哪个城 ... [详细]
  • 本文介绍了在Linux下安装和配置Kafka的方法,包括安装JDK、下载和解压Kafka、配置Kafka的参数,以及配置Kafka的日志目录、服务器IP和日志存放路径等。同时还提供了单机配置部署的方法和zookeeper地址和端口的配置。通过实操成功的案例,帮助读者快速完成Kafka的安装和配置。 ... [详细]
  • 解决nginx启动报错epoll_wait() reported that client prematurely closed connection的方法
    本文介绍了解决nginx启动报错epoll_wait() reported that client prematurely closed connection的方法,包括检查location配置是否正确、pass_proxy是否需要加“/”等。同时,还介绍了修改nginx的error.log日志级别为debug,以便查看详细日志信息。 ... [详细]
  • 本文介绍了一个适用于PHP应用快速接入TRX和TRC20数字资产的开发包,该开发包支持使用自有Tron区块链节点的应用场景,也支持基于Tron官方公共API服务的轻量级部署场景。提供的功能包括生成地址、验证地址、查询余额、交易转账、查询最新区块和查询交易信息等。详细信息可参考tron-php的Github地址:https://github.com/Fenguoz/tron-php。 ... [详细]
  • 本文探讨了在设置了HTTP客户端超时时间后,向HTTP服务器发送请求时出现两个请求的情况。其中一个请求正常,另一个请求无法获取请求参数。文章分析了可能导致此问题的原因,并提供了解决方案。 ... [详细]
  • centos安装Mysql的方法及步骤详解
    本文介绍了centos安装Mysql的两种方式:rpm方式和绿色方式安装,详细介绍了安装所需的软件包以及安装过程中的注意事项,包括检查是否安装成功的方法。通过本文,读者可以了解到在centos系统上如何正确安装Mysql。 ... [详细]
author-avatar
潘怡安淑华寧生
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有