tcp:粘包和拆包问题、封包和解包方案

参考:

workerman (框架协议源码)

https://blog.csdn.net/msdnwolaile/article/details/50769708 (tcp粘包问题经典分析)

https://wiki.swoole.com/#/learn?id=tcp粘包问题 (swoole文档)

https://www.cnblogs.com/vipstone/p/14239160.html(粘包分包问题解决的3种思路)

粘包与拆包的概念

TCP是基于字节流的,只维护发送出去多少,确认了多少,并没有维护消息与消息之间的边界;

 

在TCP/IP协议中,由于传输层并不了解应用层数据的含义,发送端传输层可能会对应用层数据进行拆分或者合并,在接收端也同样如此。由此而产生的问题就是常常会听说的“粘包与拆包”的问题。

“粘包拆包”的问题在“短报文”和“一问一答”的场景下其实并不会出现。短报文是指报文长度远小于MSS的情况,应用层的报文在TCP报文中完全可以放下。

另一方面,“一问一答”的通信模式可以保证报文会以单一的TCP包发送出去。在这两个条件下都满足时,我们不需要考虑“粘包拆包”问题。

反之,如果这两个条件不同时满足,就很可能会出现“粘包拆包”问题。

粘包的主要原因:

  • 发送方每次写入数据 < 套接字(Socket)缓冲区大小;
  • 接收方读取套接字(Socket)缓冲区数据不够及时。

半包的主要原因:

  • 发送方每次写入数据 > 套接字(Socket)缓冲区大小;
  • 发送的数据大于协议的 MTU (Maximum Transmission Unit,最大传输单元),因此必须拆包。

问题描述,上图

解决方法有三种,具体如下:

1,发送端给每个数据包添加包首部,首部长度应该至少包含数据包的长度 ;

2,固定每次发送的报文长度,不够用0补充;

3,约定好包的边界,添加首部尾部标识,如特许字符;

关键方法

1,封包方法

2,解包方法

3,重要函数和字符

pack(format,arg):将参数打包成二进制字符串

unpack(format,arg):从二进制解析出字符串

ord() 返回字符的ASCII码值

chr() ASCII码值对应的字符,与ord互补
strlen:字符串长度 substr:从某个位置开始截取 strpos:特殊边界字符判断 trim/rtrim/ltrim:去除特殊边界字符

 

基本思想

参考:workerman,zinx,ppython(框架)

提出问题

1,tcp链接是里面的数据流,流数据获取和发送问题

2,原数据包在不封装的情况下,直接处理的一般方式

3,原数据包在封包格式化的情况下,处理包的一般方式

关键知识点

1,IP 数据报的最大长度 = 2^16-1 = 65535 字节 (64kB)

TCP 报文段的数据部分 = IP 数据报的最大长度 - IP 数据报的首部 - TCP 报文段的首部 = 65535 - 20 - 20 = 65495 字节

问题1:数据流的获取和发送的问题

1,当客户端发送一个tcp请求,数据包长度 > tcp的最大长度:将会分多次发送。此时接受方接受数据也是read多次。//每次传输和接受的长度还有很多细节,此处只作一个抽象值。

2,如果数据包长度 <= tcp最大长度,一次发送完。

3,在数据包较小的情况下,tcp可能会合并发送,一次发送多个数据包。

4,每次能接收到

问题2:数据包不封装的处理方式

读取的情况

1,可以一次读取完发送端一次发送的数据包。如果读取完就会读到空字节流,如果链接关闭则会读取到错误

2,也可以每次读取固定长度的数据包,用循环反复读取。直到读取指定长度的数据,或者读取完

发送的情况

1,如果数据包较小,可以一次发送完

2,如果数据包较大,可以先计算数据包的字节总长度。循环发送直到发送长度值之和为总长度。

3,如果一次发送的长度大于tcp的最大长度,会被分段发送

问题3:有格式化数据包的处理方式(其实就是粘包的问题的解决方法)

1,不论tcp底层对数据流做了什么处理,也不论每次读取read和发送write了多大的数据,在代码逻辑层实现数据的正确获取就行

2,怎样正确发送一个数据包:只要发送的总长度 = 1次或多次发送的长度之和,这个包就算发送完成

3,怎样争正确读取一个数据包:在没有封装的情况下,要么循环读取固定长度直到读到空为止,要么1次就读取读是多少是多少(小于等于设置的最大长度)

4,在有封装的情况下,先读取特定长度的头部,如果长度太少的情况,可以直接返回或者阻塞直到获取到特定长度的头部,从头部中解析包体的长度,直到读到包体长度的的数据为止。循环下一次消息

总结:

1,不用关心tcp链接的数据怎样传输,只需要把数据源看成一个抽象流就行了,该怎么发送就怎么发送,该怎么解析就怎么解析。

2,不要去考虑读取read的时候太短不够解析的情况,就当系统调用会阻塞,直到能读取到完整数据。那是操作系统需要关系的问题。

3,也不要考虑发送write的时候太长没写完的情况,可以循环发送直到发送的总长度之和等于数据包长度就成功了。底层自会处理长度的问题。该合并就合并,该拆封就拆分。

4,一句话:使用tcp协议必须 定义协议规则,服务端和客户端依据协议规则通信。

 

posted @ 2021-02-25 09:48  小匡程序员  阅读(935)  评论(0编辑  收藏  举报