TCP的粘包拆包问题及方案(Netty)

4.1.1 粘包拆包问题描述

传输层除了有TCP协议外还有UDP协议。

#UDP
不会发生粘包或拆包现象
因为UDP是基于报文发送的,从UDP的帧结构可以看出,
在UDP首部采用了16bit来指示UDP数据报文的长度,
因此在应用层能很好的将不同的数据报文区分开,从而避免粘包和拆包的问题。

#TCP
是基于字节流的,在基于流的传输里(如TCP/IP),接收到的数据会先被存储到一个socket接收缓冲里。
不幸的是,基于流的传输并不是一个数据包队列,而是一个字节队列。
TCP底层并不了解上层业务数据的具体含义,它会根据TCP缓冲区的实际情况进行化包的划分,
所以在业务上认为,一个完整的包可能会被TCP拆成多个包进行发送,
也有多个小的包封装成一个大的数据包发送,这就是所谓的TCP粘包和拆包问题。
TCP的粘包拆包问题及方案(Netty)_第1张图片
粘包拆包问题.png

4.1.2 粘包拆包产生的原因

服务端和客户端都会造成粘包、半包问题,以下列出常见原因。

#服务端:
>> 要发送的数据大于TCP发送缓冲区剩余空间大小,将会发生拆包。
>> 要发送的数据大于MSS(最大报文长度),TCP在传输前将进行拆包。
>> 要发送的数据小于TCP发送缓冲区的大小,TCP将多次写入缓冲区的数据一次发送出去,将会发生粘包。

#接收端:
>> 接收数据端的应用层没有及时读取接收缓冲区中的数据,将发生粘包。

4.1.3 粘包拆包问题的解决思路

TCP以流的方式进行数据传输,由于底层TCP无法理解上层的业务数据,
所以在底层是无法保证数据包不被拆分和重组的,
这个问题只能通过上层的应用协议栈设计来解决,上层的应用协议为了对消息进行区分。

#业界主流的解决方案归纳如下:
>> 客户端在发送数据包的时候,每个包都固定长度,比如1024个字节大小,
如果客户端发送的数据长度不足1024个字节,则通过补充空格的方式补全到指定长度;
>> 客户端在每个包的末尾使用固定的分隔符,例如\r\n,
如果一个包被拆分了,则等待下一个包发送过来之后找到其中的\r\n,
然后对其拆分后的头部部分与前一个包的剩余部分进行合并,这样就得到了一个完整的包;
>> 将消息分为头部和消息体,在头部中保存有当前整个消息的长度,
只有在读取到足够长度的消息之后才算是读到了一个完整的消息;
>> 更复杂的应用层协议。
>> 自定义协议解决 (可参考 Dubbo)。

4.1.4 Netty中粘包拆包问题的解决方案

#FixedLengthFrameDecoder
对于使用固定长度的粘包和拆包场景,可以使用FixedLengthFrameDecoder,该解码一器会每次读取固定长度的消息,
如果当前读取到的消息不足指定长度,那么就会等待下一个消息到达后进行补足。
其使用也比较简单,只需要在构造函数中指定每个消息的长度即可。
这里需要注意的是,FixedLengthFrameDecoder只是一个解码器,Netty也只提供了一个解码器,
这是因为对于解码是需要等待下一个包的进行补全的,代码相对复杂,
对于编码器,用户可以自行编写,因为编码时只需要将不足指定长度的部分进行补全即可。

#LineBasedFrameDecoder与DelimiterBasedFrameDecoder
这俩适用于通过分隔符进行粘包和拆包问题的处理。
LineBasedFrameDecoder的作用主要是通过换行符,即\n或者\r\n对数据进行处理;
DelimiterBasedFrameDecoder的作用则是通过用户指定的分隔符对数据进行粘包和拆包处理。
这两个类都是解码器类,而对于数据的编码,
也即在每个数据包最后添加换行符或者指定分割符的部分需要用户自行进行处理。

#LengthFieldBasedFrameDecoder与LengthFieldPrepender
二者需要配合起来使用,其实本质上来讲,这两者一个是解码,一个是编码的关系。
它们处理粘拆包的主要思想是在生成的数据包中添加一个长度字段,用于记录当前数据包的长度。
1.LengthFieldBasedFrameDecoder:
会按照参数指定的包长度偏移量数据对接收到的数据进行解码,从而得到目标消息体数据;
>> maxFrameLength:指定了每个包所能传递的最大数据包大小;
>> lengthFieldOffset:指定了长度字段在字节码中的偏移量;
>> lengthFieldLength:指定了长度字段所占用的字节长度;
>> lengthAdjustment:对一些不仅包含有消息头和消息体的数据进行消息头的长度的调整,
这样就可以只得到消息体的数据,这里的lengthAdjustment指定的就是消息头的长度;
>> initialBytesToStrip:对于长度字段在消息头中间的情况,
可以通过initialBytesToStrip忽略掉消息头以及长度字段占用的字节。
2.LengthFieldPrepender则会在响应的数据前面添加指定的字节数据,
这个字节数据中保存了当前消息体的整体字节数据长度。

#自定义粘包与拆包器
>> 方案1:
通过继承LengthFieldBasedFrameDecoder和LengthFieldPrepender来实现粘包和拆包的处理。
>> 方案2:
通过继承MessageToByteEncoder和ByteToMessageDecoder来实现。
这里MessageToByteEncoder的作用是将响应数据编码为一个ByteBuf对象,
而ByteToMessageDecoder则是将接收到的ByteBuf数据转换为某个对象数据。
通过实现这两个抽象类,用户就可以达到实现自定义粘包和拆包处理的目的。
TCP的粘包拆包问题及方案(Netty)_第2张图片
LengthFieldBasedFrameDecoder的解码过程.png
TCP的粘包拆包问题及方案(Netty)_第3张图片
LengthFieldPrepender的编码过程.png

https://my.oschina.net/zhangxufeng/blog/3023794 (netty中粘包拆包)

你可能感兴趣的:(TCP的粘包拆包问题及方案(Netty))