加入收藏 | 设为首页 | 会员中心 | 我要投稿 梧州站长网 (https://www.0774zz.cn/)- 云原生、分布式云、媒体处理、业务安全、数据可视化!
当前位置: 首页 > 站长学院 > PHP教程 > 正文

Swoole TCP 流数据边界问题解决方案

发布时间:2022-08-02 13:45:42 所属栏目:PHP教程 来源:互联网
导读:1. 数据发送过程 我的官方群点击此处。 首先由客户端将数据发往缓冲区 (服务端并不是直接收到的), 对于客户端来说,这次的数据即是发送成功了, 对于服务端是否真正的收到他是不知道的, 然后再由服务端从缓冲区中读取数据。图解: 2. 什么是数据边界 因为
  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 请求头的分隔符,在每次发送的数据包结尾处使用 rn (可以配置) 来结尾, 当服务端从缓冲区中读取数据, 根据指定字符来分割数据包,EOF 有两种配置方案:
 
  3.1 open_eof_check
  首先放出配置方式:
 
  $server->set([
      'open_eof_check' => true,
      'package_eof' => "rn"
  ]);
  这种配置方式会对客户端发来的数据包进行检测, 当发现结尾是 rn 时,才会投递给 worker 进程, 也就是我们的 onReceive 回调,否则会一直拼接数据包,直到超出缓冲区或者超时才终止。 但此方法有一个问题是可能会一次性收到多个数据包,因为他是从数据包的结尾处来进行检查的,在数据内容中存在 rn 时程序并不会发现,需要我们自己在应用代码中再次使用 rn 来拆分数据包。
 
  客户端运行代码
  $client = new SwooleClient(SWOOLE_SOCK_TCP);
 
  if ($client->connect('127.0.0.1', 9501, -1)) {
 
      while(true) {
          $send2 = "Hello World rn";
          $client->send($send2);        
      }
  }
 
  $client->close();
 
  服务端代码
  $server = new SwooleServer('127.0.0.1', 9501);
  $server->set([
      'open_eof_check' => true,
      'package_eof' => "rn"
  ]);
 
  $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' => "rn"
  ]);
  这种配置方式,服务端会对客户端发来的数据逐个字符进行检查,遇到 rn 就发送给 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
  ]);
 
  下面是一个数据包结构例子,可以很好的体现了字段含义。

(编辑:梧州站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    热点阅读