Netty是一个Java NIO客户端/服务器框架。
Netty中的Reactor模式
Java Reactor模式中IO事件的处理流程:
Reactor模式中IO事件的处理流程大致分为4步:
1、通道注册。IO事件源于通道(Channel),IO是和通道(对应于底层连接而言)强相关的。一个IO事件一定属于某个通道。如果要查询通道的事件,首先就要将通道注册到选择器。
2、查询事件。在Reactor模式中,一个线程会负责一个反应器(或者SubReactor子反应器),不断地轮询,查询选择器中的IO事件(选择键)。
3、事件分发。如果查询到IO事件,则分发给与IO事件有绑定关系的Handler业务处理器。
4、完成真正的IO操作和业务处理,这一步由Handler业务处理器负责。
服务端编程一般用到最多的通信协议还是TCP,对应的Netty传输通道类型为NioSocketChannel
类、Netty服务器监听通道类型为NioServerSocketChannel
。
在Netty的NioSocketChannel内部封装了一个Java NIO的SelectableChannel成员,通过对该内部的Java NIO通道的封装,对Netty的NioSocketChannel通道上的所有IO操作最终都会落地到Java NIO的SelectableChannel底层通道。
对应于NioSocketChannel通道,Netty的反应器类为NioEventLoop(NIO事件轮询)。
理论上来说,一个EventLoop反应器和NettyChannel通道是一对多的关系:一个反应器可以注册成千上万的通道。
Netty的Handler分为两大类:第一类是ChannelInboundHandler
入站处理器;第二类是ChannelOutboundHandler
出站处理器,二者都继承了ChannelHandler
处理器接口。
Bootstrap
Netty提供了一系列辅助类,用于把上面的三个组件快速组装起来完成一个Netty应用,这个系列的类叫作引导类。服务端的引导类叫作ServerBootstrap
类,客户端的引导类叫作Bootstrap
类。
Bootstrap的启动流程也就是Netty组件的组装、配置,以及Netty服务器或者客户端的启动流程。
BootStrap启动流程
创建一个服务端的ServerBootstrap引导类。
|
|
1、创建反应器轮询组,并设置到ServerBootstrap引导类实例。
|
|
在设置反应器轮询组之前,创建了两个NioEventLoopGroup
-
bossLoopGroup:负责处理连接监听IO事件
-
workerLoopGroup:负责数据传输事件和处理
在两个轮询组创建完成后,就可以配置给ServerBootstrap引导类实例,它一次性地给引导类配置了两大轮询组。
2、设置通道的IO类型。
Netty不仅支持Java NIO,也支持阻塞式的OIO。下面配置的是Java NIO类型的通道类型:
|
|
3、设置监听端口。
|
|
4、设置传输通道的配置选项。
|
|
设置了一个底层TCP相关的选项ChannelOption.SO_KEEPALIVE。该选项表示是否开启TCP底层心跳机制,true为开启,false为关闭。启用该功能时,TCP会主动探测空闲连接的有效性。需要注意的是:默认的心跳间隔是7200秒,即2小时。Netty默认关闭该功能。
5、装配子通道的Pipeline。每一个通道都用一条ChannelPipeline流水线,它的内部有一个双向的链表。
|
|
在装配流水线时需要注意的是,ChannelInitializer处理器有一个泛型参数SocketChannel,它代表需要初始化的通道类型,这个类型需要和前面的引导类中设置的传输通道类型一一对应起来。
6、开始绑定服务器新连接的监听端口
|
|
7、自我阻塞,直到监听通道关闭
|
|
8、关闭EventLoopGroup
|
|
关闭反应器轮询组,同时会关闭内部的子反应器线程,也会关闭内部的选择器、内部的轮询线程以及负责查询的所有子通道。在子通道关闭后,会释放掉底层的资源,如Socket文件描述符等。
Channel
Netty通道的抽象类AbstractChannel的构造函数:
|
|
AbstractChannel内部有一个parent父通道属性,保持通道的父通道。
-
连接监听通道(如NioServerSocketChannel)parent属性的值为null;
-
传输通道(如NioSocketChannel)parent属性的值为接收到该连接的监听通道。
在Netty的实际开发中,底层通信传输的基础工作Netty已经替大家完成。实际上,更多的工作是设计和开发ChannelHandler业务处理器
。处理器开发完成后,需要投入单元测试。一般单元测试的大致流程是:先将Handler业务处理器加入到通道的Pipeline流水线中,接下来先后启动Netty服务器、客户端程序,相互发送消息,测试业务处理器的效果。这些复杂的工序存在一个问题:如果每开发一个业务处理器都进行服务器和客户端的重复启动,那么整个的过程是非常烦琐和浪费时间的。如何解决这种徒劳、低效的重复工作呢?Netty提供了一个专用通道,即EmbeddedChannel(嵌入式通道)。
EmbeddedChannel仅仅是模拟入站与出站的操作,底层不进行实际传输,不需要启动Netty服务器和客户端。
Handler
在Reactor经典模型中,反应器查询到IO事件后会分发到Handler业务处理器,由Handler完成IO操作和业务处理。
整个IO处理操作环节大致包括:
- 从通道读数据包
- 数据包解码
- 业务处理
- 目标数据编码
- 把数据包写到通道
- 由通道发送到对端。
整个的IO处理操作环节的前后两个环节
-
从通道读数据包
-
由通道发送到对端
由Netty的底层负责完成,不需要用户程序负责。
用户程序主要涉及的Handler环节为
-
数据包解码:入站处理
-
业务处理:入站处理
-
目标数据编码:出站处理
-
把数据包写到通道中:出站处理
从应用程序开发人员的角度来看有入站和出站两种类型操作。
- 入站处理触发的方向为自底向上,从Netty的内部(如通道)到ChannelInboundHandler入站处理器。
- 出站处理触发的方向为自顶向下,从ChannelOutboundHandler出站处理器到Netty的内部(如通道)。
PipeLine
Netty的业务处理器流水线ChannelPipeline
是基于责任链设计模式(Chain of Responsibility)来设计的,内部是一个双向链表
结构,能够支持动态地添加和删除业务处理器。
在Netty的设计中Handler是无状态的
,不保存和Channel有关的信息。Handler的目标是将自己的处理逻辑做得很通用,可以给不同的Channel使用。
与Handler不同的是,Pipeline是有状态的
,保存了Channel的关系。
于是,Handler和Pipeline之间通过一个中间角色ChannelHandlerContext
(通道处理器上下文)将它们联系起来。
ByteBuf
Netty提供了ByteBuf缓冲区组件来替代Java NIO的ByteBuffer缓冲区组件,以便更加快捷和高效地操纵内存缓冲区。