netty粘包拆包之LengthFieldBasedFrameDecoder解码器

netty解码器LengthFieldBasedFrameDecoder用法详解 LengthFieldBasedFrameDecoder 是一个非常灵活的解码器,可以处理各种带有长度字段的帧结构。通过调整lengthFieldOffset, lengthFieldLength, lengthAdjustment 和initialBytesToStrip 参数,可以适应不同的帧结构。 阅读详情

​目录

          一、 背景简介

          二、 应用

          三、源码


一、背景简介

LengthFieldBasedFrameDecoder 基于长度字段解码器,是一个非常灵活、强大的解码器,他能够根据我们动态配置的参数对接收到的消息进行动态解码,以满足实际的业务需求。当解码具有消息头长度字段表示该消息主体或整个消息的长度的二进制消息时,它是特别有用的。

二、应用

在这里先简单看下LengthFieldBasedFrameDecoder类中的核心字段。这些字段在下面分析各种情况的解码的时候是非常有用的。

    //=====================核心属性====================
    //表示字节流的数据是大端还是小端,默认是大端
    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;

下面是DelimiterBasedFrameDecoder的一个简单小测试。

服务端:

  public class LengthFieldBasedFrameDecoderTestServer {
​
    public static void main(String[] args) throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG, 1024)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) {
                            ch.pipeline()
                                    .addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE,0,2,0,2))
                                    .addLast(new ChannelInboundHandlerAdapter() {
                                        @Override
                                        public void channelRead(ChannelHandlerContext ctx, Object msg) {
                                            if (msg instanceof ByteBuf) {
                                                ByteBuf packet = (ByteBuf) msg;
                                                System.out.println(packet.toString(Charset.defaultCharset()));
                                            }
                                        }
                                    });
                        }
                    });
            ChannelFuture f = b.bind(9000).sync();
            System.out.println("Started LengthFieldBasedFrameDecoderTestServer...");
            f.channel().closeFuture().sync();
        } finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }
}

客户端:

public class LengthFieldBasedFrameDecoderTestClient {
​
    public static void main(String[] args) throws Exception {
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            Bootstrap b = new Bootstrap();
            b.group(workerGroup)
                    .channel(NioSocketChannel.class)
                    .option(ChannelOption.SO_KEEPALIVE, true)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) {
                            ch.pipeline()
                                    .addLast(new ChannelInboundHandlerAdapter() {
                                        public void channelActive(ChannelHandlerContext ctx) {
                                            ByteBuf byteBuf = Unpooled.copiedBuffer("hello world".getBytes());
                                            ctx.writeAndFlush(byteBuf);
                                        }
                                    });
                        }
                    });
            ChannelFuture f = b.connect("127.0.0.1", 9000).sync();
            System.out.println("Started LengthFieldBasedFrameDecoderTestClient...");
            f.channel().closeFuture().sync();
        } finally {
            workerGroup.shutdownGracefully();
        }
    }
}

当然在这里基于这种配置下是没有任何输出的。

然后在接下来我们一一分析,各种配置参数到底在LengthFieldBasedFrameDecoder解码器中扮演着什么角色,起到什么作用,能够满足什么样的协议要求。

【以下示例来自JDK类LengthFieldBasedFrameDecoder DOC文档】

示例①:

2字节长度的长度域,长度域偏移量为0,不跳过消息头。

BEFORE DECODE (14 bytes)         AFTER DECODE (14 bytes)
+--------+----------------+      +--------+----------------+
| Length | Actual Content |----->| Length | Actual Content |
| 0x000C | "HELLO, WORLD" |      | 0x000C | "HELLO, WORLD" |
+--------+----------------+      +--------+----------------+

说明:

在这个例子中,长度字段的值是12(0x0C)表示“HELLO,WORLD”的长度(不包括长度域,只代表数据域的长度)。默认情况下,解码器假定长度字段表示随后的长度字段中的字节数。因此,它可以与简单的参数组合进行解码。

   lengthFieldOffset   = 0

   lengthFieldLength   = 2

   lengthAdjustment    = 0

   initialBytesToStrip = 0 (= do not strip header)

LengthFieldBasedFrameDecoder构造传参如下:

new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE,0,2,0,0);

示例②:

2字节长度的长度域,长度域偏移量为0,跳过消息头。

   BEFORE DECODE (14 bytes)         AFTER DECODE (12 bytes)
   +--------+----------------+      +----------------+
   | Length | Actual Content |----->| Actual Content |
   | 0x000C | "HELLO, WORLD" |     |"HELLO, WORLD" |
   +--------+----------------+      +----------------+

说明:

在这个例子中,长度字段的值是12(0x0C)表示“HELLO,WORLD”的长度(不包括长度域,只代表数据域的长度)。解码后长度域丢失。

   lengthFieldOffset   = 0

   lengthFieldLength   = 2

   lengthAdjustment    = 0

   initialBytesToStrip = 2 (= the length of the Length field)

LengthFieldBasedFrameDecoder构造传参如下:

new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE,0,2,0,2);

示例③:

2字节长度的长度域,lengthAdjustment为向左2个字节,不跳过消息头。

  BEFORE DECODE (14 bytes)         AFTER DECODE (14 bytes)
   +--------+----------------+      +--------+----------------+
   | Length | Actual Content |----->| Length | Actual Content |
   | 0x000E | "HELLO, WORLD" |      | 0x000E | "HELLO, WORLD" |
   +--------+----------------+      +--------+----------------+

说明:

在大多数情况下,长度字段表示只有消息主体的长度。然而,在一些协议中,长度字段表示整个消息的长度,其中包括消息报头。在这种情况下,我们指定一个非零lengthAdjustment向左偏移,我们指定-2作为lengthAdjustment赔偿。

   lengthFieldOffset   =  0

   lengthFieldLength   =  2

   lengthAdjustment    = -2 (= the length of the Length field)

   initialBytesToStrip =  0

LengthFieldBasedFrameDecoder构造传参如下:

new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE,0,2,-2,0);

示例④:

3字节长度的长度域,2个字节的消息头长度Header 1,不跳过消息头。

   BEFORE DECODE (17 bytes)                      AFTER DECODE (17 bytes)
   +----------+----------+----------------+      +----------+----------+----------------+
   | Header 1 |  Length  | Actual Content |----->| Header 1 |  Length  | Actual Content |
   |  0xCAFE  | 0x00000C | "HELLO, WORLD" |      |  0xCAFE  | 0x00000C | "HELLO, WORLD" |
   +----------+----------+----------------+      +----------+----------+----------------+

说明:

该消息是第一个实施的一个简单的变化。一个消息头值被预置到该消息。lengthAdjustment是零。

   lengthFieldOffset   = 2 (= the length of Header 1)

   lengthFieldLength   = 3

   lengthAdjustment    = 0

   initialBytesToStrip = 0

LengthFieldBasedFrameDecoder构造传参如下:

new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE,2,3,0,0);

示例⑤:

3字节长度的长度域,2个字节的消息头长度Header 1,不跳过消息头。

     BEFORE DECODE (17 bytes)                      AFTER DECODE (17 bytes)
   +----------+----------+----------------+      +----------+----------+----------------+
   |  Length  | Header 1 | Actual Content |----->|  Length  | Header 1 | Actual Content |
   | 0x00000C |  0xCAFE  | "HELLO, WORLD" |      | 0x00000C |  0xCAFE  | "HELLO, WORLD" |
   +----------+----------+----------------+      +----------+----------+----------------+

说明:

该情况和实例4类似,不过其中在长度域和数据域之间有附加head的情况。你必须指定一个lengthAdjustment,使得解码器计数额外的头到帧的长度计算。

   lengthFieldOffset   = 0

   lengthFieldLength   = 3

   lengthAdjustment    = 2 (= the length of Header 1)

   initialBytesToStrip = 0

LengthFieldBasedFrameDecoder构造传参如下:

new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE,0,3,2,0);

示例⑥:

1字节长度的Header 1,2个字节的长度域,1字节长度的Header 2,跳过Header 1和长度域。

   BEFORE DECODE (16 bytes)                       AFTER DECODE (13 bytes)
   +------+--------+------+----------------+      +------+----------------+
   | HDR1 | Length | HDR2 | Actual Content |----->| HDR2 | Actual Content |
   | 0xCA | 0x000C | 0xFE | "HELLO, WORLD" |      | 0xFE | "HELLO, WORLD" |
   +------+--------+------+----------------+      +------+----------------+

说明:

这是所有上面的例子的组合。有长度字段之前Header 1和长度字段后附标题Header 2。Header 1影响lengthFieldOffset和Header 2影响lengthAdjustment。我们还指定非零initialBytesToStrip减去长度字段和Header 1。如果你不想要去除预谋头,你可以为initialBytesToSkip指定为0。

   lengthFieldOffset   = 1 (= the length of HDR1)

   lengthFieldLength   = 2

   lengthAdjustment    = 1 (= the length of HDR2)

   initialBytesToStrip = 3 (= the length of HDR1 + LEN)

LengthFieldBasedFrameDecoder构造传参如下:

new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE,1,2,1,3);

示例⑦:

1字节长度的Header 1,2个字节的长度域,1字节长度的Header 2,跳过Header 1和长度域,指定initialBytesToSkip。

   BEFORE DECODE (16 bytes)                       AFTER DECODE (13 bytes)
   +------+--------+------+----------------+      +------+----------------+
   | HDR1 | Length | HDR2 | Actual Content |----->| HDR2 | Actual Content |
   | 0xCA | 0x0010 | 0xFE | "HELLO, WORLD" |      | 0xFE | "HELLO, WORLD" |
   +------+--------+------+----------------+      +------+----------------+

说明:

让我们再到前面的例子。和前面的例子中,唯一的区别是,长度字段表示整个消息,而不是在消息主体的长度,就像示例③。 此时需要指定initialBytesToSkip向左偏移作为补偿。

   lengthFieldOffset   =  1

   lengthFieldLength   =  2

   lengthAdjustment    = -3 (= the length of HDR1 + LEN, negative)

   initialBytesToStrip =  3

LengthFieldBasedFrameDecoder构造传参如下:

new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE,1,2,-3,3);

三、源码

netty提供的各种解码器统一都继承了ByteToMessageDecoder,ByteToMessageDecoder负责读取字节流并转换成其他消息,而ByteToMessageDecoder又继承了ChannelInboundHandlerAdapter,而ChannelInboundHandlerAdapter正是我们刚才在测试数据的时候在客户端重写了他的方法channelActive(),使管道生效并且数据写入缓冲区并发送数据。

在客户端代码流程原理并不复杂,我们主要看服务端是怎么解码、获取到数据的。

当客户端通过Channel发送数据的时候,服务端会在ByteToMessageDecoder的channelRead方法接收到。我们以channelRead方法为入口看服务端是怎么进行消息解码的。

在channelRead方法中首先会判断消息msg是不是ByteBuf类型的:

①、如果msg不是ByteBuf类型的,直接调用ctx.fireChannelRead(msg)方法,fireChannelRead方法实际上是调用pipeline管道的下一个Handler去继续处理消息其他的逻辑去了,我们进入到AbstractChannelHandlerContext的fireChannelRead方法,发现只有一个方法invokeChannelRead,寻找下一个绑定的Handler并且调用ChannelRead方法。

    @Override
    public ChannelHandlerContext fireChannelRead(final Object msg) {
        //寻找下一个绑定的Handler并且调用ChannelRead方法
        invokeChannelRead(findContextInbound(), msg);
        return this;
    }

继续进入到invokeChannelRead。

    static void invokeChannelRead(final AbstractChannelHandlerContext next, Object msg) {
        final Object m = next.pipeline.touch(ObjectUtil.checkNotNull(msg, "msg"), next);
        EventExecutor executor = next.executor();
        if (executor.inEventLoop()) {
            next.invokeChannelRead(m);
        } else {
            executor.execute(() -> next.invokeChannelRead(m));
        }
    }

最终都会调用invokeChannelRead,而invokeChannelRead就是将Handler转换成ChannelInboundHandler类型去执行具体的channelRead方法,去进一步的读取消息并处理相关Handler逻辑。

    private void invokeChannelRead(Object msg) {
        if (invokeHandler()) {
            try {
                ((ChannelInboundHandler) handler()).channelRead(this, msg);
            } catch (Throwable t) {
                notifyHandlerException(t);
            }
        } else {
            fireChannelRead(msg);
        }
    }

②、如果msg是ByteBuf类型的,去调用解码逻辑callDecode方法去解析数据。

    protected void callDecode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
        try {
            while (in.isReadable()) {
                int outSize = out.size();
                //将out里的事件执行并且清空out
                if (outSize > 0) {
                    fireChannelRead(ctx, out, outSize);
                    out.clear();
                    // 在继续解码之前,请检查是否已删除此处理程序
                    // 如果已将其删除,则继续在缓冲区上操作是不安全的。
                    if (ctx.isRemoved()) {
                        break;
                    }
                    outSize = 0;
                }
                int oldInputLength = in.readableBytes();
                // 开始解析数据,如果解析出来数据,那么out的长度一定会改变
                decode(ctx, in, out);
​
                // 在继续循环之前,请检查是否已删除此处理程序。
                // 如果已将其删除,则继续在缓冲区上操作是不安全的。
                if (ctx.isRemoved()) {
                    break;
                }
                // 如果没有解析出来数据
                if (outSize == out.size()) {
                    if (oldInputLength == in.readableBytes()) {
                        break;
                    } else {
                        continue;
                    }
                }
​
                if (oldInputLength == in.readableBytes()) {
                    throw new DecoderException(
                            StringUtil.simpleClassName(getClass()) +
                            ".decode() did not read anything but decoded a message.");
                }
                if (isSingleDecode()) {
                    break;
                }
            }
        } catch (DecoderException e) {
            throw e;
        } catch (Throwable cause) {
            throw new DecoderException(cause);
        }
    }

在callDecode方法中,使用while循环去解析数据,这是因为一次发送的数据有可能被解析成多个分段,对于循环中的每次解析,首先判断缓冲区中是否还有可读空间,有可读空间执行while循环体,第一次执行循环体的时候out肯定是没有数据的,也就是out的size为0,下面会调用解码逻辑decode(ctx, in, out)去进行数据的第一次解析,我们先不看decode(ctx, in, out)到底执行了什么逻辑,现在假设已经执行了一次decode(ctx, in, out)逻辑,也就是out里面有数据了,就是第一次解析出来的数据,现在out的size为1,现在进入outSize > 0逻辑判断中,可以看到这个if判断逻辑核心就是fireChannelRead(ctx, out, outSize),然后将out清空,size置位0,关于fireChannelRead方法,其实在上面刚才我们也说过了,就是调用下一个Handler处理器去执行下一步处理逻辑,我们再次跟进去。

    static void fireChannelRead(ChannelHandlerContext ctx, List<Object> msgs, int numElements) {
        if (msgs instanceof CodecOutputList) {
            fireChannelRead(ctx, (CodecOutputList) msgs, numElements);
        } else {
            for (int i = 0; i < numElements; i++) {
                ctx.fireChannelRead(msgs.get(i));
            }
        }
    }

继续进入到fireChannelRead,来到AbstractChannelHandlerContext的fireChannelRead。

    @Override
    public ChannelHandlerContext fireChannelRead(final Object msg) {
        //寻找下一个绑定的Handler并且调用ChannelRead方法
        invokeChannelRead(findContextInbound(), msg);
        return this;
    }

继续往里走。

    static void invokeChannelRead(final AbstractChannelHandlerContext next, Object msg) {
        final Object m = next.pipeline.touch(ObjectUtil.checkNotNull(msg, "msg"), next);
        EventExecutor executor = next.executor();
        if (executor.inEventLoop()) {
            next.invokeChannelRead(m);
        } else {
            executor.execute(() -> next.invokeChannelRead(m));
        }
    }
​
    private void invokeChannelRead(Object msg) {
        if (invokeHandler()) {
            try {
                ((ChannelInboundHandler) handler()).channelRead(this, msg);
            } catch (Throwable t) {
                notifyHandlerException(t);
            }
        } else {
            fireChannelRead(msg);
        }
    }

重点看((ChannelInboundHandler) handler()).channelRead(this, msg),这里将handler转成ChannelInboundHandler类型,执行他的channelRead方法,其实就是我们刚才服务端的重写的channelRead。

也就是说解析完一次数据时候立刻就去执行我们自定义的处理逻辑去了。当然在这里只是一个测试代码,如果真正我们自定义的处理逻辑比较复杂,比如涉及到操作数据库、磁盘IO等,可以考虑放入线程池中进行业务处理,避免造成解析数据缓慢,网络迟钝。

回到我们刚才while循环解析数据的逻辑,进入到decode(ctx, in, out)中。进入到其实现类LengthFieldBasedFrameDecoder中。

这里我们回顾一下LengthFieldBasedFrameDecoder中核心属性。

    //=====================核心属性====================
    //表示字节流的数据是大端还是小端,默认是大端
    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;

核心属性都已标明注释,具体作用等下分析源码的时候再详细介绍。

接着进入到decode方法中去。

    @Override
    protected final void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        Object decoded = decode(ctx, in);
        if (decoded != null) {
            out.add(decoded);
        }
    }

decoded就是解析完的对象,如果这个对象不为空,就放到out中去,再看decode方法。

    protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
        //首先判断是否正在处于丢弃模式中,如果正在处于丢弃模式中,直接进行数据丢弃
        if (discardingTooLongFrame) {
            long bytesToDiscard = this.bytesToDiscard;
            int localBytesToDiscard = (int) Math.min(bytesToDiscard, in.readableBytes());
            //直接跳过指定的字节数据
            in.skipBytes(localBytesToDiscard);
            bytesToDiscard -= localBytesToDiscard;
            this.bytesToDiscard = bytesToDiscard;
            failIfNecessary(false);
        }
        // 如果当前可读字节还未达到lengthFieldEndOffset大小,说明数据域中并没有可读内容,此时直接返回null
        if (in.readableBytes() < lengthFieldEndOffset) {
            return null;
        }
        // 拿到实际的lengthFieldOffset
        int actualLengthFieldOffset = in.readerIndex() + lengthFieldOffset;
        // 拿到未调整过得数据包长度
        long frameLength = getUnadjustedFrameLength(in, actualLengthFieldOffset, lengthFieldLength, byteOrder);
        // 如果数据包长度为负数,直接跳过长度域并抛出异常
        if (frameLength < 0) {
            in.skipBytes(lengthFieldEndOffset);
            throw new CorruptedFrameException("negative pre-adjustment length field: " + frameLength);
        }
        // 调整包的总长度
        frameLength += lengthAdjustment + lengthFieldEndOffset;
        // 包的总长度还没有长度域长,直接抛出异常
        if (frameLength < lengthFieldEndOffset) {
            in.skipBytes(lengthFieldEndOffset);
            throw new CorruptedFrameException(
                    "Adjusted frame length (" + frameLength + ") is less " +
                    "than lengthFieldEndOffset: " + lengthFieldEndOffset);
        }
        // 数据包长度超出最大包长度,进入丢弃模式,总丢弃数据长度tooLongFrameLength
        if (frameLength > maxFrameLength) {
            long discard = frameLength - in.readableBytes();
            tooLongFrameLength = frameLength;
​
            if (discard < 0) {
                // 缓冲区包含更多的字节,然后是frameLength,所以我们现在就可以丢弃所有字节
                // 当前readableBytes达到frameLength,直接跳过frameLength个字节
                in.skipBytes((int) frameLength);
            } else {
                // 当前可读字节未达到frameLength,说明后面未读到的字节也需要丢弃,进入丢弃模式
                discardingTooLongFrame = true;
                // bytesToDiscard表示还需要丢弃多少字节
                bytesToDiscard = discard;
                // 进入丢弃模式并丢弃到目前为止收到的所有内容.
                in.skipBytes(in.readableBytes());
            }
            failIfNecessary(true);
            return null;
        }
​
        // frameLength永远不会溢出,因为它小于maxFrameLength
        int frameLengthInt = (int) frameLength;
        if (in.readableBytes() < frameLengthInt) {
            return null;
        }
​
        // 如果返回数据包之前跳过的字节都大于frameLengthInt,说明数据异常,跳过现在包中的数据
        if (initialBytesToStrip > frameLengthInt) {
            in.skipBytes(frameLengthInt);
            throw new CorruptedFrameException(
                    "Adjusted frame length (" + frameLength + ") is less " +
                    "than initialBytesToStrip: " + initialBytesToStrip);
        }
        // 正常跳过initialBytesToStrip字节
        in.skipBytes(initialBytesToStrip);
​
        // 提取帧数据
        int readerIndex = in.readerIndex();
        int actualFrameLength = frameLengthInt - initialBytesToStrip;
        ByteBuf frame = extractFrame(ctx, in, readerIndex, actualFrameLength);
        // 调整读索引
        in.readerIndex(readerIndex + actualFrameLength);
        return frame;
    }

这个方法的处理逻辑就是LengthFieldBasedFrameDecoder解码器的核心解码逻辑。

首先判断是否正在处于丢弃模式中,如果正在处于丢弃模式中,直接进行数据丢弃。bytesToDiscard代表的是每次需要丢弃的字节数据,直到bytesToDiscard为0标明,数据都已经丢弃完成。然后判断如果当前可读字节还未达到lengthFieldEndOffset大小,说明数据域中并没有可读内容,此时直接返回null。接着往下走拿到实际的lengthFieldOffset,然后通过getUnadjustedFrameLength方法计算未调整过得数据包长度frameLength,如果数据包frameLength的长度为负数,直接跳过长度域总长度lengthFieldEndOffset并抛出异常,然后通过frameLength += lengthAdjustment + lengthFieldEndOffset计算调整包的总长度,此时frameLength就是总的数据长度(长度域偏移量+长度域+调整长度+数据域长度)。如果此时包的总长度还没有长度域长,直接抛出异常,如果此时数据包长度超出最大包长度,进入丢弃模式相关逻辑,总丢弃数据长度tooLongFrameLength,如果当前readableBytes达到frameLength,直接跳过frameLength个字节,后面的字节无需丢弃,如果当前可读字节未达到frameLength,说明后面未读到的字节也需要丢弃,进入丢弃模式,并且将丢弃模式开启。最终返回null,这次解析完成。如果以上所有的过程都通过了,继续往下走,此时frameLength永远不会溢出,因为它小于maxFrameLength,如果此时frameLengthInt大于了可读字节,说明数据还不完整,直接返回null,期待下次发送数据,然后如果返回数据包之前跳过的字节都大于frameLengthInt,说明数据异常,跳过现在包中的数据,至此,所有异常情况都已经处理完成,接下来就是正常解码的逻辑,正常跳过initialBytesToStrip字节,然后开始提取帧数据从读索引开始读取实际的数据域长度,获得frame数据,最后返回键就行了。这样一次解析就完成了,接着循环往复解析并执行其他handler逻辑。

至此,服务端利用LengthFieldBasedFrameDecoder解码器解析数据的流程也就分析完了。

个人才疏学浅、信手涂鸦,netty框架更多模块解读相关源码持续更新中,感兴趣的朋友请移步至个人公众号,谢谢支持😜😜......

公众号:wenyixicodedog

[netty]--最通用TCP黏包解决方案:LengthFieldBasedFrameDecoder和LengthFieldPrepender 前面已经说过: TCP以流的方式进行数据传输,上层应用协议为了对消息进行区分,往往采用如下4种方式。 (1)消息长度固定:累计读取到固定长度为LENGTH之后就认为读取到了一个完整的消息。然后将计数器复位,重新开始读下一个数据报文。 (2)回车换行符作为消息结束符:在文本协议中应用比较广泛。 (3)将特殊的分隔符作为消息的结束标志,回车换行符就是一种特殊的结... 阅读详情

相关推荐

Netty如何驾驭TCP流式传输?粘包拆包问题全解与编解码器最佳实践

其实上述的问题的原因都是因为TCP是面向流的协议,导致了数据包无法被正常切割成一个个正常数据包的流。就以上面的数据包为例,发送的数据为如果发送的数据都是以结尾,那我们的分割时就判断收到的流是否包含,只有包含时再将数据装成数据包发送。上述发送的数据长度为19,我们也可以规定发送的数据长度为19字节,一旦收到的数据达到19个字节之后,就组装成一个数据包。

禅与计算机程序设计的艺术 4935

LengthFieldBasedFrameDecoder 详解

LengthFieldBasedFrameDecoder 详解 疯狂创客圈 Java 分布式聊天室【 亿级流量】实战系列之 -31【 博客园 总入口 】 文章目录LengthFieldBasedFrameDecoder 详解写在前面1.1.1. 解码器:FrameDecoder1.1.1. 难点:自定义长度帧解码器写在最后疯狂创客圈 亿级流量 高并发IM 学习实战 ...

架构师尼恩 9705

Netty4实战 - TCP粘包&拆包解决方案

Netty4实战 - TCP粘包&拆包解决方案

Ricky 1万+

LengthFieldBasedFrameDecoder解析

LengthFieldBasedFrameDecoder源码解析 LengthFieldBasedFrameDecoder 是netty中功能最强大,应用最广泛的一个解码器,可以方便的用其解析用户的自定义协议。 1、参数说明 lengthFieldOffset 长度域的偏移量,简单而言就是偏移几个字节是长度域 lengthFieldLength : 长度域的所占的字节数 lengthAdjustment : 长度值的调整值 initialBytesToStrip : 需要跳过的长度值 如图所示:上面

博客 6559

用Netty自己写拆包粘包解码器

最近做了一个项目,项目中用到Netty来接受一些自定义的报文。 一、背景 tcp是以流的方式进行传输,在流里我们要判断消息的起始位置和结束位置。为了区分消息,往往采用下面的几种方式。 消息有固定的长度 换行符做分隔 用一个特殊的分隔符来分隔 在消息头中增加length字段 Netty中针对以上的方案都有已经实现好的解码器作为解决方案。 针对有固定长度的消息,Netty提供了Fixed...

wlittlefive的博客 1989

Netty LengthFieldBasedFrameDecoder:解决拆包粘包问题的解码器

拆包指的是接收方无法完整地接收到发送方传输的数据包,而粘包则是接收方接收到了多个数据包粘在一起的情况。通过使用该解码器,我们能够在后端开发中处理网络通信时,确保数据的完整性并准确解析数据包,从而避免解析错误和业务逻辑混乱的情况发生。通过使用LengthFieldBasedFrameDecoder,我们可以确保从网络中接收到的数据按照指定的长度字段进行拆包,每个完整的数据包都可以被正确解析。它基于长度字段的值来解码数据帧,通过读取数据包中的长度字段来判断数据的边界,从而将一个完整的数据包解析出来。

YOLOv7895的博客 410

LengthFieldBasedFrameDecoder

是一个根据在接收到的 ByteBuf 消息中的长度值对这个消息动态的切分的解码器。在需要解码二进制消息并且在消息头中包含代表这条消息的整体长度或消息体长度的字段的情况下非常有用。 A decoder that splits the received {@link ByteBuf}s dynamically by the value of the length field in the m...

xssl_优快云的博客 2336

基于单片机控制双向可控硅灯光强度设计

*单片机设计介绍,基于单片机控制双向可控硅灯光强度设计。

QQ2193276455的博客 2457

Netty学习(四)—LengthFieldBasedFrameDecoder解码器

Netty学习(四)—LengthFieldBasedFrameDecoder解码器 LengthFieldBasedFrameDecoder和LengthFieldPrepender组合是解决TCP粘包和拆包问题的最佳方案,通过将消息分为消息头和消息体记录消息长度解决读半包问题; 个人主页:tuzhenyu’s page 原文地址:Netty学习(四)—LengthFie

tuzhenyu的博客 2351

2025数学建模美赛C题完整建模思路——奥运会奖牌榜的模型

2025数学建模美赛C题完整建模思路——奥运会奖牌榜的模型

Yilu264的博客 1万+

树莓派3-改装wifi外置天线

声明:本文只是技术性探讨,改装引起的产品损坏等后果,由用户自负! 改装主要有两种方法 方法一:直接替换原来的天线 1、移除原有的小陶瓷天线,就在Made in PRC(或UK)那边的那个 清理一下阻焊剂方便天线接地(图中PRC左侧) 2、焊接天线 天线的屏蔽层焊接到清理完阻焊剂的地方,中间的信号线焊接到PRC右侧焊盘。 3、最终的样子

huayucong的博客 2万+

通过大量实战案例分解Netty中是如何解决拆包黏包问题的?

TCP传输协议是基于数据流传输的,而基于流化的数据是没有界限的,当客户端向服务端发送数据时,可能会把一个完整的数据报文拆分成多个小报文进行发送,也可能将多个报文合并成一个大报文进行发送。 在这样的情况下,有可能会出现图3-1所示的情况。 服务端恰巧读到了两个完整的数据包 A 和 B,没有出现拆包/粘包问题; 服务端接收到 A 和 B 粘在一起的数据包,服务端需要解析出 A 和 B; 服务端收到完整的 A 和 B 的一部分数据包 B-1,服务端需要解析出完整的 A,并等待读取完整的 B 数据包; 服务端接.

跟着Mic学架构 841

手把手教你解决Kernel panic报错:init找不到的5种排查方法(附NFS挂载实战)

本文深入解析了Linux系统启动时出现“Kernel panic - not syncing: No working init found”报错的根本原因,并提供了五种系统性的排查方法。文章结合NFS挂载根文件系统的实战场景,详细阐述了从动态链接库依赖、内核引导参数、NFS权限配置、文件系统完整性到内核配置干扰的完整排查路径,帮助开发者快速定位并解决这一常见的系统启动故障。

weixin_29159995的博客 193

Netty中的LengthFieldBasedFrameDecoder解码器

假如客户端给服务端发送数据,那么服务端的​Netty从网络中读取的数据都是连续的字节流数据,同时粘包和拆包也在’捣乱’,如何读取一个完整的数据包, 这个重担就落在了解码器的身上. 本篇文章介绍下使用广泛的LengthFieldBasedFrameDecoder解码器.在介绍之前, 先看个总览图 简单描述上面这张图, 假如客户端给服务端发送数据. 第一次​当数据(HEL)到达服务端之后,Netty中的NioByteUnsafe类会从TCP缓冲区将数据(HEL)读取出来并封装成一个ByteBuf传给帧解码器.

书唐瑞的博客 2359

龙虾 Skill 技能库|OpenClaw+Hermes 全集成 一键调用所有 AI 技能

龙虾 Skill 技能库、OpenClaw、Hermes Agent、AI 技能集成、本地 AI 自动化、办公工具、数字员工

weixin_42219287的博客 624

Autosar NvM配置-手动配置Nvblock及使用-基于ETAS软件

NVM作为存储协议栈中最顶层的模块,是必须要掌握的。目前项目基本使用MCU带的Dflash模块,使用Fee模拟eeprom。在项目前期阶段,应该充分讨论需要存储的内容,包括应用数据,诊断数据等,用以评估Dflash空间是否够用。本文基于ETAS软件介绍NVBlock配置生成NVM参数及使用。使用NVBlock配置生成,基本不用手写代码,还是比较方便的。对于Nv block的划分,建议将同时写的一类数据放到一个block中,因为block除了data以外,Fee还会增加一些header数据,占用不少内存。

赞的博客 1884

TCP粘包/拆包问题之Netty解决方案

TCP以流的方式进行数据传输,上层的应用协议为了对消息进行区分,往往采用如下四种方式: 消息长度固定,累计读取到长度总和为定长LEN的报文后,就认为读取到了一个完整的消息;将计数器置位,重新开始读取下一个数据报; 将回车换行符作为消息结束符,例如FTP协议,这种方式在文本协议中应用比较广泛; 将特殊的分隔符作为消息的结束标志,回车换行符就是一种特殊的结束分隔符; 通过在消息头中定义...

. 449

c#算法(5)—最小二乘法(二次曲线拟合)

最小二乘法——二次曲线拟合

qq_34059233的博客 1659

关于 LengthFieldBasedFrameDecoder 不得不说的事

LengthFieldBasedFrameDecoder 是什么? LengthFieldBasedFrameDecoder对于不经常接触netty 的人来说可能有些陌生,但是对于经常使用netty的人来说,对它一定不陌生。 A decoder that splits the received ByteBufs dynamically by the value of the length field in the message. It is particularly useful when you de

hxj413977035的博客 2531

评估篇| 大模型评测综述

1、基于人类的评测。与针对基础大语言模型的自动化评测不同,微调大语言模型的评测更加注重模型在实际应用场景中的表现,如与人类交互的自然度、对齐度等。2、基于模型的评测。例如,AlpacaEval 排行榜 基于由大语言模型合成的人类需求指令作为评测任务,然后收集待评估大模型的回应,并采用 GPT-4 等大语言模型作为评测员,将待评估大语言模型的输出与参考输出进行成对比较。1、大语言模型对评估设置极为敏感,包括问题的表述方式、提示样本的选择以及答案的解析策略等,这些细微的差别都可能导致评估结果的显著变化。

youbingchen的博客 636

深度学习工具包 DeepLearningToolbox

深度学习工具包Deprecation notice.-----This toolbox is outdated and no longer maintained.There are much better tools available for deep learning than this toolbox, e.g. [Theano](http://deeplearning.net/software/theano/), [torch](http://torch.ch/) or [tensorflow](http://www.tensorflow.org/)I would suggest you use one of the tools mentioned above rather than use this toolbox.Best, Rasmus.DeepLearnToolbox================A Matlab toolbox for Deep Learning.Deep Learning is a new subfield of machine learning that focuses on learning deep hierarchical models of data.It is inspired by the human brain's apparent deep (layered, hierarchical) architecture.A good overview of the theory of Deep Learning theory is[Learning Deep Architectures for AI](http://www.iro.umontreal.ca/~bengioy/papers/ftml_book.pdf)For a more informal introduction, see the following videos by Geoffrey Hinton and Andrew Ng.* [The Next Generation of Neural Networks](http://www.youtube.com/watch?v=AyzOUbkUf3M) (Hinton, 2007)* [Recent Developments in Deep Learning](http://www.youtube.com/watch?v=VdIURAu1-aU) (Hinton, 2010)* [Unsupervised Feature Learning and Deep Learning](http://www.youtube.com/watch?v=ZmNOAtZIgIk) (Ng, 2011)If you use this toolbox in your research please cite [Prediction as a candidate for learning deep hierarchical models of data](http://www2.imm.dtu.dk/pubdb/views/publication_details.php?id=6284)```@MASTERSTHESIS\{IMM2012-06284, author = "R. B. Palm", title = "Prediction as a candidate for learning deep hierarchical models of data", year = "2012",}```Contact: rasmusbergpalm at gmail dot comDirectories included in the toolbox-----------------------------------`NN/` - A library for Feedforward Backpropagation Neural Networks`CNN/` - A library for Convolutional Neural Networks`DBN/` - A library for Deep Belief Networks`SAE/` - A library for Stacked Auto-Encoders`CAE/` - A library for Convolutional Auto-Encoders`util/` - Utility functions used by the libraries`data/` - Data used by the examples`tests/` - unit tests to verify toolbox is workingFor references on each library check REFS.mdSetup-----1. Download.2. addpath(genpath('DeepLearnToolbox'));Example: Deep Belief Network---------------------```matlabfunction test_example_DBNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit RBM and visualize its weightsrand('state',0)dbn.sizes = [100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);figure; visualize(dbn.rbm{1}.W'); % Visualize the RBM weights%% ex2 train a 100-100 hidden unit DBN and use its weights to initialize a NNrand('state',0)%train dbndbn.sizes = [100 100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);%unfold dbn to nnnn = dbnunfoldtonn(dbn, 10);nn.activation_function = 'sigm';%train nnopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.10, 'Too big error');```Example: Stacked Auto-Encoders---------------------```matlabfunction test_example_SAEload mnist_uint8;train_x = double(train_x)/255;test_x = double(test_x)/255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit SDAE and use it to initialize a FFNN% Setup and train a stacked denoising autoencoder (SDAE)rand('state',0)sae = saesetup([784 100]);sae.ae{1}.activation_function = 'sigm';sae.ae{1}.learningRate = 1;sae.ae{1}.inputZeroMaskedFraction = 0.5;opts.numepochs = 1;opts.batchsize = 100;sae = saetrain(sae, train_x, opts);visualize(sae.ae{1}.W{1}(:,2:end)')% Use the SDAE to initialize a FFNNnn = nnsetup([784 100 10]);nn.activation_function = 'sigm';nn.learningRate = 1;nn.W{1} = sae.ae{1}.W{1};% Train the FFNNopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.16, 'Too big error');```Example: Convolutional Neural Nets---------------------```matlabfunction test_example_CNNload mnist_uint8;train_x = double(reshape(train_x',28,28,60000))/255;test_x = double(reshape(test_x',28,28,10000))/255;train_y = double(train_y');test_y = double(test_y');%% ex1 Train a 6c-2s-12c-2s Convolutional neural network %will run 1 epoch in about 200 second and get around 11% error. %With 100 epochs you'll get around 1.2% errorrand('state',0)cnn.layers = { struct('type', 'i') %input layer struct('type', 'c', 'outputmaps', 6, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %sub sampling layer struct('type', 'c', 'outputmaps', 12, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %subsampling layer};cnn = cnnsetup(cnn, train_x, train_y);opts.alpha = 1;opts.batchsize = 50;opts.numepochs = 1;cnn = cnntrain(cnn, train_x, train_y, opts);[er, bad] = cnntest(cnn, test_x, test_y);%plot mean squared errorfigure; plot(cnn.rL);assert(er<0.12, 'Too big error');```Example: Neural Networks---------------------```matlabfunction test_example_NNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);% normalize[train_x, mu, sigma] = zscore(train_x);test_x = normalize(test_x, mu, sigma);%% ex1 vanilla neural netrand('state',0)nn = nnsetup([784 100 10]);opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samples[nn, L] = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.08, 'Too big error');%% ex2 neural net with L2 weight decayrand('state',0)nn = nnsetup([784 100 10]);nn.weightPenaltyL2 = 1e-4; % L2 weight decayopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex3 neural net with dropoutrand('state',0)nn = nnsetup([784 100 10]);nn.dropoutFraction = 0.5; % Dropout fraction opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex4 neural net with sigmoid activation functionrand('state',0)nn = nnsetup([784 100 10]);nn.activation_function = 'sigm'; % Sigmoid activation functionnn.learningRate = 1; % Sigm require a lower learning rateopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex5 plotting functionalityrand('state',0)nn = nnsetup([784 20 10]);opts.numepochs = 5; % Number of full sweeps through datann.output = 'softmax'; % use softmax outputopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex6 neural net with sigmoid activation and plotting of validation and training error% split training data into training and validation datavx = train_x(1:10000,:);tx = train_x(10001:end,:);vy = train_y(1:10000,:);ty = train_y(10001:end,:);rand('state',0)nn = nnsetup([784 20 10]); nn.output = 'softmax'; % use softmax outputopts.numepochs = 5; % Number of full sweeps through dataopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, tx, ty, opts, vx, vy); % nntrain takes validation set as last two arguments (optionally)[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');```[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/rasmusbergpalm/deeplearntoolbox/trend.png)](https://bitdeli.com/free "Bitdeli Badge")

Netty的编解码器

Netty编解码器

m0_37529488的博客 1581

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

Netty如何解决粘包以及拆包问题

客户端与服务端进行`TCP`网络通信时,在发送以及读取数据时可能会出现粘包以及拆包问题,那么作为高性能网络框架的`Netty`是如何解决粘包以及拆包问题的呢?我们一起来探讨下这个问题。

4449

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

03.Netty进阶之长度域解码器

长度域解码器(LengthFieldBasedFrameDecoder)是解决 TCP 拆包/粘包问题最常用的。它基本上可以覆盖大部分基于长度拆包场景,开源消息中间件 RocketMQ 就是使用长度域解码器进行解码的。相比和要复杂一些,接下来我们就一起学习下这个强大的解码器。

weixin_38598961的博客 1826

Netty 解码器深入分析:调用时机与继承关系

定义:解码器是 Netty 中用来从 ByteBuf 中提取消息内容的组件。作用:将字节流转化为 Java 对象,以供业务层处理。常见解码器:LengthFieldBasedFrameDecoder、LineBasedFrameDecoder、DelimiterBasedFrameDecoder等。类定义:LineBasedFrameDecoder是一个基于行分隔符的帧解码器,通常用于处理类似HTTP、IRC 等协议。构造方法:maxLength:设置消息的最大长度,用于防止无限制读取数据。

m0_71749752的博客 1129

Netty如何处理TCP粘包/拆包问题?

Netty 提供了多种解码器来处理 TCP 粘包和拆包问题,选择合适的解码器取决于你的应用场景和消息格式。:适用于消息带有长度字段的情况。:适用于消息以特定定界符结尾的情况。:适用于消息长度固定的情况。通过合理选择和配置这些解码器,可以有效地解决 TCP 粘包和拆包问题。

qq_33240556的博客 710

Netty中粘包拆包问题解决探讨

介绍了Netty中粘包拆包问题,并通过代码案例,探讨对粘包拆包问题的解决

峰爸的博客 711
上一篇: netty粘包拆包之DelimiterBasedFrameDecoder解码器
下一篇: netty ByteOrder大小端
wenyixicodedog
博客等级 码龄8年 87粉丝 116原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值