什么是粘包拆包
TCP是一个面向字节流的通讯协议,没有消息保护边界。一方发送的多个报文可能会被合并成一个大的报文进行传输,这就是粘包;也可能发送的一个报文,可能会被拆分成多个小报文,这就是拆包。
粘包、拆包的过程,client分别发送了两个数据包D1和D2给server,server端一次读取到字节数是不确定的,因此可能可能存在以下几种情况:
关于这几种情况说明如下:
- server端分两次读取到了两个独立的数据包,分别是D1和D2,没有粘包和拆包
- server一次接受到了两个数据包,D1和D2粘合在一起,称之为TCP粘包
- server分两次读取到了数据包,第一次读取到了完整的D1包和D2包的部分内容,第二次读取到了D2包的剩余内容,这称之为TCP拆包
- server分两次读取到了数据包,第一次读取到了D1包的部分内容D1_1,第二次读取到了D1包的剩余部分内容D1_2和完整的D2包。
由于发送方发送的数据,可能会发生粘包、拆包的情况。这样,对于接收端就难于分辨出来了,因此必须提供科学的机制来解决粘包、拆包问题,这就是协议的作用。
粘包拆包的原因
粘包和拆包并不是错误,而是TCP在尽力优化传输效率和可靠性时的一种正常现象。解决粘包和拆包问题的关键在于应用层设计合适的协议来处理TCP流中的边界。
-
TCP的流式传输特性:TCP作为一种面向连接的协议,通过建立连接来进行可靠的字节流传输。但TCP并不了解应用层的数据包边界,它只是将数据作为一连串的字节序列发送出去,这可能导致多个数据包在接收时被当作一个整体处理,这就是粘包。
-
发送和接收缓冲区:TCP在传输数据时会使用发送和接收缓冲区。发送方将多个消息放入发送缓冲区,由TCP决定何时将数据发送出去,有时会将多个小的数据包装进一个TCP段中以提高效率;同样,接收方对数据的处理速度可能跟不上接收速度,导致多个TCP段中的数据在应用层看来粘在一起,形成粘包。
-
Nagle算法:Nagle算法是TCP协议用来减少网络传输小数据包的数量的一种优化机制。小的数据包将会被缓存起来,直到积累到一定数量或特定条件触发时才一起发送,这样就可以合并多个小数据包以减少头部开销,但这也是粘包的一个常见原因。
-
延迟确认:TCP协议中的接收方不会立即对收到的包做出确认,而是会延迟一段时间,这是为了确认响应的能够和接收方向发送方发送的下一个要求一起发出,以此来减少网络包的数量。但这种策略也可能导致粘包的现象。
-
网络拥堵和流控制:当网络出现拥堵或者接收端应用处理能力不足时,TCP协议会进行流量控制,包括减慢发送速度或者使用较小的窗口大小。这也可能导致发送方积累了多个数据包后一次性发送,或者接收方一次性接收了多个包,进而引发粘包。
-
应用层数据发送策略:应用层外发送数据的逻辑也能导致粘包和拆包。例如一个大文件可能被划分为多个小包进行发送,但如果在发送时没有恰当的间隔,则接收端可以在很短时间内接收到多个这样的小包,导致粘包。
粘包拆包解决方法
-
固定长度:每个报文都固定为相同的长度。如果某个报文的长度不足这个固定值,就在其后面填充空白字符或者特定的填充数据,直到达到固定长度。这种方式简单,但可能会浪费带宽,特别是当报文很短时。
-
特殊分隔符:在报文的末尾添加一个特殊的字符或字符串作为分隔符,来标识报文的边界。例如,在每个报文结束时插入一个换行符或特定的序列。这种方法需要确保数据部分不包含分隔符,否则需要进行数据转义处理。
-
长度字段:在报文的开始部分添加一个长度字段来指示报文主体部分的长度。接收方首先读取固定长度的长度字段,根据该字段的值来确定接下来需要接收的数据量。这种方式非常灵活高效,适用于报文长度变化较大的情况。
-
添加消息头:在报文前添加一个固定格式的消息头部,消息头中除了包含报文长度字段外,还可以包含其它信息,比如报文类型、发送者、接收者等。这种方法可以提供更多的控制,并支持更复杂的通讯协议设计。
-
协议栈封装:使用已有的高级协议或库来处理TCP数据流,如WebSocket、MQTT等。这些协议通常已经在内部解决了粘包和拆包的问题,同时提供了更为丰富的功能。
选择合适的方法时,需要考虑到实际应用的需求、性能要求和复杂度等因素。在实际开发中,长度字段方法是解决TCP粘包和拆包问题的最常用也是最有效的一种方法。