一、netty黏包半包问题处理
由于tcp消息都是byte流,无界的,netty 从入口AbstractNioByteChannel接收到消息,传递到channelHandleInbound到的Bytebuf可能不是完整的。比如:客户端发送2次"hello,world",netty服务端接收到的可能分别是"he","llo,wor","ld"。这时我们需要继承netty的ByteToMessageDecoder类实现黏包半包处理,核心源码如下。
@Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { if (msg instanceof ByteBuf) { CodecOutputList out = CodecOutputList.newInstance(); try { ByteBuf data = (ByteBuf) msg; first = cumulation == null; if (first) { cumulation = data; } else {
//ByteBuf累积器,上次接收长度不够的且未处理的累积在这次一起处理 cumulation = cumulator.cumulate(ctx.alloc(), cumulation, data); } callDecode(ctx, cumulation, out); //具体解码逻辑 } catch (DecoderException e) { throw e; } catch (Exception e) { throw new DecoderException(e); } finally { if (cumulation != null && !cumulation.isReadable()) { numReads = 0; cumulation.release(); cumulation = null; } else if (++ numReads >= discardAfterReads) { // We did enough reads already try to discard some bytes so we not risk to see a OOME. // See https://github.com/netty/netty/issues/4275 numReads = 0; discardSomeReadBytes(); } int size = out.size(); firedChannelRead |= out.insertSinceRecycled(); fireChannelRead(ctx, out, size); out.recycle(); } } else { ctx.fireChannelRead(msg); } }
ReplayingDecoder扩展了ByteToMessageDecoder,处理起来更方便,使用这个类,我们不必调用 readableBytes() 方法。参数 S 指定了用户状态管理的类型,其中 Void 代表不需要状态管理
ReplayingDecoder 使用方便,但它也有一些局限性
- 并不是所有的 ByteBuf 操作都被支持,如果调用了一个不被支持的方法,将会抛出一个 UnsupportedOperationException
- ReplayingDecoder 在某些情况下可能稍慢于 ByteToMessageDecoder,例如网络缓慢并且消息格式复杂时,消息会被拆成了多个碎片,速度变慢
ReplayingDecoder内部原理是内部使用了专门的ReplayingDecoderByteBuf,当buf里数据不够时会抛出一类特殊的错误Signal,然后ReplayingDecoder会重置readerIndex并且再次调用decode方法,如下ReplayingDecoderByteBuf的相应get方法
public int getInt(int index) { checkIndex(index, 4); //可读字节数长度小于4则抛出error,replayingDecoder会进行相应重置readIndex处理 return buffer.getInt(index); } private void checkIndex(int index, int length) { if (index + length > buffer.writerIndex()) { throw REPLAY; } }
LengthFieldBasedFrameDecoder:长度解码器,可实现头部定义长度粘包半包处理,该解码器解码完后返回一个未移动readIndex的ByteBuf(通过slice一个新的ByteBuf),后续handler可以透明感知ByteBuf,源码如下:
protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception { if (discardingTooLongFrame) { discardingTooLongFrame(in); } if (in.readableBytes() < lengthFieldEndOffset) { return null; } int actualLengthFieldOffset = in.readerIndex() + lengthFieldOffset; long frameLength = getUnadjustedFrameLength(in, actualLengthFieldOffset, lengthFieldLength, byteOrder); if (frameLength < 0) { failOnNegativeLengthField(in, frameLength, lengthFieldEndOffset); } frameLength += lengthAdjustment + lengthFieldEndOffset; //完整报文的字节长度数 if (frameLength < lengthFieldEndOffset) { failOnFrameLengthLessThanLengthFieldEndOffset(in, frameLength, lengthFieldEndOffset); } if (frameLength > maxFrameLength) { exceededFrameLength(in, frameLength); return null; } // never overflows because it's less than maxFrameLength int frameLengthInt = (int) frameLength; if (in.readableBytes() < frameLengthInt) { //本次读取ByteBuf不够总长度的,跳过,累积器cumulation会自动叠加上次未处理的 return null; } if (initialBytesToStrip > frameLengthInt) { failOnFrameLengthLessThanInitialBytesToStrip(in, frameLength, initialBytesToStrip); } in.skipBytes(initialBytesToStrip); // extract frame int readerIndex = in.readerIndex(); int actualFrameLength = frameLengthInt - initialBytesToStrip; ByteBuf frame = extractFrame(ctx, in, readerIndex, actualFrameLength); //slice零拷贝一个ByteBuf交给后续handler处理 in.readerIndex(readerIndex + actualFrameLength); //将原有ByteBuf重置readIndex return frame; }