UDP(user datagram protocol,用户数据报协议)是无连接的,面向消息的,提供高效率服务。不会使用块的合并优化算,, 由于UDP支持的是一对多的模式,所以接收端的skbuff(套接字缓冲区)采用了链式结构来记录每一个到达的UDP包,在每个UDP包中就有了消息头(消息来源地址,端口等信息),这样,对于接收端来说,就容易进行区分处理了。 即面向消息的通信是有消息保护边界的。
TCP(transport control protocol,传输控制协议)是面向连接的,面向流的,提供高可靠性服务。收发两端(客户端和服务器端)都要有一一成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。这样,接收端,就难于分辨出来了,必须提供科学的拆包机制。即面向流的通信是无消息保护边界的。
1 TCP粘包、拆包图解
5F0454F5-BAE3-42A7-8C43-DB1BA71A532B.png
假设客户端分别发送了两个数据包D1和D2给服务端,由于服务端一次读取到字节数是不确定的,故可能存在以下四种情况:
- 服务端分两次读取到了两个独立的数据包,分别是D1和D2,没有粘包和拆包
- 服务端一次接受到了两个数据包,D1和D2粘合在一起,称之为TCP粘包
- 服务端分两次读取到了数据包,第一次读取到了完整的D1包和D2包的部分内容,第二次读取到了D2包的剩余内容,这称之为TCP拆包
- 服务端分两次读取到了数据包,第一次读取到了D1包的部分内容D1_1,第二次读取到了D1包的剩余部分内容D1_2和完整的D2包。
特别要注意的是,如果TCP的接受滑窗非常小,而数据包D1和D2比较大,很有可能会发生第五种情况,即服务端分多次才能将D1和D2包完全接受,期间发生多次拆包。
2 发生原因
- socket缓冲区与滑动窗口
- MSS/MTU限制
- Nagle算法
具体请看粘包、拆包产生原因:
https://zhuanlan.zhihu.com/p/103616849
3 解决方案:定义通信协议
通过定义应用的协议(protocol)来解决。协议的作用就定义传输数据的格式。这样在接受到的数据的时候,如果粘包了,就可以根据这个格式来区分不同的包,如果拆包了,就等待数据可以构成一个完整的消息来处理。目前业界主流的协议(protocol)方案可以归纳如下:
1 定长协议:假设我们规定每3个字节,表示一个有效报文,如果我们分4次总共发送以下9个字节:
+---+----+------+----+
| A | BC | DEFG | HI |
+---+----+------+----+
那么根据协议,我们可以判断出来,这里包含了3个有效的请求报文
+-----+-----+-----+
| ABC | DEF | GHI |
+-----+-----+-----+
2 特殊字符分隔符协议:在包尾部增加回车或者空格符等特殊字符进行分割 。
例如,按行解析,遇到字符\n、\r\n的时候,就认为是一个完整的数据包。对于以下二进制字节流:
+--------------+
| ABC\nDEF\r\n |
+--------------+
那么根据协议,我们可以判断出来,这里包含了2个有效的请求报文
+-----+-----+
| ABC | DEF |
+-----+-----+
3 长度编码:将消息分为消息头和消息体,消息头中用一个int型数据(4字节),表示消息体长度的字段。在解析时,先读取内容长度Length,其值为实际消息体内容(Content)占用的字节数,之后必须读取到这么多字节的内容,才认为是一个完整的数据报文。
header body
+--------+----------+
| Length | Content |
+--------+----------+
总的来说,通信协议就是通信双方约定好的数据格式,发送方按照这个数据格式来发送,接受方按照这个格式来解析。因此发送方和接收方要完成的工作不同,发送方要将发送的数据转换成协议规定的格式,称之为编码(encode);接收方需要根据协议的格式,对二进制数据进行解析,称之为解码(decode)。
网友评论