Netty核心组件
# 4.1、Channel
Channel可以理解为是socket连接,在客户端与服务端连接的时候就会建⽴⼀个Channel,它负责基本 的IO操作,⽐如:bind()、connect(),read(),write() 等。
Netty 的 Channel 接⼝所提供的 API,⼤⼤地降低了直接使⽤ Socket 类的复杂性。
不同协议、不同的阻塞类型的连接都有不同的 Channel 类型与之对应,常⽤的 Channel 类型:
NioSocketChannel,NIO的客户端 TCP Socket 连接。
NioServerSocketChannel,NIO的服务器端 TCP Socket 连接。
NioDatagramChannel, UDP 连接。
NioSctpChannel,客户端 Sctp 连接。
NioSctpServerChannel,Sctp 服务器端连接,这些通道涵盖了 UDP 和 TCP ⽹络 IO 以及⽂件IO。
# 4.2、EventLoop、EventLoopGroup
有了 Channel 连接服务,连接之间可以消息流动。如果服务器发出的消息称作“出站”消息,服务器接受 的消息称作“⼊站”消息。那么消息的“出站”/“⼊站”就会产⽣事件(Event)。
例如:连接已激活;数据读取;⽤户事件;异常事件;打开链接;关闭链接等等。 有了事件,就需要⼀个机制去监控和协调事件,这个机制(组件)就是EventLoop。
在 Netty 中每个 Channel 都会被分配到⼀个 EventLoop。⼀个 EventLoop 可以服务于多个 Channel。 每个 EventLoop 会占⽤⼀个 Thread,同时这个 Thread 会处理 EventLoop 上⾯发⽣的所有 IO 操作和 事件。
EventLoopGroup 是⽤来⽣成 EventLoop 的,在前⾯的例⼦中,第⼀⾏代码就是new NioEventLoopGroup();
// 主线程,不处理任何业务逻辑,只是接收客户的连接请求
EventLoopGroup boss = new NioEventLoopGroup(1);
// ⼯作线程,线程数默认是:cpu*2
EventLoopGroup worker = new NioEventLoopGroup();
如果没有指定线程数⼤⼩,默认线程数为:cpu核数*2,源码如下:
static {
DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt("io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2)); //可⽤cpu核数 * 2
if (logger.isDebugEnabled()) {
logger.debug("-Dio.netty.eventLoopThreads: {}", DEFAULT_EVENT_LOOP_THREADS);
}
}
上图关系为:
- ⼀个 EventLoopGroup 包含⼀个或者多个 EventLoop;
- ⼀个 EventLoop 在它的⽣命周期内只和⼀个 Thread 绑定; 所有由 EventLoop 处理的 I/O 事件都将在它专有的 Thread 上被处理;
- ⼀个 Channel 在它的⽣命周期内只注册于⼀个 EventLoop;
- ⼀个 EventLoop 可能会被分配给⼀个或多个 Channel。
# 4.3、ChannelHandler
ChannelHandler对使⽤者⽽⾔,可以说是最重要的组件了,因为对于数据的⼊站和出站的业务逻辑的 编写都是在ChannelHandler中完成的。
在前⾯的例⼦中,MyChannelHandler就是实现了channelRead⽅法,获取到客户端传来的数据。
对于数据的出站和⼊站,有着不同的ChannelHandler类型与之对应:
- ChannelInboundHandler ⼊站事件处理器
- ChannelOutBoundHandler 出站事件处理器
接⼝继承关系如下:
ChannelHandlerAdapter提供了⼀些⽅法的默认实现,可减少⽤户对于ChannelHandler的编写。
ChannelInboundHandlerAdapter 与 SimpleChannelInboundHandler的区别:
在服务端编写ChannelHandler时继承的是ChannelInboundHandlerAdapter
在客户端编写ChannelHandler时继承的是SimpleChannelInboundHandler
两者的区别在于,前者不会释放消息数据的引⽤,⽽后者会释放消息数据的引⽤。
# 4.4、ChannelPipeline
在Channel的数据传递过程中,对应着有很多的业务逻辑需要处理,⽐如:编码解码处理、读写操作 等,那么对于每种业务逻辑实现都需要有个ChannelHandler完成,也就意味着,⼀个Channel对应着多 个ChannelHandler,多个ChannelHandler如何去管理它们,它们的执⾏顺序⼜该是怎么样的,这就需 要ChannelPipeline进⾏管理了。
⼀个Channel包含了⼀个ChannelPipeline,⽽ChannelPipeline中维护了⼀个ChannelHandler的列 表。
ChannelHandler与Channel和ChannelPipeline之间的映射关系,由ChannelHandlerContext进⾏维 护。
它们关系如下:
ChannelHandler按照加⼊的顺序会组成⼀个双向链表,⼊站事件从链表的head往后传递到最后⼀个 ChannelHandler,出站事件从链表的tail向前传递,直到最后⼀个ChannelHandler,两种类型的 ChannelHandler相互不会影响。
# 4.5、Bootstrap
Bootstrap是引导的意思,它的作⽤是配置整个Netty程序,将各个组件都串起来,最后绑定端⼝、启动 Netty服务。
Netty中提供了2种类型的引导类,⼀种⽤于客户端(Bootstrap),⽽另⼀种(ServerBootstrap)⽤于服务 器。
它们的区别在于:
ServerBootstrap 将绑定到⼀个端⼝,因为服务器必须要监听连接,⽽ Bootstrap 则是由想要连接 到远程节点的客户端应⽤程序所使⽤的。
引导⼀个客户端只需要⼀个EventLoopGroup,但是⼀个ServerBootstrap则需要两个。
- 因为服务器需要两组不同的 Channel
- 第⼀组将只包含⼀个 ServerChannel,代表服务器⾃身的已绑定到某个本地端⼝的正在监听 的套接字。
- 第⼆组将包含所有已创建的⽤来处理传⼊客户端连接。
与ServerChannel相关联的EventLoopGroup 将分配⼀个负责为传⼊连接请求创建 Channel 的 EventLoop。⼀旦连接被接受,第⼆个 EventLoopGroup 就会给它的 Channel 分配⼀个 EventLoop。
# 4.6、Future
Future提供了⼀种在操作完成时通知应⽤程序的⽅式。这个对象可以看作是⼀个异步操作的结果的占位 符,它将在未来的某个时刻完成,并提供对其结果的访问。
JDK 预置了 interface java.util.concurrent.Future,但是其所提供的实现,只允许⼿动检查对应的操作 是否已经完成,或者⼀直阻塞直到它完成。这是⾮常繁琐的,所以 Netty 提供了它⾃⼰的实现—ChannelFuture,⽤于在执⾏异步操作的时候使⽤。
- ChannelFuture提供了⼏种额外的⽅法,这些⽅法使得我们能够注册⼀个或者多个 ChannelFutureListener实例。
- 监听器的回调⽅法operationComplete(),将会在对应的 操作完成时被调⽤ 。然后监听器可以判 断该操作是成功地完成了还是出错了。
- 每个 Netty 的出站 I/O 操作都将返回⼀个 ChannelFuture,也就是说,它们都不会阻塞。 所以 说,Netty完全是异步和事件驱动的。
上图是 serverBootstrap.bind(port) ⽅法底层的逻辑实现。
# 4.7、⼩结
通过以上图将Netty中的核⼼组件串起来。