TCP 粘包/拆包
TCP 是一个“流”协议,所谓流,就是没有界限的一长串二进制数据。TCP 作为传输层协议并不不了解上层业务数据的具 体含义,它会根据 TCP 缓冲区的实际情况进行数据包的划分,所以在业务上认为是一个完整的包,可能会被 TCP 拆分成多个包进行发送,也有可能把多个小的包封装成一个大的数据包发送,这就是所谓的 TCP 粘包和拆包问题。 同样, 在 Netty 的编码器中, 也会对半包和粘包问题做相应的处理。什么是半包, 顾名思义, 就是不完整的数据包, 因为 Betty 在轮询读事件的时候, 每次将 channel 中读取的数据, 不一定是一个完整的数据包, 这种情况, 就叫半包。粘包同 样也不难理解, 如果Client往 Server发送数据包, 如果发送频繁很有可能会将多个数据包的数据都发送到通道中, 如果在 server 在读取的时候可能会读取到超过一个完整数据包的长度, 这种情况叫粘包。有关半包和粘包, 如下图所示:
粘包问题的解决策略
由于底层的 TCP 无法理解上层的业务数据,所以在底层是无法保证数据包不被拆分和重组的,这个问题只能通过上层的应用协议栈设计来解决。业界的主流协议的解决方案,可以归纳如下:
\1. 消息定长,报文大小固定长度,例如每个报文的长度固定为 200 字节,如果不够空位补空格;
\2. 包尾添加特殊分隔符,例如每条报文结束都添加回车换行符(例如 FTP 协议)或者指定特殊字符作为报文分隔符, 接收方通过特殊分隔符切分报文区分;
\3. 将消息分为消息头和消息体,消息头中包含表示信息的总长度(或者消息体长度)的字段;
\4. 更复杂的自定义应用层协议。
Netty 对半包的或者粘包的处理其实也很简单, 通过之前的学习, 我们知道, 每个 handler 是和 channel 唯一绑定的, 一个 handler 只对应一个 channel, 所以将 channel 中的数据读取时候经过解析, 如果不是一个完整的数据包, 则解析失 败, 将这块数据包进行保存, 等下次解析时再和这个数据包进行组装解析, 直到解析到完整的数据包, 才会将数据包进 行向下传递