@ -2558,6 +2558,163 @@ jute.maxbuffer
# Netty
## Netty逻辑架构
![Netty逻辑架构 ](images/Middleware/Netty逻辑架构.png )
Netty内部逻辑的流转:
![Netty内部逻辑的流转 ](images/Middleware/Netty内部逻辑的流转.png )
### 网络通信层
网络通信层的职责是**执行网络I/O的操作**, 它支持多种网络协议和I/O模型的连接操作。当网络数据读取到内核缓冲区后, 会触发各种网络事件, 这些网络事件会分发给事件调度层进行处理。三个核心组件包括:
- **BootStrap和ServerBootStrap**
主要负责整个Netty程序的启动、初始化、服务器连接等过程, 它相当于一条主线, 串联了Netty的其它核心组件。Bootstrap和ServerBootStrap十分相似, 两者的区别在于:
- Bootstrap可用于连接远端服务器, 只绑定一个EventLoopGroup
- ServerBootStrap则用于服务端启动绑定本地端口, 会绑定两个EventLoopGroup, 通常称为Boss和Worker( Boss 会不停地接收新的连接, 然后将连接分配给一个个Worker处理连接)
- **Channel**
提供了基本的API用于网络I/O操作, 如register、bind、connect、read、write、flush 等。Netty的Channel是以JDK NIO Channel为基础的, 相比较于JDK NIO, Netty的Channel提供了更高层次的抽象, 同时屏蔽了底层Socket的复杂性, 赋予了Channel更加强大的功能, 在使用Netty时基本不需要再与Java Socket类直接打交道。
Channel常用的实现类有:
- NioServerSocketChannel: 异步TCP服务端
- NioSocketChannel: 异步TCP客户端
- OioServerSocketChannel: 同步TCP服务端
- OioSocketChannel: 同步TCP客户端
- NioDatagramChannel: 异步UDP连接
- OioDatagramChannel: 同步UDP连接
Channel常见的状态事件回调有:
- channelRegistered: Channel创建后被注册到EventLoop上
- channelUnregistered: Channel创建后未注册或者从EventLoop取消注册
- channelActive: Channel处于就绪状态, 可以被读写
- channelInactive: Channel处于非就绪状态
- channelRead: Channel可以从远端读取到数据
- channelReadComplete: Channel读取数据完成
### 事件调度层
事件调度层的职责是通过Reactor线程模型对各类事件进行聚合处理, 通过Selector主循环线程集成多种事件( I/O事件、信号事件、定时事件等) , 实际的业务处理逻辑是交由服务编排层中相关的Handler完成。两个核心组件包括:
- **EventLoopGroup、EventLoop**
EventLoopGroup是Netty的核心处理引擎, 本质是一个线程池, 主要负责接收I/O请求, 并分配线程执行处理请求。EventLoopGroup的实现类**NioEventLoopGroup**也是 Netty 中最被推荐使用的线程模型。是基于NIO模型开发的, 可以把NioEventLoopGroup理解为一个线程池, 每个线程负责处理多个Channel, 而同一个Channel只会对应一个线程。
![Netty事件调度层 ](images/Middleware/Netty事件调度层.png )
- 一个 EventLoopGroup 往往包含一个或者多个 EventLoop。EventLoop 用于处理 Channel 生命周期内的所有 I/O 事件,如 accept、connect、read、write 等 I/O 事件
- EventLoop 同一时间会与一个线程绑定,每个 EventLoop 负责处理多个 Channel
- 每新建一个 Channel, EventLoopGroup 会选择一个 EventLoop 与其绑定。该 Channel 在生命周期内都可以对 EventLoop 进行多次绑定和解绑
其实EventLoopGroup是Netty Reactor线程模型的具体实现方式, Netty通过创建不同的EventLoopGroup参数配置, 就可以支持Reactor的三种线程模型:
- ** 单线程模型**: EventLoopGroup只包含一个EventLoop, Boss和Worker使用同一个EventLoopGroup
- ** 多线程模型**: EventLoopGroup包含多个EventLoop, Boss和Worker使用同一个EventLoopGroup
- ** 主从多线程模型**: EventLoopGroup包含多个EventLoop, Boss是主Reactor, Worker是从Reactor, 它们分别使用不同的EventLoopGroup, 主Reactor负责新的网络连接Channel创建, 然后把Channel注册到从Reactor
### 服务编排层
服务编排层的职责是负责组装各类服务, 是Netty的核心处理链, 用以实现网络事件的动态编排和有序传播。核心组件包括:
- **ChannelPipeline**
ChannelPipeline 是 Netty 的核心编排组件,负责组装各种 ChannelHandler, 实际数据的编解码以及加工处理操作都是由 ChannelHandler 完成的。ChannelPipeline 可以理解为ChannelHandler 的实例列表——内部通过双向链表将不同的 ChannelHandler 链接在一起。当 I/O 读写事件触发时, ChannelPipeline 会依次调用 ChannelHandler 列表对 Channel 的数据进行拦截和处理。
ChannelPipeline 是线程安全的,因为每一个新的 Channel 都会对应绑定一个新的 ChannelPipeline。一个 ChannelPipeline 关联一个 EventLoop, 一个 EventLoop 仅会绑定一个线程。
ChannelPipeline、ChannelHandler 都是高度可定制的组件。开发者可以通过这两个核心组件掌握对 Channel 数据操作的控制权。下面我们看一下 ChannelPipeline 的结构图:
![ChannelPipeline结构图 ](images/Middleware/ChannelPipeline结构图.png )
ChannelPipeline中包含入站ChannelInboundHandler和出站 ChannelOutboundHandler两种处理器, 结合客户端和服务端的数据收发流程:
![ClientServerChannelPipeline ](images/Middleware/ClientServerChannelPipeline.png )
- **ChannelHandler & ChannelHandlerContext**
数据的编解码工作以及其他转换工作实际都是通过 ChannelHandler 处理的。ChannelHandlerContext 用于保存 ChannelHandler 上下文,通过 ChannelHandlerContext 可以知道 ChannelPipeline 和 ChannelHandler 的关联关系。ChannelHandlerContext 可以实现 ChannelHandler 之间的交互, ChannelHandlerContext 包含了 ChannelHandler 生命周期的所有事件,如 connect、bind、read、flush、write、close 等。
![ChannelHandler ](images/Middleware/ChannelHandler.png )
每创建一个 Channel 都会绑定一个新的 ChannelPipeline, ChannelPipeline 中每加入一个 ChannelHandler 都会绑定一个 ChannelHandlerContext。
## 线程模型
### 单Reactor单线程
### 单Reactor多线程
### 主从Reactor多线程
## 核心设计
### 定时器TimerTask
### 时间轮HashedWheelTimer
### 无锁队列mpsc queue
### FastThreadLocal
### ByteBuf
### 编解码协议
netty-codec模块主要负责编解码工作, 通过编解码实现原始字节数据与业务实体对象之间的相互转化。Netty支持大多数业界主流协议的编解码器, 如**HTTP、HTTP2、Redis、XML**等,为开发者节省了大量的精力。此外该模块提供了抽象的编解码类**ByteToMessageDecoder**和**MessageToByteEncoder**,通过继承这两个类可以轻松实现自定义的编解码逻辑。
![Netty协议 ](images/Middleware/Netty协议.png )
### 拆包粘包
- 拆包/粘包的解决方案
- ** 消息长度固定**:每个数据报文都需要一个固定的长度。当接收方累计读取到固定长度的报文后,就认为已经获得一个完整的消息。当发送方的数据小于固定长度时,则需要空位补齐。消息定长法使用非常简单,但是缺点也非常明显,无法很好设定固定长度的值,如果长度太大会造成字节浪费,长度太小又会影响消息传输,所以在一般情况下消息定长法不会被采用。
- ** 特定分隔符**
既然接收方无法区分消息的边界,那么我们可以在每次发送报文的尾部加上特定分隔符,接收方就可以根据特殊分隔符进行消息拆分。
- ** 消息长度 + 消息内容**
消息长度 + 消息内容是项目开发中最常用的一种协议,如上展示了该协议的基本格式。消息头中存放消息的总长度,例如使用 4 字节的 int 值记录消息的长度,消息体实际的二进制的字节数据。接收方在解析数据时,首先读取消息头的长度字段 Len, 然后紧接着读取长度为 Len 的字节数据,该数据即判定为一个完整的数据报文。
## Netty流程
从功能上,流程可以分为服务启动、建立连接、读取数据、业务处理、发送数据、关闭连接以及关闭服务。整体流程如下所示(图中没有包含关闭的部分):