Netty学习(四)—LengthFieldBasedFrameDecoder解码器

本文介绍了Netty中LengthFieldBasedFrameDecoder解码器的使用,配合LengthFieldPrepender编码器解决TCP粘包和拆包问题。内容包括解码器的工作原理、构造参数解释以及在解码过程中的关键步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Netty学习(四)—LengthFieldBasedFrameDecoder解码器

LengthFieldBasedFrameDecoder和LengthFieldPrepender组合是解决TCP粘包和拆包问题的最佳方案,通过将消息分为消息头和消息体记录消息长度解决读半包问题;

个人主页:tuzhenyu’s page
原文地址:Netty学习(四)—LengthFieldBasedFrameDecoder解码器

(0)使用实例

  • 在发送端添加LengthFiledPrepender编码器,为发送的消息添加表示消息长度的消息头
Bootstrap b = new Bootstrap();
b.group(group).channel(NioSocketChannel.class)
        .handler(new ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel socketChannel) throws Exception {
                socketChannel.pipeline().addLast("encoder", new LengthFieldPrepender(4,false));
                socketChannel.pipeline().addLast(new ClientHandler());
            }
        });
public void channelActive(ChannelHandlerContext ctx) throws Exception {
    byte[] req = null;
    ByteBuf buffer = null;
    StringBuilder sb = new StringBuilder();
    for (int i=0;i<100;i++){
        sb.append("abcdefghijklmnopqrstuvwxyz");
    }
    req = sb.toString().getBytes();
    buffer = Unpooled.buffer(req.length);
    buffer.writeBytes(req);
    ctx.writeAndFlush(buffer);
}
  • 在接收端添加LengthFieldBasedFrameDecoder解码器,根据消息头中消息总长度接收消息并去除消息头往下传递消息体数据;
ServerBootstrap b= new ServerBootstrap();
b.group(bossGroup,workerGroup).channel(NioServerSocketChannel.class)
        .option(ChannelOption.SO_BACKLOG,1024)
        .childHandler(new ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel socketChannel) throws Exception {
                socketChannel.pipeline().addLast("decoder", new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE,0,4,0,4));
                socketChannel.pipeline().addLast(new ServerHandler());
            }
        });
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    ByteBuf buf = (ByteBuf)msg;
    byte[] bytes = new byte[buf.readableBytes()];
    buf.readBytes(bytes);
    System.out.println("receive the bytes: "+new String(bytes,"UTF-8"));
}
  • 发送消息的长度远远超过链路MTU,发送过程中肯定会出现TCP拆包问题,但是添加编码解码器后解决了半包问题
receive the bytes: abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnop...

(1)LengthFiledPrepender编码器

  • LengthFiledPrepender编码器继承了MessageToMessageEncoder基础编码器,将传入的消息添加消息头(仅包含消息长度)后写入Socket通道;

  • LengthFiledPrepender的构造器

    • lengthFieldLength表示消息长度的字节数,一般设为4字节

    • lengthIncludesLengthFieldLength表示length长度是否包括lengthFiledLength长度的字节,一般设为false,表示长度仅为消息体长度不包含消息头长度;


public LengthFieldPrepender(int lengthFieldLength, boolean lengthIncludesLengthFieldLength) {
    this(lengthFieldLength, 0, lengthIncludesLengthFieldLength);
}
  • LengthFiledPrepender对传入的消息进行编码,将消息体长度写入到ByteBuf中
protected void encode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) throws Exception {
    int length = msg.readableBytes() + lengthAdjustment;
    if (lengthIncludesLengthFieldLength) {
        length += lengthFieldLength;
    }

    if (length < 0) {
        throw new IllegalArgumentException(
                "Adjusted frame length (" + length + ") is less than zero");
    }

    switch (lengthFieldLength) {
    case 1:
        if (length >= 256) {
            throw new IllegalArgumentException(
                    "length does not fit into a byte: " + length);
        }
        out.add(ctx.alloc().buffer(1).order(byteOrder).writeByte((byte) length));
        break;
    case 2:
        if (length >= 65536) {
            throw new IllegalArgumentException(
                    "length does not fit into a short integer: " + length);
        }
        out.add(ctx.alloc().buffer(2).order(byteOrder).writeShort((short) length));
        break;
    case 3:
        if (length >= 16777216) {
            throw new IllegalArgumentException(
                    "length does not fit into a medium integer: " + length);
        }
        out.add(ctx.alloc().buffer(3).order(byteOrder).writeMedium(length));
        break;
    case 4:
        out.add(ctx.alloc().buffer(4).order(byteOrder).writeInt(length));
        break;
    case 8:
        out.add(ctx.alloc().buffer(8).order(byteOrder).writeLong(length));
        break;
    default:
        throw new Error("should not reach here");
    }
    out.add(msg.retain());
}

(2)LengthFieldBasedFrameDecoder解码器

  • LengthFieldBasedFrameDecoder解码器继承ByteToMessageDecoder基础解码器,解析出消息长度后根据长度接收后续消息数据,并将完整的去除消息头的数据往下传递;

  • LengthFieldBasedFrameDecoder的构造器

    • maxFrameLength:设定包的最大长度,超出包的最大长度netty将会做一些特殊处理;

      - lengthFieldOffset:指的是长度域的偏移量,表示跳过指定长度个字节之后的才是长度域LengthField;

      - lengthFieldLength:记录该帧数据长度的字段本身的长度;

      - initialBytesToStrip:从数据帧中跳过的字节数,表示获取完一个完整的数据包之后,忽略前面的指定的位数个字节,应用解码器拿到的就是不带长度域的数据包;

      - failFast:如果为true,则表示读取到长度域,TA的值的超过maxFrameLength,就抛出一个 TooLongFrameException,而为false表示只有当真正读取完长度域的值表示的字节之后,才会抛出 TooLongFrameException,默认情况下设置为true,建议不要修改,否则可能会造成内存溢出。

public LengthFieldBasedFrameDecoder(
        int maxFrameLength, int lengthFieldOffset, int lengthFieldLength,
        int lengthAdjustment, int initialBytesToStrip, boolean failFast) {
    this(
            ByteOrder.BIG_ENDIAN, maxFrameLength, lengthFieldOffset, lengthFieldLength,
            lengthAdjustment, initialBytesToStrip, failFast);
}
  • LengthFieldBasedFrameDecoder调用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);
    }

    if (in.readableBytes() < lengthFieldEndOffset) {
        return null;
    }

    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);
    }

    if (frameLength > maxFrameLength) {
        long discard = frameLength - in.readableBytes();
        tooLongFrameLength = frameLength;

        if (discard < 0) {
            // buffer contains more bytes then the frameLength so we can discard all now
            in.skipBytes((int) frameLength);
        } else {
            // Enter the discard mode and discard everything received so far.
            discardingTooLongFrame = true;
            bytesToDiscard = discard;
            in.skipBytes(in.readableBytes());
        }
        failIfNecessary(true);
        return null;
    }

    // never overflows because it's less than maxFrameLength
    int frameLengthInt = (int) frameLength;
    if (in.readableBytes() < frameLengthInt) {
        return null;
    }

    if (initialBytesToStrip > frameLengthInt) {
        in.skipBytes(frameLengthInt);
        throw new CorruptedFrameException(
                "Adjusted frame length (" + frameLength + ") is less " +
                "than initialBytesToStrip: " + initialBytesToStrip);
    }
    in.skipBytes(initialBytesToStrip);

    // extract frame
    int readerIndex = in.readerIndex();
    int actualFrameLength = frameLengthInt - initialBytesToStrip;
    ByteBuf frame = extractFrame(ctx, in, readerIndex, actualFrameLength);
    in.readerIndex(readerIndex + actualFrameLength);
    return frame;
}

总结

  • LengthFieldPrepender编码器和LengthFieldBasedFrameDecoder解码器结合通过添加添加消息头并在消息头中定义消息长度解决TCP粘包和拆包问题;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值