一、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;
}