netty源码分析之FrameDecoder(LengthFieldBasedFrameDecoder)

转载自: https://blog.csdn.net/jbgtwang/article/details/30223733

        我们接下来看一个也是比较重要的的解码器LengthFieldBasedFrameDecoder,这个和DelimiterBasedFrameDecoder比起来没有那么难理解,所以我们简单的看一下。

       和之前一样,我们先来看一下局部变量。

Java代码   收藏代码

  1. private final int maxFrameLength;  
  2. private final int lengthFieldOffset;  
  3. private final int lengthFieldLength;  
  4. private final int lengthFieldEndOffset;  
  5. private final int lengthAdjustment;  
  6. private final int initialBytesToStrip;  
  7. private final boolean failFast;  
  8. private boolean discardingTooLongFrame;  
  9. private long tooLongFrameLength;  
  10. private long bytesToDiscard;  

  • maxFrameLength 这个定义最大帧的长度
  • lengthFieldOffset 长度属性的起始指针(偏移量)
  • lengthFieldLength 长度属性的长度,即存放数据包长度的变量的的字节所占的长度
  • lengthFieldEndOffset 这个是一个快捷属性,是根据lengthFieldOffset和lengthFieldLength计算出来的,即就是起始偏移量+长度=结束偏移量
  • lengthAdjustment 这个是一个长度调节值,例如当总长包含头部信息的时候,这个可以是个负数,就比较好实现了
  • initialBytesToStrip 这个属性也比较好理解,就是解码后的数据包需要跳过的头部信息的字节数
  • failFast 这个和DelimiterBasedFrameDecoder是一致的,就是如果设置成true,当发现解析的数据超过maxFrameLenght就立马报错,否则当整个帧的数据解析完后才报错
  • discardingTooLongFrame 这个也是一个导出属性,就是当前编码器的状态,是不是处于丢弃超长帧的状态
  • tooLongFrameLength 这个是当出现超长帧的时候,这个超长帧的长度
  • bytesToDiscard 这个来定义,当出现超长帧的时候,丢弃的数据的字节数

  接下来我们就深入主题,来看这个类的实现,最后我们再分析下javadoc,这样我们就能够彻底的掌握这个编码器了。

      

Java代码   收藏代码

  1. if (discardingTooLongFrame) {  
  2.            long bytesToDiscard = this.bytesToDiscard;  
  3.            int localBytesToDiscard = (int) Math.min(bytesToDiscard, buffer.readableBytes());  
  4.            buffer.skipBytes(localBytesToDiscard);  
  5.            bytesToDiscard -= localBytesToDiscard;  
  6.            this.bytesToDiscard = bytesToDiscard;  
  7.            failIfNecessary(ctx, false);  
  8.            return null;  
  9.        }  
  10.   
  11.        if (buffer.readableBytes() < lengthFieldEndOffset) {  
  12.            return null;  
  13.        }  

        这个逻辑是判断,如果当前的编码器处于丢弃超长帧的状态,这个状态肯定是上传编码的时候被设置成这个状态的,然后取上次丢弃的字节数和当前buffer里面的可读数据的最小值,后面的程序理解起来不是那么直接,这个bytesToDiscard属性主要是在后面设置的,bytesToDiscard = frameLength - buffer.readableBytes(); frameLength是当前超长帧的字节数,buffer.readableBytes是当前buffer里面的数据的字节数,所以它的含义就是告诉编码器下次还要丢弃的字节数,举个例子,最大帧的长度是1000,当前帧的长度是1024,所以超过了最大帧了,比如当前buffer里面长度是18个字节,所以bytesToDiscard就是1024-18 = 1006,含义就是说下次解码的时候还要丢弃1006个字节,那上面的代码就比较好理解了,就是说在计算下一次编码需要丢弃的字节数。后面是就是调用failIfNecessary函数了。

       我们先来看一下它的实现吧:

      

Java代码   收藏代码

  1. private void failIfNecessary(ChannelHandlerContext ctx, boolean firstDetectionOfTooLongFrame) {  
  2.        if (bytesToDiscard == 0) {  
  3.            // Reset to the initial state and tell the handlers that  
  4.            // the frame was too large.  
  5.            long tooLongFrameLength = this.tooLongFrameLength;  
  6.            this.tooLongFrameLength = 0;  
  7.            discardingTooLongFrame = false;  
  8.            if ((!failFast) ||  
  9.                (failFast && firstDetectionOfTooLongFrame))  
  10.            {  
  11.                fail(ctx, tooLongFrameLength);  
  12.            }  
  13.        } else {  
  14.            // Keep discarding and notify handlers if necessary.  
  15.            if (failFast && firstDetectionOfTooLongFrame)  
  16.            {  
  17.                fail(ctx, this.tooLongFrameLength);  
  18.            }  
  19.        }  
  20.   
  21.    }  

        如果bytesToDiscard是0,就是说下次编码的时候不需要丢弃了,说明这个超长帧读取完毕,那么将这个编码器的状态设置为非丢弃超长帧状态,如果不是failFast状态或者是failFast又是第一次出现超长帧,就报错,意思其实挺明白,如果不是failFast状态,在这个超长帧读取完毕后理应抛出异常,如果是failFast是第一次发现了超长帧,所以也要抛出异常。else分支很好理解,就是说发现了超长帧,并且客户配置的是立马抛出异常,我们就直接抛出异常即可。

      我们接着来看decode的代码实现:

           

Java代码   收藏代码

  1. if (buffer.readableBytes() < lengthFieldEndOffset) {  
  2.           return null;  
  3.       }  
  4.   
  5.       int actualLengthFieldOffset = buffer.readerIndex() + lengthFieldOffset;  
  6.       long frameLength;  
  7.       switch (lengthFieldLength) {  
  8.       case 1:  
  9.           frameLength = buffer.getUnsignedByte(actualLengthFieldOffset);  
  10.           break;  
  11.       case 2:  
  12.           frameLength = buffer.getUnsignedShort(actualLengthFieldOffset);  
  13.           break;  
  14.       case 3:  
  15.           frameLength = buffer.getUnsignedMedium(actualLengthFieldOffset);  
  16.           break;  
  17.       case 4:  
  18.           frameLength = buffer.getUnsignedInt(actualLengthFieldOffset);  
  19.           break;  
  20.       case 8:  
  21.           frameLength = buffer.getLong(actualLengthFieldOffset);  
  22.           break;  
  23.       default:  
  24.           throw new Error(“should not reach here”);  
  25.       }  

       这段代码结构性很好,也很好理解,如果发现可读的字节数不够,就返回null,等待下次messageReceived通知,然后根据length的长度进行读操作。

      我们接着超下看:

Java代码   收藏代码

  1. if (frameLength < 0) {  
  2.             buffer.skipBytes(lengthFieldEndOffset);  
  3.             throw new CorruptedFrameException(  
  4.                     ”negative pre-adjustment length field: ” + frameLength);  
  5.         }  
  6.   
  7.         frameLength += lengthAdjustment + lengthFieldEndOffset;  
  8.         if (frameLength < lengthFieldEndOffset) {  
  9.             buffer.skipBytes(lengthFieldEndOffset);  
  10.             throw new CorruptedFrameException(  
  11.                     ”Adjusted frame length (“ + frameLength + “) is less ” +  
  12.                     ”than lengthFieldEndOffset: ” + lengthFieldEndOffset);  
  13.         }  


  •  如果我们发现读到的长度是负值,我们将read指针重新设置成读之前的位置,并抛出异常。
  • 计算帧的长度,这个里面是lengthAdjustment + lengthFieldEndOffset,这个比较好理解,这个里面同样做了判断,就是当前帧的长度,不能比lenghtFieldEndOffset小,含义就是帧的数据内容可能为0,但是不可能为负
Java代码   收藏代码

  1. if (frameLength > maxFrameLength) {  
  2.            // Enter the discard mode and discard everything received so far.  
  3.            discardingTooLongFrame = true;  
  4.            tooLongFrameLength = frameLength;  
  5.            bytesToDiscard = frameLength - buffer.readableBytes();  
  6.            buffer.skipBytes(buffer.readableBytes());  
  7.            failIfNecessary(ctx, true);  
  8.            return null;  
  9.        }  
  10.   
  11.        // never overflows because it’s less than maxFrameLength  
  12.        int frameLengthInt = (int) frameLength;  
  13.        if (buffer.readableBytes() < frameLengthInt) {  
  14.            return null;  
  15.        }  

        这个代码就是在处理超长帧的问题,这个在上面我们已经进行解析了

  • 如果发现当前帧的长度超过超长帧的定义,我们将当前的解码器标记为丢弃超长帧状态
  • 计算下一次编码还需要丢弃的长度
  • 修改buffer的读指针
  • 调用failIfNecessary这个函数,判断是否需要抛出异常,关于failIfNecessary函数我们上面已经讲了
  • 然后返回null标记此次解码失败,等待下次messageReceived通知
  • 然后做一次判断,看当前可读数据是不是够一个int型的字节,因为现在马上要读取数据帧的长度了

Java代码   收藏代码

  1. if (initialBytesToStrip > frameLengthInt) {  
  2.           buffer.skipBytes(frameLengthInt);  
  3.           throw new CorruptedFrameException(  
  4.                   ”Adjusted frame length (“ + frameLength + “) is less ” +  
  5.                   ”than initialBytesToStrip: ” + initialBytesToStrip);  
  6.       }  
  7.       buffer.skipBytes(initialBytesToStrip);  
  8.   
  9.       // extract frame  
  10.       int readerIndex = buffer.readerIndex();  
  11.       int actualFrameLength = frameLengthInt - initialBytesToStrip;  
  12.       ChannelBuffer frame = extractFrame(buffer, readerIndex, actualFrameLength);  
  13.       buffer.readerIndex(readerIndex + actualFrameLength);  
  14.       return frame;  

         这个是decode的最后的实现了

  • 最开始是做了一个判断,就是起始的跳过字节数的变量太大,也要抛出异常的
  • 计算数据帧的长度,就是数据包头部里面定义的长度减去需要跳过的字节数
  • 最后就是提取数据并修改读指针,最后返回数据帧

          我们最后来看下这个类的javadoc吧,我感觉netty的作者作为和我们同年龄的80后青年,不但代码写的好,注释也写的很好,这种分享精神很值得我们学习。

  •    第一种情况:从lenght从头开始,长度为2,不跳过头部

                  

C代码   收藏代码

  1. lengthFieldOffset   = 0  
  2.  lengthFieldLength   = 2  
  3.  lengthAdjustment    = 0  
  4.  initialBytesToStrip = 0 (= do not strip header)  
  5.   
  6.  BEFORE DECODE (14 bytes)         AFTER DECODE (14 bytes)  
  7.  +——–+—————-+      +——–+—————-+  
  8.  | Length | Actual Content |—–>| Length | Actual Content |  
  9.  | 0x000C | ”HELLO, WORLD” |      | 0x000C | “HELLO, WORLD” |  
  10.  +——–+—————-+      +——–+—————-+  



  •  第二种情况,从length从头开始,长度为2,跳过头部
C代码   收藏代码

  1. lengthFieldOffset   = 0  
  2. lengthFieldLength   = 2  
  3. lengthAdjustment    = 0  
  4. initialBytesToStrip = 2 (= the length of the Length field)  
  5.   
  6. BEFORE DECODE (14 bytes)         AFTER DECODE (12 bytes)  
  7. +——–+—————-+      +—————-+  
  8. | Length | Actual Content |—–>| Actual Content |  
  9. | 0x000C | ”HELLO, WORLD” |      | “HELLO, WORLD” |  
  10. +——–+—————-+      +—————-+  

     这个和上面的区别是,数据经过解码后头部被去掉了

  • 第三种情况,2个字节长度的头部,offset是0,但是长度代表的是整个数据帧的长度,即就是包含头部

Java代码   收藏代码

  1. lengthFieldOffset   =  0  
  2.  lengthFieldLength   =  2  
  3.  lengthAdjustment    = -2 (= the length of the Length field)  
  4.  initialBytesToStrip =  0  
  5.   
  6.  BEFORE DECODE (14 bytes)         AFTER DECODE (14 bytes)  
  7.  +——–+—————-+      +——–+—————-+  
  8.  | Length | Actual Content |—–>| Length | Actual Content |  
  9.  | 0x000E | “HELLO, WORLD” |      | 0x000E | “HELLO, WORLD” |  
  10.  +——–+—————-+      +——–+—————-+  

  • 第四种情况,5个字节长度的头部,但是数据帧的lenght的字节数是3,offset是2,不跳过头部

Java代码   收藏代码

  1. lengthFieldOffset   = 2 (= the length of Header 1)  
  2. lengthFieldLength   = 3  
  3. lengthAdjustment    = 0  
  4. initialBytesToStrip = 0  
  5.   
  6. BEFORE DECODE (17 bytes)                      AFTER DECODE (17 bytes)  
  7. +———-+———-+—————-+      +———-+———-+—————-+  
  8. | Header 1 |  Length  | Actual Content |—–>| Header 1 |  Length  | Actual Content |  
  9. |  0xCAFE  | 0x00000C | “HELLO, WORLD” |      |  0xCAFE  | 0x00000C | “HELLO, WORLD” |  
  10. +———-+———-+—————-+      +———-+———-+—————-+  

  • 第五种情况,5个字节长度的头部,但是数据帧的lenght的字节数是3,offset是0,不跳过头部

Java代码   收藏代码

  1. lengthFieldOffset   = 0  
  2.  lengthFieldLength   = 3  
  3.  lengthAdjustment    = 2 (= the length of Header 1)  
  4.  initialBytesToStrip = 0  
  5.   
  6.  BEFORE DECODE (17 bytes)                      AFTER DECODE (17 bytes)  
  7.  +———-+———-+—————-+      +———-+———-+—————-+  
  8.  |  Length  | Header 1 | Actual Content |—–>|  Length  | Header 1 | Actual Content |  
  9.  | 0x00000C |  0xCAFE  | “HELLO, WORLD” |      | 0x00000C |  0xCAFE  | “HELLO, WORLD” |  
  10.  +———-+———-+—————-+      +———-+———-+—————-+  

    这个稍微解释一下,由于我们的头部是5个字节,但是length数据域只有3个字节,所以lengthAdjustment是2,表明需要2个字节来调整整个帧的长度。

  • 第六种情况,4个字节长度的头部,但是数据帧的lenght的字节数是2,offset是1,跳过头部第一个数据域和长度域

Java代码   收藏代码

  1. lengthFieldOffset   = 1 (= the length of HDR1)  
  2.  lengthFieldLength   = 2  
  3.  lengthAdjustment    = 1 (= the length of HDR2)  
  4.  initialBytesToStrip = 3 (= the length of HDR1 + LEN)  
  5.   
  6.  BEFORE DECODE (16 bytes)                       AFTER DECODE (13 bytes)  
  7.  +——+——–+——+—————-+      +——+—————-+  
  8.  | HDR1 | Length | HDR2 | Actual Content |—–>| HDR2 | Actual Content |  
  9.  | 0xCA | 0x000C | 0xFE | “HELLO, WORLD” |      | 0xFE | “HELLO, WORLD” |  
  10.  +——+——–+——+—————-+      +——+—————-+  

         这个我稍微解释一下,HDR1是一个字节,Lenght是2个字节,HDR2是两个字节,解码后需要包含HDR2,所以lenghtFieldOffset是1,lenghtFieldLenght是2,lenghtAdjustment是1,实际上就是HDR2的长度,initailBytesToStrip是3,实际上就是HDR1+LEN的长度。

  • 第七种情况,4个字节长度的头部,但是数据帧的lenght的字节数是2,offset是1,跳过头部第一个数据域和长度域,消息的长度代表整个数据帧的长度

Java代码   收藏代码

  1. lengthFieldOffset   =  1  
  2.  lengthFieldLength   =  2  
  3.  lengthAdjustment    = -3 (= the length of HDR1 + LEN, negative)  
  4.  initialBytesToStrip =  3  
  5.   
  6.  BEFORE DECODE (16 bytes)                       AFTER DECODE (13 bytes)  
  7.  +——+——–+——+—————-+      +——+—————-+  
  8.  | HDR1 | Length | HDR2 | Actual Content |—–>| HDR2 | Actual Content |  
  9.  | 0xCA | 0x0010 | 0xFE | “HELLO, WORLD” |      | 0xFE | “HELLO, WORLD” |  

   这个我稍微解释一下,HDR1是一个字节,Lenght是2个字节,HDR2是两个字节,解码后需要包含HDR2,所以lenghtFieldOffset是1,lenghtFieldLenght是2,lenghtAdjustment是-3,实际上就是HDR1+LEN的长度,initailBytesToStrip是3,实际上就是HDR1+LEN的长度。

转自: http://asialee.iteye.com/blog/1784844


一些说明:

1.如果是自定义包结构进行数据传输,利用LengthFieldBasedFrameDecoder可以接收到完整数据包,但数据包中的各部分数据要取出来还需自己在messageReceived中处理 (基于netty3.x)

public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)
            throws Exception {
         ChannelBuffer buf = (ChannelBuffer) e.getMessage();

         //根据自定义的包结构进行拆包处理

        //假设前四字节是长度

        int length = buf.readInt();

        //假设后一字节是类型

        byte type = buf.readByte();

        //其它结构…

}


2.回写消息 (基于netty3.x)

byte [] returnMsg = “这是一个回写消息”.getBytes();

ChannelBuffer returnMsgBuffer = ChannelBuffers.buffer(returnMsg.length);
returnMsgBuffer.writeBytes(returnMsg);
ChannelFuture future = ctx.getChannel().write(returnMsgBuffer);//注意ctx 是 ChannelHandlerContext

future.addListener(new ChannelFutureListener(){

            @Override
            public void operationComplete(ChannelFuture future) throws Exception {
                if(future.isSuccess()){

                       System.out.println(“回写消息成功”);

                 }else{

                       future.getChannel().close();

                }
            }
            
        });


3.以上操作在netty4又有些不同,主要是部分类的名称发生了变化和方法发生了变化,但大致流程还是一样的

public void messageReceived(ChannelHandlerContext ctx, , Object msg)
            throws Exception {
         ByteBuf buf= (ByteBuf)msg;

         //根据自定义的包结构进行拆包处理

        //假设前四字节是长度

        int length = buf.readInt();

        //假设后一字节是类型

        byte type = buf.readByte();

        //其它结构…

}


回写消息

byte [] returnMsg = “这是一个回写消息”.getBytes();

ByteBuf returnMsgBuf = ctx.alloc().buffer(returnMsg.length);
returnMsgBuf.writeBytes(returnMsg);
ChannelFuture future = ctx.writeAndFlush(returnMsgBuf).sync();

sync()应该是把异步变成了同步


4.netty3.x中,如果不想使用LengthFieldBasedFrameDecoder,想自定义解码类来实现解码,则可以让自定义类继承FrameDecoder

        我们接下来看一个也是比较重要的的解码器LengthFieldBasedFrameDecoder,这个和DelimiterBasedFrameDecoder比起来没有那么难理解,所以我们简单的看一下。

       和之前一样,我们先来看一下局部变量。

Java代码   收藏代码

  1. private final int maxFrameLength;  
  2. private final int lengthFieldOffset;  
  3. private final int lengthFieldLength;  
  4. private final int lengthFieldEndOffset;  
  5. private final int lengthAdjustment;  
  6. private final int initialBytesToStrip;  
  7. private final boolean failFast;  
  8. private boolean discardingTooLongFrame;  
  9. private long tooLongFrameLength;  
  10. private long bytesToDiscard;  

  • maxFrameLength 这个定义最大帧的长度
  • lengthFieldOffset 长度属性的起始指针(偏移量)
  • lengthFieldLength 长度属性的长度,即存放数据包长度的变量的的字节所占的长度
  • lengthFieldEndOffset 这个是一个快捷属性,是根据lengthFieldOffset和lengthFieldLength计算出来的,即就是起始偏移量+长度=结束偏移量
  • lengthAdjustment 这个是一个长度调节值,例如当总长包含头部信息的时候,这个可以是个负数,就比较好实现了
  • initialBytesToStrip 这个属性也比较好理解,就是解码后的数据包需要跳过的头部信息的字节数
  • failFast 这个和DelimiterBasedFrameDecoder是一致的,就是如果设置成true,当发现解析的数据超过maxFrameLenght就立马报错,否则当整个帧的数据解析完后才报错
  • discardingTooLongFrame 这个也是一个导出属性,就是当前编码器的状态,是不是处于丢弃超长帧的状态
  • tooLongFrameLength 这个是当出现超长帧的时候,这个超长帧的长度
  • bytesToDiscard 这个来定义,当出现超长帧的时候,丢弃的数据的字节数

  接下来我们就深入主题,来看这个类的实现,最后我们再分析下javadoc,这样我们就能够彻底的掌握这个编码器了。

      

Java代码   收藏代码

  1. if (discardingTooLongFrame) {  
  2.            long bytesToDiscard = this.bytesToDiscard;  
  3.            int localBytesToDiscard = (int) Math.min(bytesToDiscard, buffer.readableBytes());  
  4.            buffer.skipBytes(localBytesToDiscard);  
  5.            bytesToDiscard -= localBytesToDiscard;  
  6.            this.bytesToDiscard = bytesToDiscard;  
  7.            failIfNecessary(ctx, false);  
  8.            return null;  
  9.        }  
  10.   
  11.        if (buffer.readableBytes() < lengthFieldEndOffset) {  
  12.            return null;  
  13.        }  

        这个逻辑是判断,如果当前的编码器处于丢弃超长帧的状态,这个状态肯定是上传编码的时候被设置成这个状态的,然后取上次丢弃的字节数和当前buffer里面的可读数据的最小值,后面的程序理解起来不是那么直接,这个bytesToDiscard属性主要是在后面设置的,bytesToDiscard = frameLength - buffer.readableBytes(); frameLength是当前超长帧的字节数,buffer.readableBytes是当前buffer里面的数据的字节数,所以它的含义就是告诉编码器下次还要丢弃的字节数,举个例子,最大帧的长度是1000,当前帧的长度是1024,所以超过了最大帧了,比如当前buffer里面长度是18个字节,所以bytesToDiscard就是1024-18 = 1006,含义就是说下次解码的时候还要丢弃1006个字节,那上面的代码就比较好理解了,就是说在计算下一次编码需要丢弃的字节数。后面是就是调用failIfNecessary函数了。

       我们先来看一下它的实现吧:

      

Java代码   收藏代码

  1. private void failIfNecessary(ChannelHandlerContext ctx, boolean firstDetectionOfTooLongFrame) {  
  2.        if (bytesToDiscard == 0) {  
  3.            // Reset to the initial state and tell the handlers that  
  4.            // the frame was too large.  
  5.            long tooLongFrameLength = this.tooLongFrameLength;  
  6.            this.tooLongFrameLength = 0;  
  7.            discardingTooLongFrame = false;  
  8.            if ((!failFast) ||  
  9.                (failFast && firstDetectionOfTooLongFrame))  
  10.            {  
  11.                fail(ctx, tooLongFrameLength);  
  12.            }  
  13.        } else {  
  14.            // Keep discarding and notify handlers if necessary.  
  15.            if (failFast && firstDetectionOfTooLongFrame)  
  16.            {  
  17.                fail(ctx, this.tooLongFrameLength);  
  18.            }  
  19.        }  
  20.   
  21.    }  

        如果bytesToDiscard是0,就是说下次编码的时候不需要丢弃了,说明这个超长帧读取完毕,那么将这个编码器的状态设置为非丢弃超长帧状态,如果不是failFast状态或者是failFast又是第一次出现超长帧,就报错,意思其实挺明白,如果不是failFast状态,在这个超长帧读取完毕后理应抛出异常,如果是failFast是第一次发现了超长帧,所以也要抛出异常。else分支很好理解,就是说发现了超长帧,并且客户配置的是立马抛出异常,我们就直接抛出异常即可。

      我们接着来看decode的代码实现:

           

Java代码   收藏代码

  1. if (buffer.readableBytes() < lengthFieldEndOffset) {  
  2.           return null;  
  3.       }  
  4.   
  5.       int actualLengthFieldOffset = buffer.readerIndex() + lengthFieldOffset;  
  6.       long frameLength;  
  7.       switch (lengthFieldLength) {  
  8.       case 1:  
  9.           frameLength = buffer.getUnsignedByte(actualLengthFieldOffset);  
  10.           break;  
  11.       case 2:  
  12.           frameLength = buffer.getUnsignedShort(actualLengthFieldOffset);  
  13.           break;  
  14.       case 3:  
  15.           frameLength = buffer.getUnsignedMedium(actualLengthFieldOffset);  
  16.           break;  
  17.       case 4:  
  18.           frameLength = buffer.getUnsignedInt(actualLengthFieldOffset);  
  19.           break;  
  20.       case 8:  
  21.           frameLength = buffer.getLong(actualLengthFieldOffset);  
  22.           break;  
  23.       default:  
  24.           throw new Error(“should not reach here”);  
  25.       }  

       这段代码结构性很好,也很好理解,如果发现可读的字节数不够,就返回null,等待下次messageReceived通知,然后根据length的长度进行读操作。

      我们接着超下看:

Java代码   收藏代码

  1. if (frameLength < 0) {  
  2.             buffer.skipBytes(lengthFieldEndOffset);  
  3.             throw new CorruptedFrameException(  
  4.                     ”negative pre-adjustment length field: ” + frameLength);  
  5.         }  
  6.   
  7.         frameLength += lengthAdjustment + lengthFieldEndOffset;  
  8.         if (frameLength < lengthFieldEndOffset) {  
  9.             buffer.skipBytes(lengthFieldEndOffset);  
  10.             throw new CorruptedFrameException(  
  11.                     ”Adjusted frame length (“ + frameLength + “) is less ” +  
  12.                     ”than lengthFieldEndOffset: ” + lengthFieldEndOffset);  
  13.         }  


  •  如果我们发现读到的长度是负值,我们将read指针重新设置成读之前的位置,并抛出异常。
  • 计算帧的长度,这个里面是lengthAdjustment + lengthFieldEndOffset,这个比较好理解,这个里面同样做了判断,就是当前帧的长度,不能比lenghtFieldEndOffset小,含义就是帧的数据内容可能为0,但是不可能为负
Java代码   收藏代码

  1. if (frameLength > maxFrameLength) {  
  2.            // Enter the discard mode and discard everything received so far.  
  3.            discardingTooLongFrame = true;  
  4.            tooLongFrameLength = frameLength;  
  5.            bytesToDiscard = frameLength - buffer.readableBytes();  
  6.            buffer.skipBytes(buffer.readableBytes());  
  7.            failIfNecessary(ctx, true);  
  8.            return null;  
  9.        }  
  10.   
  11.        // never overflows because it’s less than maxFrameLength  
  12.        int frameLengthInt = (int) frameLength;  
  13.        if (buffer.readableBytes() < frameLengthInt) {  
  14.            return null;  
  15.        }  

        这个代码就是在处理超长帧的问题,这个在上面我们已经进行解析了

  • 如果发现当前帧的长度超过超长帧的定义,我们将当前的解码器标记为丢弃超长帧状态
  • 计算下一次编码还需要丢弃的长度
  • 修改buffer的读指针
  • 调用failIfNecessary这个函数,判断是否需要抛出异常,关于failIfNecessary函数我们上面已经讲了
  • 然后返回null标记此次解码失败,等待下次messageReceived通知
  • 然后做一次判断,看当前可读数据是不是够一个int型的字节,因为现在马上要读取数据帧的长度了

Java代码   收藏代码

  1. if (initialBytesToStrip > frameLengthInt) {  
  2.           buffer.skipBytes(frameLengthInt);  
  3.           throw new CorruptedFrameException(  
  4.                   ”Adjusted frame length (“ + frameLength + “) is less ” +  
  5.                   ”than initialBytesToStrip: ” + initialBytesToStrip);  
  6.       }  
  7.       buffer.skipBytes(initialBytesToStrip);  
  8.   
  9.       // extract frame  
  10.       int readerIndex = buffer.readerIndex();  
  11.       int actualFrameLength = frameLengthInt - initialBytesToStrip;  
  12.       ChannelBuffer frame = extractFrame(buffer, readerIndex, actualFrameLength);  
  13.       buffer.readerIndex(readerIndex + actualFrameLength);  
  14.       return frame;  

         这个是decode的最后的实现了

  • 最开始是做了一个判断,就是起始的跳过字节数的变量太大,也要抛出异常的
  • 计算数据帧的长度,就是数据包头部里面定义的长度减去需要跳过的字节数
  • 最后就是提取数据并修改读指针,最后返回数据帧

          我们最后来看下这个类的javadoc吧,我感觉netty的作者作为和我们同年龄的80后青年,不但代码写的好,注释也写的很好,这种分享精神很值得我们学习。

  •    第一种情况:从lenght从头开始,长度为2,不跳过头部

                  

C代码   收藏代码

  1. lengthFieldOffset   = 0  
  2.  lengthFieldLength   = 2  
  3.  lengthAdjustment    = 0  
  4.  initialBytesToStrip = 0 (= do not strip header)  
  5.   
  6.  BEFORE DECODE (14 bytes)         AFTER DECODE (14 bytes)  
  7.  +——–+—————-+      +——–+—————-+  
  8.  | Length | Actual Content |—–>| Length | Actual Content |  
  9.  | 0x000C | ”HELLO, WORLD” |      | 0x000C | “HELLO, WORLD” |  
  10.  +——–+—————-+      +——–+—————-+  



  •  第二种情况,从length从头开始,长度为2,跳过头部
C代码   收藏代码

  1. lengthFieldOffset   = 0  
  2. lengthFieldLength   = 2  
  3. lengthAdjustment    = 0  
  4. initialBytesToStrip = 2 (= the length of the Length field)  
  5.   
  6. BEFORE DECODE (14 bytes)         AFTER DECODE (12 bytes)  
  7. +——–+—————-+      +—————-+  
  8. | Length | Actual Content |—–>| Actual Content |  
  9. | 0x000C | ”HELLO, WORLD” |      | “HELLO, WORLD” |  
  10. +——–+—————-+      +—————-+  

     这个和上面的区别是,数据经过解码后头部被去掉了

  • 第三种情况,2个字节长度的头部,offset是0,但是长度代表的是整个数据帧的长度,即就是包含头部

Java代码   收藏代码

  1. lengthFieldOffset   =  0  
  2.  lengthFieldLength   =  2  
  3.  lengthAdjustment    = -2 (= the length of the Length field)  
  4.  initialBytesToStrip =  0  
  5.   
  6.  BEFORE DECODE (14 bytes)         AFTER DECODE (14 bytes)  
  7.  +——–+—————-+      +——–+—————-+  
  8.  | Length | Actual Content |—–>| Length | Actual Content |  
  9.  | 0x000E | “HELLO, WORLD” |      | 0x000E | “HELLO, WORLD” |  
  10.  +——–+—————-+      +——–+—————-+  

猜你喜欢

转载自blog.csdn.net/u011730199/article/details/80841261