摘自:https://blog.youkuaiyun.com/mzjnumber1/article/details/106663193
1、介绍
大多数的协议(私有或者公有),协议头中会携带长度字段,用于标识消息体或者整包消息的长度,例如SMPP、HTTP协议等。由于基于长度解码需求 的通用性,Netty提供了LengthFieldBasedFrameDecoder/LengthFieldPrepender,自动屏蔽TCP底层的拆包和粘包问题,只需要传入正确的参数,即可轻松解决“读半包“问题。
发送方使用LengthFieldPrepender给实际内容Content进行编码添加报文头Length字段,接受方使用LengthFieldBasedFrameDecoder进行解码。协议格式如下所示:
+--------+----------+
| Length | Content |
+--------+----------+
AI生成项目
Length字段:
表示Conent部分的字节数,例如Length值为100,那么意味着Conent部分占用的字节数就是100。
Length字段本身是个整数,也要占用字节,一般会使用固定的字节数表示。例如我们指定使用2个字节(有符号)表示length,那么可以表示的最大值为32767(约等于32K),也就是说,Content部分占用的字节数,最大不能超过32767。当然,Length字段存储的是Content字段的真实长度。
Content字段:
是我们要处理的真实二进制数据。 在发送Content内容之前,首先需要获取其真实长度,添加在内容二进制流之前,然后再发送。Length占用的字节数+Content占用的字节数,就是我们总共要发送的字节。
事实上,我们可以把Length部分看做报文头,报文头包含了解析报文体(Content字段)的相关元数据,例如Length报文头表示的元数据就是Content部分占用的字节数。当然,LengthFieldBasedFrameDecoder并没有限制我们只能添加Length报文头,我们可以在Length字段前或后,加上一些其他的报文头,此时协议格式如下所示:
+---------+--------+----------+----------+
|........ | Length | ....... | Content |
+---------+--------+----------+----------+
AI生成项目
不过对于LengthFieldBasedFrameDecoder而言,其关心的只是Length字段。因此当我们在构造一个LengthFieldBasedFrameDecoder时,最主要的就是告诉其如何处理Length字段。
2 、LengthFieldPrepender参数详解
LengthFieldPrepender提供了多个构造方法,最终调用的都是:
public LengthFieldPrepender(int lengthFieldLength, boolean lengthIncludesLengthFieldLength) {
this(lengthFieldLength, 0, lengthIncludesLengthFieldLength);
}
public LengthFieldPrepender(
ByteOrder byteOrder, int lengthFieldLength,
int lengthAdjustment, boolean lengthIncludesLengthFieldLength)
AI生成项目
其中:
byteOrder:表示Length字段本身占用的字节数使用的是大端还是小端编码
lengthFieldLength:表示Length字段本身占用的字节数,只可以指定 1, 2, 3, 4, 或 8
lengthAdjustment:表示Length字段调整值
lengthIncludesLengthFieldLength:表示Length字段本身占用的字节数是否包含在Length字段表示的值中。
例如:对于以下包含12个字节的报文
+----------------+
| "HELLO, WORLD" |
+----------------+
AI生成项目
假设我们指定Length字段占用2个字节,lengthIncludesLengthFieldLength指定为false,即不包含本身占用的字节,那么Length字段的值为0x000C(即12)。
+--------+----------------+
+ 0x000C | "HELLO, WORLD" |
+--------+----------------+
AI生成项目
如果我们指定lengthIncludesLengthFieldLength指定为true,那么Length字段的值为:0x000E(即14)=Length(2)+Content字段(12)
+--------+----------------+
+ 0x000E | "HELLO, WORLD" |
+--------+----------------+
AI生成项目
关于lengthAdjustment字段的含义,参见下面的LengthFieldBasedFrameDecoder。
LengthFieldPrepender尤其值得说明的一点是,其提供了实现零拷贝的另一种思路(实际上编码过程,是零拷贝的一个重要应用场景)。
在Netty中我们可以使用ByteBufAllocator.directBuffer()创建直接缓冲区实例,从而避免数据从堆内存(用户空间)向直接内存(内核空间)的拷贝,这是系统层面的零拷贝;
也可以使用CompositeByteBuf把两个ByteBuf合并在一起,例如一个存放报文头,另一个存放报文体。而不是创建一个更大的ByteBuf,把两个小ByteBuf合并在一起,这是应用层面的零拷贝。
而LengthFieldPrepender,由于需要在原来的二进制数据之前添加一个Length字段,因此就需要对二者进行合并发送。但是LengthFieldPrepender并没有采用CompositeByteBuf,其编码过程如下:
protected void encode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) throws Exception {
//1 获得Length字段的值:真实数据可读字节数+Length字段调整值
int length = msg.readableBytes() + lengthAdjustment;
if (lengthIncludesLengthFieldLength) {
length += lengthFieldLength;
}
...
//2 根据lengthFieldLength指定的值(1、2、3、4、8),创建一个ByteBuffer实例,写入length的值,
//并添加到List类型的out变量中
switch (lengthFieldLength) {
case 1:
if (length >= 256) {
throw new IllegalArgumentException(
"length does not fit into a byte: " + length);
}
&

最低0.47元/天 解锁文章
1万+

被折叠的 条评论
为什么被折叠?



