我们接下来看一个也是比较重要的的解码器LengthFieldBasedFrameDecoder,这个和DelimiterBasedFrameDecoder比起来没有那么难理解,所以我们简单的看一下。
和之前一样,我们先来看一下局部变量。
private final int maxFrameLength; private final int lengthFieldOffset; private final int lengthFieldLength; private final int lengthFieldEndOffset; private final int lengthAdjustment; private final int initialBytesToStrip; private final boolean failFast; private boolean discardingTooLongFrame; private long tooLongFrameLength; private long bytesToDiscard;
- maxFrameLength 这个定义最大帧的长度
- lengthFieldOffset 长度属性的起始指针(偏移量)
- lengthFieldLength 长度属性的长度,即存放数据包长度的变量的的字节所占的长度
- lengthFieldEndOffset 这个是一个快捷属性,是根据lengthFieldOffset和lengthFieldLength计算出来的,即就是起始偏移量+长度=结束偏移量
- lengthAdjustment 这个是一个长度调节值,例如当总长包含头部信息的时候,这个可以是个负数,就比较好实现了
- initialBytesToStrip 这个属性也比较好理解,就是解码后的数据包需要跳过的头部信息的字节数
- failFast 这个和DelimiterBasedFrameDecoder是一致的,就是如果设置成true,当发现解析的数据超过maxFrameLenght就立马报错,否则当整个帧的数据解析完后才报错
- discardingTooLongFrame 这个也是一个导出属性,就是当前编码器的状态,是不是处于丢弃超长帧的状态
- tooLongFrameLength 这个是当出现超长帧的时候,这个超长帧的长度
- bytesToDiscard 这个来定义,当出现超长帧的时候,丢弃的数据的字节数
接下来我们就深入主题,来看这个类的实现,最后我们再分析下javadoc,这样我们就能够彻底的掌握这个编码器了。
if (discardingTooLongFrame) { long bytesToDiscard = this.bytesToDiscard; int localBytesToDiscard = (int) Math.min(bytesToDiscard, buffer.readableBytes()); buffer.skipBytes(localBytesToDiscard); bytesToDiscard -= localBytesToDiscard; this.bytesToDiscard = bytesToDiscard; failIfNecessary(ctx, false); return null; } if (buffer.readableBytes() < lengthFieldEndOffset) { return null; }
这个逻辑是判断,如果当前的编码器处于丢弃超长帧的状态,这个状态肯定是上传编码的时候被设置成这个状态的,然后取上次丢弃的字节数和当前buffer里面的可读数据的最小值,后面的程序理解起来不是那么直接,这个bytesToDiscard属性主要是在后面设置的,bytesToDiscard = frameLength - buffer.readableBytes(); frameLength是当前超长帧的字节数,buffer.readableBytes是当前buffer里面的数据的字节数,所以它的含义就是告诉编码器下次还要丢弃的字节数,举个例子,最大帧的长度是1000,当前帧的长度是1024,所以超过了最大帧了,比如当前buffer里面长度是18个字节,所以bytesToDiscard就是1024-18 = 1006,含义就是说下次解码的时候还要丢弃1006个字节,那上面的代码就比较好理解了,就是说在计算下一次编码需要丢弃的字节数。后面是就是调用failIfNecessary函数了。
我们先来看一下它的实现吧:
private void failIfNecessary(ChannelHandlerContext ctx, boolean firstDetectionOfTooLongFrame) { if (bytesToDiscard == 0) { // Reset to the initial state and tell the handlers that // the frame was too large. long tooLongFrameLength = this.tooLongFrameLength; this.tooLongFrameLength = 0; discardingTooLongFrame = false; if ((!failFast) || (failFast && firstDetectionOfTooLongFrame)) { fail(ctx, tooLongFrameLength); } } else { // Keep discarding and notify handlers if necessary. if (failFast && firstDetectionOfTooLongFrame) { fail(ctx, this.tooLongFrameLength); } } }
如果bytesToDiscard是0,就是说下次编码的时候不需要丢弃了,说明这个超长帧读取完毕,那么将这个编码器的状态设置为非丢弃超长帧状态,如果不是failFast状态或者是failFast又是第一次出现超长帧,就报错,意思其实挺明白,如果不是failFast状态,在这个超长帧读取完毕后理应抛出异常,如果是failFast是第一次发现了超长帧,所以也要抛出异常。else分支很好理解,就是说发现了超长帧,并且客户配置的是立马抛出异常,我们就直接抛出异常即可。
我们接着来看decode的代码实现:
if (buffer.readableBytes() < lengthFieldEndOffset) { return null; } int actualLengthFieldOffset = buffer.readerIndex() + lengthFieldOffset; long frameLength; switch (lengthFieldLength) { case 1: frameLength = buffer.getUnsignedByte(actualLengthFieldOffset); break; case 2: frameLength = buffer.getUnsignedShort(actualLengthFieldOffset); break; case 3: frameLength = buffer.getUnsignedMedium(actualLengthFieldOffset); break; case 4: frameLength = buffer.getUnsignedInt(actualLengthFieldOffset); break; case 8: frameLength = buffer.getLong(actualLengthFieldOffset); break; default: throw new Error("should not reach here"); }
这段代码结构性很好,也很好理解,如果发现可读的字节数不够,就返回null,等待下次messageReceived通知,然后根据length的长度进行读操作。
我们接着超下看:
if (frameLength < 0) { buffer.skipBytes(lengthFieldEndOffset); throw new CorruptedFrameException( "negative pre-adjustment length field: " + frameLength); } frameLength += lengthAdjustment + lengthFieldEndOffset; if (frameLength < lengthFieldEndOffset) { buffer.skipBytes(lengthFieldEndOffset); throw new CorruptedFrameException( "Adjusted frame length (" + frameLength + ") is less " + "than lengthFieldEndOffset: " + lengthFieldEndOffset); }
- 如果我们发现读到的长度是负值,我们将read指针重新设置成读之前的位置,并抛出异常。
- 计算帧的长度,这个里面是lengthAdjustment + lengthFieldEndOffset,这个比较好理解,这个里面同样做了判断,就是当前帧的长度,不能比lenghtFieldEndOffset小,含义就是帧的数据内容可能为0,但是不可能为负
if (frameLength > maxFrameLength) { // Enter the discard mode and discard everything received so far. discardingTooLongFrame = true; tooLongFrameLength = frameLength; bytesToDiscard = frameLength - buffer.readableBytes(); buffer.skipBytes(buffer.readableBytes()); failIfNecessary(ctx, true); return null; } // never overflows because it's less than maxFrameLength int frameLengthInt = (int) frameLength; if (buffer.readableBytes() < frameLengthInt) { return null; }
这个代码就是在处理超长帧的问题,这个在上面我们已经进行解析了
- 如果发现当前帧的长度超过超长帧的定义,我们将当前的解码器标记为丢弃超长帧状态
- 计算下一次编码还需要丢弃的长度
- 修改buffer的读指针
- 调用failIfNecessary这个函数,判断是否需要抛出异常,关于failIfNecessary函数我们上面已经讲了
- 然后返回null标记此次解码失败,等待下次messageReceived通知
- 然后做一次判断,看当前可读数据是不是够一个int型的字节,因为现在马上要读取数据帧的长度了
if (initialBytesToStrip > frameLengthInt) { buffer.skipBytes(frameLengthInt); throw new CorruptedFrameException( "Adjusted frame length (" + frameLength + ") is less " + "than initialBytesToStrip: " + initialBytesToStrip); } buffer.skipBytes(initialBytesToStrip); // extract frame int readerIndex = buffer.readerIndex(); int actualFrameLength = frameLengthInt - initialBytesToStrip; ChannelBuffer frame = extractFrame(buffer, readerIndex, actualFrameLength); buffer.readerIndex(readerIndex + actualFrameLength); return frame;
这个是decode的最后的实现了
- 最开始是做了一个判断,就是起始的跳过字节数的变量太大,也要抛出异常的
- 计算数据帧的长度,就是数据包头部里面定义的长度减去需要跳过的字节数
- 最后就是提取数据并修改读指针,最后返回数据帧
我们最后来看下这个类的javadoc吧,我感觉netty的作者作为和我们同年龄的80后青年,不但代码写的好,注释也写的很好,这种分享精神很值得我们学习。
- 第一种情况:从lenght从头开始,长度为2,不跳过头部
lengthFieldOffset = 0 lengthFieldLength = 2 lengthAdjustment = 0 initialBytesToStrip = 0 (= do not strip header) BEFORE DECODE (14 bytes) AFTER DECODE (14 bytes) +--------+----------------+ +--------+----------------+ | Length | Actual Content |----->| Length | Actual Content | | 0x000C | "HELLO, WORLD" | | 0x000C | "HELLO, WORLD" | +--------+----------------+ +--------+----------------+
- 第二种情况,从length从头开始,长度为2,跳过头部
lengthFieldOffset = 0 lengthFieldLength = 2 lengthAdjustment = 0 initialBytesToStrip = 2 (= the length of the Length field) BEFORE DECODE (14 bytes) AFTER DECODE (12 bytes) +--------+----------------+ +----------------+ | Length | Actual Content |----->| Actual Content | | 0x000C | "HELLO, WORLD" | | "HELLO, WORLD" | +--------+----------------+ +----------------+
这个和上面的区别是,数据经过解码后头部被去掉了
- 第三种情况,2个字节长度的头部,offset是0,但是长度代表的是整个数据帧的长度,即就是包含头部
lengthFieldOffset = 0 lengthFieldLength = 2 lengthAdjustment = -2 (= the length of the Length field) initialBytesToStrip = 0 BEFORE DECODE (14 bytes) AFTER DECODE (14 bytes) +--------+----------------+ +--------+----------------+ | Length | Actual Content |----->| Length | Actual Content | | 0x000E | "HELLO, WORLD" | | 0x000E | "HELLO, WORLD" | +--------+----------------+ +--------+----------------+
- 第四种情况,5个字节长度的头部,但是数据帧的lenght的字节数是3,offset是2,不跳过头部
lengthFieldOffset = 2 (= the length of Header 1) lengthFieldLength = 3 lengthAdjustment = 0 initialBytesToStrip = 0 BEFORE DECODE (17 bytes) AFTER DECODE (17 bytes) +----------+----------+----------------+ +----------+----------+----------------+ | Header 1 | Length | Actual Content |----->| Header 1 | Length | Actual Content | | 0xCAFE | 0x00000C | "HELLO, WORLD" | | 0xCAFE | 0x00000C | "HELLO, WORLD" | +----------+----------+----------------+ +----------+----------+----------------+
- 第五种情况,5个字节长度的头部,但是数据帧的lenght的字节数是3,offset是0,不跳过头部
lengthFieldOffset = 0 lengthFieldLength = 3 lengthAdjustment = 2 (= the length of Header 1) initialBytesToStrip = 0 BEFORE DECODE (17 bytes) AFTER DECODE (17 bytes) +----------+----------+----------------+ +----------+----------+----------------+ | Length | Header 1 | Actual Content |----->| Length | Header 1 | Actual Content | | 0x00000C | 0xCAFE | "HELLO, WORLD" | | 0x00000C | 0xCAFE | "HELLO, WORLD" | +----------+----------+----------------+ +----------+----------+----------------+
这个稍微解释一下,由于我们的头部是5个字节,但是length数据域只有3个字节,所以lengthAdjustment是2,表明需要2个字节来调整整个帧的长度。
- 第六种情况,4个字节长度的头部,但是数据帧的lenght的字节数是2,offset是1,跳过头部第一个数据域和长度域
lengthFieldOffset = 1 (= the length of HDR1) lengthFieldLength = 2 lengthAdjustment = 1 (= the length of HDR2) initialBytesToStrip = 3 (= the length of HDR1 + LEN) BEFORE DECODE (16 bytes) AFTER DECODE (13 bytes) +------+--------+------+----------------+ +------+----------------+ | HDR1 | Length | HDR2 | Actual Content |----->| HDR2 | Actual Content | | 0xCA | 0x000C | 0xFE | "HELLO, WORLD" | | 0xFE | "HELLO, WORLD" | +------+--------+------+----------------+ +------+----------------+
这个我稍微解释一下,HDR1是一个字节,Lenght是2个字节,HDR2是两个字节,解码后需要包含HDR2,所以lenghtFieldOffset是1,lenghtFieldLenght是2,lenghtAdjustment是1,实际上就是HDR2的长度,initailBytesToStrip是3,实际上就是HDR1+LEN的长度。
- 第七种情况,4个字节长度的头部,但是数据帧的lenght的字节数是2,offset是1,跳过头部第一个数据域和长度域,消息的长度代表整个数据帧的长度
lengthFieldOffset = 1 lengthFieldLength = 2 lengthAdjustment = -3 (= the length of HDR1 + LEN, negative) initialBytesToStrip = 3 BEFORE DECODE (16 bytes) AFTER DECODE (13 bytes) +------+--------+------+----------------+ +------+----------------+ | HDR1 | Length | HDR2 | Actual Content |----->| HDR2 | Actual Content | | 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的长度。
相关推荐
一个netty的入门教程以及源码分析视频,适合刚学习的人
《Netty源码深入分析》是由美团基础架构部的闪电侠老师所分享的一系列关于Netty源码解析的视频教程。以下将根据标题、描述、标签以及部分内容等信息,对Netty及其源码进行深入剖析。 ### Netty简介 Netty是基于...
《Netty源码剖析视频》课程是一份深度探讨Netty框架源码及其实战应用的资源集合。课程分为两个主要部分,旨在帮助开发者深入理解Netty的内部机制,并通过实战项目提升其在实际开发中的应用能力。 第一部分,深入浅...
Netty 是一个高性能、异步事件驱动的网络应用程序框架,用于快速开发可维护的...在分析源码的过程中,我们通常会关注类的设计模式、线程模型、内存管理以及性能优化等方面,这对于提升网络编程和系统架构能力大有裨益。
Netty是一款高性能的网络应用程序框架,它使用Java编程语言开发,主要用于网络应用程序的快速和易于开发,支持TCP和UDP...通过对Netty源码的深入分析,可以更好地理解其工作机制,对开发高性能的网络应用有极大的帮助。
压缩包内的文件"netty源码剖析视频教程.txt"可能是课程的详细大纲或笔记,提供了对课程内容的进一步概述,包括每个章节的重点和案例分析,是学习过程中不可或缺的参考资料。通过结合视频教程和文本资料,学习者可以...
接下来,我们谈谈 Netty 的源码分析。通过阅读 Netty 源码,我们可以深入了解其设计模式和优化策略: 1. **EventLoop(事件循环)**:Netty 使用单线程的 EventLoop 实现了事件的高效分发,减少了线程切换的开销。 ...
### Netty源码剖析与NIO及Netty5各种RPC架构实战演练三部曲知识点解析 #### 一、Netty概述 Netty是一款基于Java NIO的高性能服务器端编程框架,用于快速开发可维护的网络应用程序。它简化了网络编程的复杂性,使...
Netty源码剖析+视频
总的来说,Netty 源码分析涉及了网络编程、并发处理、事件驱动、协议编解码等多个领域,对理解 Java 高性能网络应用开发有着重要的指导意义。通过阅读源码,我们可以更深入地了解 Netty 如何实现高效的网络通信,并...
本文档主要讲述了Netty5.0架构剖析和源码解读,涵盖了Netty的架构、源码分析、NIO入门等方面的知识点。 概述 JAVA 的 IO 演进是一个长期的过程,从传统的 BIO 通信到 NIO 的出现,都是为了解决通信中的问题。传统...
#### 五、Netty源码分析实战案例 1. **ChannelHandlerContext与ChannelHandlerAdaptor详解**: - 分析`ChannelHandlerContext`的生命周期及其与`ChannelHandler`之间的交互方式。 - 深入理解`...
在描述中提到的"只需要下载netty源码,再添加这些jar就可以编译通过了",这意味着你需要获取Netty的源代码仓库,通常可以从GitHub等开源平台获得。源代码包含了Netty的所有模块和组件,可以让你深入了解其内部工作...
视频分两部分 第1 章 : 第一部分、深入浅出Netty源码剖析。。 第2 章 : 第二部分、NIO+Netty5各种RPC架构实战演练
netty-3.3.1.Final-sources.jar src源码
Netty 是一个高性能、异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端。在深入探讨 Netty 源码之前...通过分析源码,不仅可以提升自己的技术能力,还能为解决实际问题提供灵感和参考。
1. **理解底层机制**:通过分析Netty的源码依赖包,可以更深入地理解其内部的工作原理和设计模式,这对于优化网络应用性能至关重要。 2. **学习优秀实践**:Netty作为一个成熟且广泛使用的项目,其代码质量和架构...
在深入分析 Netty 4.x 源码之前,我们首先需要了解其核心概念和架构。 Netty 的核心组件包括: 1. **ByteBuf**: 作为传统 ByteBuffer 的替代品,ByteBuf 提供了更高效且易用的内存管理机制,支持读写分离,避免了...