Netty 学习(五)理解拆包/粘包

前言

本文记录有关数据包在网络通讯中拆包和粘包的知识:

  • 出现拆包 / 粘包的原因
  • 解决拆包 / 粘包的方案

一、什么是拆包/粘包

在网络通讯过程中,可能会将一个完整的报文拆分成多个小数据包进行传送,也可能将多个报文合并成一个大的数据包进行传送,这就是所谓的拆包和粘包。

二、为什么会有拆包和粘包

在网络通讯过程中,每次传送的数据包大小受到各种因素的限制:一个完整的数据报文大小超过传输单元的限制,可能会拆分成多个数据包发送出去。如果单次传输的数据报文较小,可能会采用 Nagle 算法进行优化,将多个报文通过一个数据包发送出去。

1. MTU 最大传输单元和 MSS 最大分段大小

MTU (Maximum Transmission Unit)是链路层一次最大传输数据的大小,一般为 1500 byte。
MSS (Maximum Segement Unit)是TCP 传输层单次发型的最大数据大小。
如下图所示,MTU 和 MSS 的一般计算关系为:MSS = MTU - IP Header - TCP Header。如果 MSS + IP Header + TCP Header > MTU,那么数据包会拆分为多个包发送,这就是拆包现象。
Netty 学习(五)理解拆包/粘包

2. 滑动窗口

滑动窗口是 TCP 传输层用于控制流量的一种有效措施。数据接收方设置滑动窗口大小,即允许发送方同时发生数据分组(数据包)的数量;数据发送方在可用窗口数量内,不需要每发送一个数据包就等待接收方确认,一旦窗口占满,需等待接收方处理完成后才能继续发送。

3. Nagle 算法

Negle 算法可以理解为批量发送,用于解决频繁发送小数据包带来的网络拥塞问题。如果发送的数据较小,可以先写入缓冲区,等待数据确认或累积到一定大小后一起发送。
Linux 默认是开启 Nagle 算法的,在大量小数据包的场景下可以有效降低网络开销。但 Nagle 算法会带来一定的数据延迟,影响及时响应。Linux 可以通过 TCP_NODELAY 参数来禁用 Nagle 算法。Netty 默认禁用 Nagle 算法。

三、拆包和粘包的解决方案

1. 固定长度

所有报文都是统一的长度,但消息内容不足时,发送方采用空位补齐。接收方读取到固定长度后,就认为获得了一个完整的消息。
固定长度的方法使用非常简单,但很难找到一个合适固定长度值,如果长度太大会造成字节浪费,长度太小又会增加网络开销。所以一般不采用。

2. 特定分隔符

如果消息不采用固定长度,那么接收方就需要区分消息的边界。特定分隔符时在每次发送的报文后面增加特殊的标识。接收方可以根据这个标识进行消息拆分。
选择分隔符时一定要避免和消息体中的字节相同,以免冲突。否则可能出现消息的错误拆分。特定分隔符法在消息协议足够简单的场景下比较高效,如 Redis 在通讯过程中

3. 报文长度 + 消息内容

在消息头固定位置中存放了消息的总长度,接收方在解析时,先读取消息头的长度 len,然后紧接着读取长度为 len 的字节数据,该数据即判定为一个完整的数据报文。
报文长度 + 消息内容 的使用方式非常灵活, 且不存在固定长度和特定分隔符法的明显缺陷。当然在消息头中不仅只限于存放消息长度,还可以自定义其他扩展字段,如消息版本,算法类型等。

总结

本文从网络通讯基础来理解通讯过程中的拆包和粘包问题,以及如何通过应用层的通讯协议来解决拆包和粘包问题。