Swoole 可用于php快速开发高效的tcp/udp服务,其中tcp是用的比较多的一个场景,http虽然是基于tcp协议的,但和直接开发tcp服务还是有明显的区别的。
TCP是数据流
tcp是数据流,这是一个基本的概念,这里有两个要点:
-
数据没有边界
你可以理解为水在一个水管里的流动,我们不知道哪段数据是一个我们需要的完整数据
-
收发有缓冲区
比如:当水从一端流到了另一端,我们在收数据的时候,不可能每来一滴水就处理一次,这个缓冲区就相当于有了一个水桶,再接了一定的水之后内核再给数据交到用户空间,这样可以大大提升性能。
那这里就有一个问题:由于这个特性,内核无法知道哪部分数据是你想要的,所以应用层拿到数据可能是完整数据,也可能是不完整或者一部分完整、一部分不完整(粘包),那么怎样能得到想要的数据呢?
数据协议
数据协议可以理解为一种约定,按照这个约定来处理收到的数据进而得到想要的数据,这个过程就称为拆包,那问题又来了?
Web开发为什么不用考虑粘包
因为web的http可以理解为一个公共的数据协议,在实现一个web服务器的时候,就必需按照这个协议来进行数据处理,这样保证在应用层获取到数据是完整的数据,http是典型的包头+包体的这个一个约定格式,有那现几个特点:
-
通过\r\n\r\n来区分包头和包体
-
包头通过\r\n来区分不同的数据组
看个示例:
GET / HTTP/1.1
Host: www.swoole.com
Connection: keep-alive
这是一个典型的请求头,第一行约定了请求的方法(GET/POST/PUT/DELETE等)、请求地址、http协议版本, 从第二行开始,都是健:值的形式,反应到PHP里,就是$_SERVER超全局变里里 HTTP_开头的数据,由于这是GET请求,所有没有包体,如果是POST或者response响应,我们可以看到包体,包体的长度通过包头里的Content-Length参数来控制的
Swoole怎么处理粘包
swoole考虑到在粘包是一个必需处理的过程,内置了两种方案:
-
约定结束符
这个类似于c里面的字符串数组,约定了\0表示字符串结束,在swoole里,可以的setting数组中,开启open_eof_check=true,并用package_eof来设置一个完整数据结尾字符。
举个例子:
"open_eof_check"=>true
"package_eof" = >'swooleend',
这里设置了package_eof = 'swooleend', 那client在每个完整数据之后再拼上swooleend之后,再发送到swoole server,swoole server就能自动识别并拼出一个完整的包。这里又隐含了两个小问题?
a) 如果我一次收到了多个完整包怎么办?
那可以开启 open_eof_split=>true,这样在onReceive里回调里拿到的数据就是一个完整的包了,否则需要业务层里自行explode('swooleend', $data)了,
b) 要保证业务数据里不能出现package_eof设置的字符,否则将导致数据错误了。
-
自定义包头包体
这种方式也很常见,特别是在二进制数据流中,原理是通过约定数据流的前几个字节来表示一个完整的数据有多长,从第一个数据到达之后,先通过读取固定的几个字节,解出数据包的长度,然后按这个长度继续取出后面的数据,依次循环。
相关配置:
'package_length_type' => 'N', // 数据unpack方式,这里N表示无符号32位大端长整型,更多定义可以参考:http://php.net/manual/zh/function.pack.php
'package_length_offset'=> 0, // 第N个字节是包长度的值
'package_body_offset' => 4, // 第几个字节开始计算长度
'package_max_length' => 2000000, // 协议最大长度
这个配置表示,前面4个字节是数据包长度,按照N的方式解析,最后一个配置表示数据包的最大长度,这个配置的目的是为了控制内存。
总结:
粘包处理是很多新做服务器网络编程最容易碰到的问题,特别是长期做web开发的同学,基本没有这个概念,所以通过这个思维转换,更深入的了解http协议,进而了解tcp协议,对服务器网络编程是非常有好处的。