netty源码分析(六):netty解码

89 阅读5分钟

一.解码概述

数据在网络中传输是二进制字节流,数据到达服务端后,需要将二进制字节流转换为java对象,或者对数据进行切分,防止拆包/粘包现象,而处理这些功能点就需要netty的解码器

如果使用netty的解码器? netty解码器也是一个channelHandler,只需要加入channel中即可,举例如下:

 pipeline.addLast("encode", new FixedLengthFrameDecoder(6));

二.抽象解码器ByteToMessageDecoder

解码器的抽象父类是ByteToMessageDecoder,当消息通过网络到达channel时候,会调用channelRead()方法,通过pipline调用到解码器,代码如下

public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    if (msg instanceof ByteBuf) {
        CodecOutputList out = CodecOutputList.newInstance();
        try {
            ByteBuf data = (ByteBuf) msg;
            //cumulation 累加器
            first = cumulation == null;
            if (first) {
               // 数据第一次进来,累加器等于数据
                cumulation = data;
            } else {
              // 非第一次进来,累加器会把当前数据添加到累加器中
                cumulation = cumulator.cumulate(ctx.alloc(), cumulation, data);
            }
            // 调用decode方法,进行解码,把解码后的数据放在out中
            callDecode(ctx, cumulation, out);
        } catch (DecoderException e) {
            throw e;
        } catch (Throwable t) {
            throw new DecoderException(t);
        } finally {
           // 累加器的数据读取后,判断累加器是否还有数据
            if (cumulation != null && !cumulation.isReadable()) {
                numReads = 0;
                //没有数据,就回收累加器,重新赋值累加器为null
                cumulation.release();
                cumulation = null;
            } else if (++ numReads >= discardAfterReads) {
                numReads = 0;
                discardSomeReadBytes();
            }

            int size = out.size();
            decodeWasNull = !out.insertSinceRecycled();
            fireChannelRead(ctx, out, size);
            out.recycle();
        }
    } else {
        ctx.fireChannelRead(msg);
    }
}

紧着进入

protected void callDecode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
    try {
       //1. 如果累加器中有数据,就一直循环
        while (in.isReadable()) {
            int outSize = out.size();
            if (outSize > 0) {
             //2.如果outsize里面有解析后的对象,就直接往后面触发channelRead()方法
                fireChannelRead(ctx, out, outSize);
                out.clear();
                if (ctx.isRemoved()) {
                    break;
                }
                outSize = 0;
            }
            int oldInputLength = in.readableBytes();
            //进入子类方法,完成解析从左
            decode(ctx, in, out);
            if (ctx.isRemoved()) {
                break;
            }
            // 如果outsize数量没有变化
            if (outSize == out.size()) {
            // byteBuf里面数据和旧的数据一样,说明这一次没有读取,直接跳出循环
                if (oldInputLength == in.readableBytes()) {
                    break;
                } else {
                    continue;
                }
            }

            if (oldInputLength == in.readableBytes()) {
                throw new DecoderException(
                        StringUtil.simpleClassName(getClass()) +
                        ".decode() did not read anything but decoded a message.");
            }

            if (isSingleDecode()) {
                break;
            }
        }
    } catch (DecoderException e) {
        throw e;
    } catch (Throwable cause) {
        throw new DecoderException(cause);
    }
}

核心方法 decode(ctx, in, out);

  • 使用模板设计模式,父类中定义,到具体的子类去实现

三.几种常见的解码器分析

由上面可知,解码器的抽象父类中核心方法decode(ctx, in, out),需要不同的子类去实现,下面我们来看看常用的几种解码器

1.固定长度解码器

  // 构造方法数字代表,长度大小
  pipeline.addLast("encode", new FixedLengthFrameDecoder(6));

看他的decode方法

@Override
protected final void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
    Object decoded = decode(ctx, in);
    if (decoded != null) {
        out.add(decoded);
    }
}

继续进入

protected Object decode( ChannelHandlerContext ctx, ByteBuf in) throws Exception {
    //判断byteBuf可读长度是否小于"数据报的长度"
    if (in.readableBytes() < frameLength) {
        return null;
    } else {
        // 不小于,直接读取frameLength的数据
        return in.readRetainedSlice(frameLength);
    }
}

2.行分隔符解码器

pipelie.addLast("encode", new LineBasedFrameDecoder(8));

看他的decoce方法

@Override
protected final void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
    Object decoded = decode(ctx, in);
    if (decoded != null) {
        out.add(decoded);
    }
}

继续进入

protected Object decode(ChannelHandlerContext ctx, ByteBuf buffer) throws Exception {
   //1.找到分隔符的索引(判断分隔符是\r\n还是\n)
    final int eol = findEndOfLine(buffer);
    if (!discarding) {
        if (eol >= 0) {
            final ByteBuf frame;
            final int length = eol - buffer.readerIndex();
            final int delimLength = buffer.getByte(eol) == '\r'? 2 : 1;
            //如果数据报长度大于最大长度,触发fail方法
            if (length > maxLength) {
                buffer.readerIndex(eol + delimLength);
                fail(ctx, length);
                return null;
            }
            // 截取到第一个换行符前的数据,并跳过换行符的长度
            if (stripDelimiter) {
                frame = buffer.readRetainedSlice(length);
                buffer.skipBytes(delimLength);
            } else {
                frame = buffer.readRetainedSlice(length + delimLength);
            }

            return frame;
        } else {
            final int length = buffer.readableBytes();
            if (length > maxLength) {
                discardedBytes = length;
                buffer.readerIndex(buffer.writerIndex());
                discarding = true;
                if (failFast) {
                    fail(ctx, "over " + discardedBytes);
                }
            }
            return null;
        }
    } else {
        if (eol >= 0) {
            final int length = discardedBytes + eol - buffer.readerIndex();
            final int delimLength = buffer.getByte(eol) == '\r'? 2 : 1;
            buffer.readerIndex(eol + delimLength);
            discardedBytes = 0;
            discarding = false;
            if (!failFast) {
                fail(ctx, length);
            }
        } else {
            discardedBytes += buffer.readableBytes();
            buffer.readerIndex(buffer.writerIndex());
        }
        return null;
    }
}

上面代码,主要逻辑是

  • 找到第一个换行符的索引
  • 判断长度是否大于最大长度,如果大于,就传播异常事件
  • 截取第一个换行符前的数据,并跳过换行符的长度
  • 把截取的带有回车符的数据返回,存入outlist中,往下传递

3.动态根据长度字段值的解码器

pipeline.addLast("encode", new LengthFieldBasedFrameDecoder(100,1,2,0,0));

image.png 这个类上面举例很多例子,主要就是有一个字段记录该数据的长度,所以需要传递

  • 1.lengthFieldOffset:长度字段的偏移量
  • 2.lengthFieldLength:长度字段自己占据的长度
  • 3.lengthAdjustment:从该长度字段到真正数据之间的调整量
  • 4.initialBytesToStrip:截取数据时候,从哪里开始 这个类的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;
}

这里比较复杂了,核心就是通过前面设置的四个值,去bytebuf中取值,兄弟们,可以自行debug一下,就能很清晰了

四.总结

解码器作为一个channelHandler添加到pipline中,当数据到达服务端时候,会触发channel中pipline的inbount类型handler的channelRead方法,在解码器中,会

  • 1.累加字节流
  • 2.调用子类decode方法,更具不同的解码规则进行解析
  • 3.把解析后的对象放在out中,向下传递

五.累加器的作用

累加器是为了处理netty的拆包和粘包事件,当发生拆包/粘包事件的时候,一个butyBuf中可以包含半个/一个/多个报文的数据,这个时候,就把读取到的数据都追加到累加器中,在decod方法中,会进行判断,如果当前数据不够组成一个完整的数据包,说明数据没有全部到来,前一部分数据不会被处理,继续存在累加器中,等后一部分数据到达后,继续存入累加器中,再次对数据进行处理,从而得到完整的数据包