Netty编解码总结

背景

TCP协议是个“流”性质协议,它的底层根据二进制缓冲区的实际情况进行包的划分,会把上层(Netty层)的ByteBuf包,进行重新的划分和重组,组成一帧一帧的二进制数据。换句话说,一个上层Netty中的 ByteBuf包,可能会被TCP底层拆分成多个二进制数据帧进行发送;也有可能,底层将多个小的ByteBuf包,封装成一个大的底层数据帧发送出去。

备注:

UDP协议不会发生沾包或拆包现象, 因为UDP是基于报文发送的,在UDP首部采用了16bit来指示UDP数据报文的长度,因此在应用层能很好的将不同的数据报文区分开。

如何从底层的二进制数据帧中,界定出来上层数据包的边界,也即是上层包的起点和末尾呢?

简单一点方法就是规定上层数据包的长度。例如,规定每个上层数据包的长度为100byte。再比如说,可以规定上层包的分割符号,比如换行符。无论采用什么方法,最为重要的是,发送方和接收方,在界定方法上必须保持一致。


Netty中,提供了几个重要的可以直接使用的帧解码器。

(1)固定长度帧解码器 - FixedLengthFrameDecoder

适用场景:每个上层数据包的长度,都是固定的,比如 100。在这种场景下,只需要把这个解码器加到 pipeline 中,Netty 会把底层帧,拆分成一个个长度为 100 的数据包 (ByteBuf),发送到下一个 channelHandler入站处理器。

(2)行分割帧解码器 - LineBasedFrameDecoder

适用场景:每个上层数据包,使用换行符或者回车换行符做为边界分割符。发送端发送的时候,每个数据包之间以换行符/回车换行符作为分隔。在这种场景下,只需要把这个解码器加到 pipeline 中,Netty 会使用换行分隔符,把底层帧分割成一个一个完整的应用层数据包,发送到下一站。前面的例子,已经对这个解码器进行了演示。

(3)自定义分隔符帧解码器 - DelimiterBasedFrameDecoder

DelimiterBasedFrameDecoder 是LineBasedFrameDecoder的通用版本。不同之处在于,这个解码器,可以自定义分隔符,而不是局限于换行符。如果使用这个解码器,在发送的时候,末尾必须带上对应的分隔符。

(4)自定义长度帧解码器 - LengthFieldBasedFrameDecoder

这是一种基于灵活长度的解码器。在数据包中,加了一个长度字段(长度域),保存上层包的长度。解码的时候,会按照这个长度,进行上层ByteBuf应用包的提取。

基于行分割符的示例

public class TestDecoder {

@Test

public void testLineBasedFrameDecoder() {

 //...

 ChannelInitializer i = new ChannelInitializer<EmbeddedChannel>() {

​                    protected void initChannel(EmbeddedChannel ch) {

​                                       ch.pipeline().addLast(new LineBasedFrameDecoder(1024));

​                                       ch.pipeline().addLast(new StringDecoder());

​                                       ch.pipeline().addLast(new StringProcessHandler());

​                    }

 };

 EmbeddedChannel channel = new EmbeddedChannel(i);

 for (int j = 0; j < 100; j++) {

​                    ByteBuf buf = Unpooled.buffer();

​                    String s = "I am " + j;

​                    buf.writeBytes(s.getBytes("UTF-8"));

​                    buf.writeBytes("\r\n".getBytes("UTF-8"));

​                    channel.writeInbound(buf);

 }

 //...

}

StringDecoder,它的作用是将接收到ByteBuf二进制数据,转换成字符串。

第四个解码器LengthFieldBasedFrameDecoder(自定义长度帧解码器)的参数比较多,比较难,同时也比较重要。

它的核心可以看作是把长度相关的信息也在报文里进行传送,可以看作是基于固定长度解码的通用版本

LengthFieldBasedFrameDecoder构造器,涉及5个参数,都与长度域(数据包中的长度字段)相关,具体介绍如下:

(1) maxFrameLength:最大帧长度。也就是可以接收的数据的最大长度。如果超过,此次数据会被丢弃。

(2) lengthFieldOffset:长度域偏移。就是说数据开始的几个字节可能不是表示数据长度,需要后移几个字节才是长度域。

(3) lengthFieldLength:长度域字节数。用几个字节来表示数据长度。

(4) lengthAdjustment:数据长度修正。因为长度域指定的长度可以使 header+body 的整个长度,也可以只是body的长度。如果表示header+body的整个长度,那么我们需要修正数据长度。

(5) initialBytesToStrip:跳过的字节数。如果你需要接收 header+body 的所有数据,此值就是0,如果你只想接收body数据,那么需要跳过header所占用的字节数。

自定义长度解码器的构造参数值如下:

LengthFieldBasedFrameDecoder spliter=new LengthFieldBasedFrameDecoder(1024,0,4,0,4);

第一个参数为1024,表示数据包的最大长度为1024;第二个参数0,表示长度域的偏移量为0,也就是长度域放在了最前面,处于包的起始位置(该参数大于零的时候说明存在请求头);第三个参数为4,表示长度域占用4个字节;第四个参数为0,表示长度域保存的值,仅仅为有效数据长度,不包含其他域(如长度域)的长度;第五个参数为4,表示最终的取到的目标数据包,抛弃最前面的4个字节数据,长度域的值被抛弃。

15854876-ced001511a8bfdfe.png
基于长度域的编解码器.png

0x000C表示正式报文的字节数是12

猜你喜欢

转载自blog.csdn.net/weixin_33923148/article/details/87165527