在使用 TCP 协议进行网络通信时,由于 TCP 本身是一个基于流的协议,它不保证数据的边界,因此发送的数据包可能会被操作系统或网络设备拆分成多个小包发送,或者多个小数据包可会被合并成一个大的数据包发送给接收方,这就是所谓的 TCP 拆包和粘包问题。
Netty 作为一个高性能的网络编程框架,提供了一些解码器机制来解决 TCP 拆包和粘包问题:
一、固定长度消息协议FixedLengthFrameDecoder
消息定长,报文长度固定,需要注意的是FixedLengthFrameDecoder并没有提供一个对应的编码器,发送方每个报文的长度不够时,可填补空格,报文长度也需要校验。
Netty提供的解码器为FixedLengthFrameDecoder:
其工作原理:
FixedLengthFrameDecoder 通过构造函数设置期望的消息长度 frameLength。解码器将按照以下步骤工作:
1、每次从 ByteBuf 中读取数据时,会检查当前可读取的字节数。
2、如果可读的字节数小于 frameLength,将等待直到有足够的数据。
3、一旦累积到了 frameLength 字节的数据将这些字节作为一个完整的消息传递给下一个处理程序。
4、如果数据多于 frameLength 字节,剩余的数据将会在下一次解码操作中处理。
一般情况下,很少有client与server交互时,直接使用固定长度消息协议,可能会造成宽带浪费。例如你实际要发送的实际只有3个字节,但是定长协议设置的1024,那么可能你就要为这3个字节基础上,在加1021个空格,以便server端可以解析这个请求,而且 server端需要把多余的空格去掉之后再反序列化为消息体。
二、基于换行符协议的LineBasedFrameDecoder
主要以换行符来进行数据的区分。根据操作系统的不同,换行可以有两种换行符,分别是 “\n” 和 “\r\n” 。
通常情况下,LineBasedFrameDecoder会和StringDecoder配合使用,组合成按行切换的文本解码器,对于文本类协议的解析,文本换行解码器非常实用,例如对HTTP消息头的解析、FTP消息的解析等。
三、基于特殊分割符协议的DelimiterBasedFrameDecoder
DelimiterBasedFrameDecoder与LineBasedFrameDecoder类似,只不过更加通用,允许我们指定任意特殊字符作为分隔符。
我们还可以同时指定多个分隔符,如果在请求中的确有多个分隔符,将会选择内容最短的一个分隔符作为依据:例如选择"\n"为分隔符
+--------------+
| ABC\nDEF\r\n |
+--------------+
将会拆分为两个数据包:
+-----+-----+
| ABC | DEF |
+-----+-----+
对于以特殊字符作为报文分割条件的协议的解码器,如LineBasedFrameDecoder、DelimiterBasedFrameDecoder,都存在一个典型的问题,如果发送数据当中本身就包含了分隔符,怎么办?这种情况下,我可以选择对发送的内容进行base64编码,分隔符选择base64字符之外的特殊字符,而且Netty也提供了Base64Encoder、Base64Decoder。
四、基于消息头中的长度字段来确定消息长度协议的LengthFieldPrepender/ LengthFieldBasedFrameDecoder
是一种比较灵活的编码、解码协议,把消息的长度等某些属性包含在了消息体中。
LengthFieldPrepender是其对应的编码器,有 4 个成员变量:
//
private final ByteOrder byteOrder;
private final int lengthFieldLength;
private final boolean lengthIncludesLengthFieldLength;
private final int lengthAdjustment;
1、byteOrder:设置字节序,默认大字端。表示在缓冲区处理数据是以大字端方式,还是以小字端方式;
2、lengthFieldLength:数据长度所占用的字节数,没有默认值,必须设置,而且值必须为1、2、3、4、8中的一个;
3、lengthIncludesLengthFieldLength:默认false,数据长度中是否包含数据长度本身的长度;
4、lengthAdjustment:默认0,长度调整字节数,消息体的长度等于数据长度加上长度调整字节数。
写入数据包时写入:数据长度(消息体本身长度+
lengthAdjustment+lengthFieldLength)+ 数据。
LengthFieldBasedFrameDecoder是其对应的解码器,其中有 8 个 final 类型的成员变量,有 3 个类型的非 final 类型的成员变量:
private final ByteOrder byteOrder;
private final int maxFrameLength;
private final int lengthFieldOffset;
private final int lengthFieldLength;
private final int lengthFieldEndOffset;
private final int lengthAdjustment;
private final int initialBytesToStrip;
private final boolean failFast;
private boolean discardingTooLongFrame;
private long tooLongFrameLength;
private long bytesToDiscard;
1、byteOrder:字节序,默认大字端;
2、maxFrameLength:一个数据包允许的最大长度,初始化时必须设置;
3、lengthFieldOffset:数据长度所在位置偏移量,从第几位开始读数据长度;
4、lengthFieldLength:数据长度所占用的字节数;
5、lengthFieldEndOffset:默认值为 0,结束偏移量;
6、lengthAdjustment:默认值为 0,长度调整字节数;
7、initialBytesToStrip:默认值为0,要剥离的初始字节;
8、failFast:快速失败,默认 true,如果为 true 时,不读完数据包就抛出异常,否则读完数据包再抛出异常;
9、discardingTooLongFrame:是否跳过超出存储范围的字节,默认false;
10、tooLongFrameLength:最长的包长;
11、bytesToDiscard:需要跳过的字节数;
LengthFieldBasedFrameDecoder 拥有许多配置参数,这使得它能够解码任何带有长度字段的消息,这种情况通常出现在专有的客户端-服务器通信协议中。以下是一些示例,它们将为您提供关于各个选项功能的基本理解。
五、自定义协议
比较知名的netty tcp 框架都使用了自己的编码器、解码器解决tcp的拆包、粘包,比如dubbo2协议:
来源:https://cn.dubbo.apache.org/zh-cn/overview/reference/protocols/tcp/