Netty:奇怪的 IndexOutOfBoundsException:readerIndex + 长度超过 writerIndex

Netty: Weird IndexOutOfBoundsException: readerIndex + length exceeds writerIndex

我目前正在通过 netty 发送不同的数据包,并且我经常收到这样的异常 在接收它们时:

java.lang.IndexOutOfBoundsException: readerIndex(39) + length(32) exceeds writerIndex(64): UnpooledUnsafeDirectByteBuf(ridx: 39, widx: 64, cap: 2048)
at io.netty.buffer.AbstractByteBuf.checkReadableBytes(AbstractByteBuf.java:1166)
at io.netty.buffer.AbstractByteBuf.readBytes(AbstractByteBuf.java:655) 
at <snip>.PacketDataHolder.readString(PacketDataHolder.java:79)

…… 或

java.lang.IndexOutOfBoundsException: readerIndex(0) + length(1) exceeds writerIndex(0): UnpooledUnsafeDirectByteBuf(ridx: 0, widx: 0, cap: 2048)
at io.netty.buffer.AbstractByteBuf.checkReadableBytes(AbstractByteBuf.java:1166)
at io.netty.buffer.AbstractByteBuf.readByte(AbstractByteBuf.java:570) ......

这是我发送数据包的方式:

public void sendPacket(Packet packet) {
    PacketDataHolder packetDataHolder = new PacketDataHolder(); //I'm creating a packetDataHolder, code is below
    packetDataHolder.writeHeader(packet); //Then i'm adding the packet ID as a header
    packet.writeData(packetDataHolder); //Then I'm writing the data of the packet (String for example)
    socketChannel.writeAndFlush(packetDataHolder.getByteBuf()); //Then I'm sending the packet
}

以及我如何接收和阅读它们:

protected void channelRead0(ChannelHandlerContext ctx, ByteBuf byteBuf) throws Exception {
    PacketDataHolder packetDataHolder = new PacketDataHolder(byteBuf); //First I'm creating a PacketDataHolder containing the bytebuf
    Packet packet = PacketParser.parsePacket(packetDataHolder); //Then I'm parsing the header I wrote (The packet id)

相关代码如下:

(PacketDataHolder class) public synchronized void writeHeader(Packet packet) { this.writeByte(packet.getId()); }

public synchronized byte readHeader() {
    return this.readByte();
}
public synchronized void writeByte(int value) {
    this.byteBuf.writeByte(value);
}

public synchronized byte readByte() {
    return this.byteBuf.readByte();
}
public synchronized void writeString(String value) {
    if(value == null)
        value = "An error occurred while displaying this message";
    byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
    if (bytes.length > 32767)
        throw new EncoderException("String too big (was " + value.length()
                + " bytes encoded, max 32767)");
    else {
        writeVarInt(bytes.length);
        byteBuf.writeBytes(bytes);
    }
}

public synchronized String readString() {
    int length = readVarInt();
    if (length < 0)
        throw new DecoderException(
                "The received encoded string buffer length is less than zero! Weird string!");
    return new String(byteBuf.readBytes(length).array(),
            StandardCharsets.UTF_8);
}

我描述的第一个错误总是发生在 writeString 方法中,所以我认为它与此有关。 我认为主要是在发送很长的字符串时,但我不确定。

如果您需要更多示例代码,请随时询问! 我希望你能帮帮我, 燃烧器

当开始从 ByteBuf 中读取内容时,您是否确保 ByteBuf 中有足够的可读字节。好像你没有。检查 ByteToMessageDecoder 以了解如何在解码器中确保它

您的代码假定您的通信通道正在处理框架。也就是说,它期望当您发送 1024 个字节时,接收处理程序将接收到恰好 1024 个字节的 bytebuf。据我所知,您的代码是直接写入 TCP,这是一种基于流的协议,无法以这种方式工作。如果您写入 1024 个字节,对等方将收到所有 1024 个字节,但字节可能会分段到达 - 例如两次调用 channelRead0(第一个为 300,第二个为 724)。

幸运的是,Netty 包含几个 out-of-the-box 帧编解码器来为您处理这个问题。请参阅 http://netty.io/4.0/xref/io/netty/example/worldclock/WorldClockServerInitializer.html

中的示例

它展示了如何使用 ProtobufVarint32FrameDecoder(确保管道中的下一个处理程序每​​次始终接收到一帧字节)和 ProtobufVarint32LengthFieldPrepender(负责添加帧长度)配置服务器通道 header 所有外发邮件)

我建议您更改代码以将这些(或类似的成帧编解码器)插入到您的管道中。然后将您的应用程序代码修改为 而不是 调用 readVarInt 和 writeVarInt,因为编解码器会为您处理这件事。接收到的 ByteBuf 的大小将始终与发送的 ByteBuf 的大小匹配,因为解码器负责聚合任何片段。