Netty文件传输出现IndexOutOfBoundsException异常求助
Hey there, let's break down why you're hitting that IndexOutOfBoundsException when running without delays under high load—it all boils down to Netty's partial packet handling (often called "half-packets") and your PacketDecoder not accounting for incomplete data arriving in chunks.
The Root Cause
When you add a 1-second delay, each 64KB FileChunk has enough time to arrive as a complete unit at the receiver. But under high load, TCP splits large payloads into smaller segments, so Netty's ByteToMessageDecoder gets called with only part of your full packet. Your original decoder tries to read the full length and byte array immediately, even when the data isn't fully available, triggering the index overflow.
Fix 1: Update PacketDecoder to Handle Half-Packets
You need to check if the incoming ByteBuf has enough readable bytes before attempting to parse each part of the packet. Use markReaderIndex() and resetReaderIndex() to preserve the current position if data is incomplete, so Netty can resume parsing when more bytes arrive.
Here's the corrected decoder:
public class PacketDecoder extends ByteToMessageDecoder { @Override protected void decode(ChannelHandlerContext ctx, ByteBuf buf, List<Object> output) throws Exception { // First, ensure we can read the 4-byte packet type if (buf.readableBytes() < 4) { return; // Not enough data yet—wait for more } // Mark current position to reset if we don't have full data buf.markReaderIndex(); int type = buf.readInt(); switch (type) { case 0: // String payload // Check if we can read the 4-byte string length if (buf.readableBytes() < 4) { buf.resetReaderIndex(); return; } int strLength = buf.readInt(); // Check if we have the full string bytes if (buf.readableBytes() < strLength) { buf.resetReaderIndex(); return; } byte[] strBuffer = new byte[strLength]; buf.readBytes(strBuffer); output.add(new String(strBuffer)); break; case 1: // FileChunk payload // Check if we can read the 4-byte chunk index if (buf.readableBytes() < 4) { buf.resetReaderIndex(); return; } int chunkIndex = buf.readInt(); // Check if we can read the 4-byte chunk length if (buf.readableBytes() < 4) { buf.resetReaderIndex(); return; } int chunkLength = buf.readInt(); // Check if we have the full chunk data if (buf.readableBytes() < chunkLength) { buf.resetReaderIndex(); return; } byte[] chunkBuffer = new byte[chunkLength]; buf.readBytes(chunkBuffer); output.add(new FileChunk(chunkBuffer, chunkIndex)); break; default: System.out.println("Unknown packet type received."); buf.resetReaderIndex(); buf.skipBytes(buf.readableBytes()); // Skip invalid data to avoid blocking break; } } }
Fix 2: Hardening the FileTransfer Sync Logic
Your current wait()/synchronized setup works, but we can make it safer by using a dedicated lock object and adding a listener to confirm the chunk was sent successfully before waiting:
public class FileTransfer implements Runnable { private FileSlicer slicer; private Client client; private final Object transferLock = new Object(); // Dedicated lock to avoid this-lock conflicts public FileTransfer(FileSlicer slicer, Client client) { this.slicer = slicer; this.client = client; } public void run() { synchronized(transferLock) { while(slicer.hasNext()) { try { client.getContext().writeAndFlush(slicer.getNextSlice()) .addListener(future -> { // Wake up if send fails to avoid permanent blocking if (!future.isSuccess()) { synchronized(transferLock) { transferLock.notify(); } future.cause().printStackTrace(); } }); transferLock.wait(); // Wait for receiver's success ack } catch (InterruptedException e) { Thread.currentThread().interrupt(); e.printStackTrace(); } } } } // Call this from your ServerHandler when a chunk is saved successfully public void triggerNextChunk() { synchronized(transferLock) { transferLock.notify(); } } }
Bonus Optimizations
- Avoid
ByteBuf.array(): This method throws errors for pooled or direct ByteBufs. Usebuf.readBytes(byte[] dst)instead (as shown in the corrected decoder) to safely extract bytes. - Use adaptive buffer allocation: Replace
FixedRecvByteBufAllocatorwithAdaptiveRecvByteBufAllocatorto let Netty automatically adjust buffer sizes for varying load:channel.config().setRecvByteBufAllocator(AdaptiveRecvByteBufAllocator.DEFAULT);
内容的提问来源于stack exchange,提问作者Felix Gaebler

