了解 ServerBootstrap 的一些方法。
网上很多资料都将创建的 NioEventLoopGroup 对象称之为: bossGroup(boss) 和 workGroup(work),这里OP不会这么称呼,而是采用源码中变量的命名,即: parentGroup和 childGroup。
本文 Netty 源码解析基于 4.1.86.Final 版本。
1. 继承关系
ServerBootstrap 的继承实现关系比较简单,向上只继承了 AbstractBootstrap。
2. group方法
group 方法用于设置 NioEventLoopGroup ,NioEventloopGroup 本质是线程池, 它的核心作用是接收 I/O 请求,并分配 NioEventLoop 执行处理;
如果调用 group(EventLoopGroup group)
方法,对于 Netty 来说,parentGroup 和 childGroup 是同一个 NioEventLoopGroup,对照的就是 单Reactor模式,但至于是 单reactor-单线程模型 还是 单reactor-多线程模型 下面再说。
而如果像 示例代码(点击跳转) 一样,调用 group(EventLoopGroup parentGroup, EventLoopGroup childGroup)
方法,parentGroup 和 childGroup 分别是不同的 NioEventLoopGroup ,对照的就是 主从reactor-多线程模型。
1 | // io.netty.bootstrap.ServerBootstrap |
父类AbstractBootstrap
中的group
方法
1 | // io.netty.bootstrap.AbstractBootstrap#group(io.netty.channel.EventLoopGroup) |
parentGroup
保存在父类 AbstractBootstrap
中,而 childGroup
保存在子类 ServerBootstrap
中。
2.1. 如何配置三种模型
Netty 在配置 ServerBootstrap 时通过 group
方法传递不同的参数,就可以支持 Reactor 的三种线程模型:
2.1.1. 单reactor-单线程模型
1 | // 示例代码 |
此处需要显式的构建一个线程池数量为1 的 NioEventLoopGroup,parentGroup 和 childGroup 使用同一个 EventLoopGroup;如果使用 NioEventLoopGroup 默认的构造方法,NioEventLoopGroup 会创建 CPU * 2
个线程(NioEventLoop)。
2.1.2. 单reactor-多线程模型
1 | // 示例代码 |
此处用默认构造方法创建 NioEventLoopGroup 即可,NioEventLoopGroup 默认构造方法中会创建 CPU * 2
个线程(NioEventLoop),同时 parentGroup 和 childGroup 同样使用同一个 NioEventLoopGroup。
2.1.3. 主从reactor-多线程模型
1 | // 示例代码 |
此处需要显式创建两个 NioEventLoopGroup 对象,parentNioEventLoopGroup 对照 主Reactor,childNioEventLoopGroup 对照 从Reactor 。
3. channel方法
Netty 中两种常用的 Channel 类型:
- 一种是服务端用于监听绑定端口地址并负责与客户端建立连接的 NioServerSocketChannel, 是对
java.nio.channels.ServerSocketChannel
的封装, parentGroup 中的 Channel 就是 NioServerSocketChannel。 - 一种是与客户端通信的 NioSocketChannel, 是对
java.nio.channels.SocketChannel
的封装, childGroup 中的 Channel 就是 NioSocketChannel。
每种 Channel 类型实例都会对应一个 PipeLine 用于编排对应 Channel 实例上的 I/O 事件处理逻辑,而 PipeLine 中组织的就是ChannelHandler,其用于编写特定的IO处理逻辑。
channel 方法继承自父类 AbstractBootstrap
,channel 方法接收的是一个 Class 对象,用于实例化创建 channel 的工厂。
结合 示例代码(点击跳转) 这里接收的就是 NioServerSocketChannel 的 Class。
1 | // io.netty.bootstrap.AbstractBootstrap |
ReflectiveChannelFactory 这个类比较简单,接收到 NioServerSocketChannel 的 Class 后,调用 getConstructor
方法,然后将返回内容存储到 constructor 属性中。
1 | public class ReflectiveChannelFactory<T extends Channel> implements ChannelFactory<T> { |
java.lang.Class#getConstructor(Class<?>... parameterTypes)
方法用于获取 对应参数类型 的构造函数,再通过调用 java.lang.reflect.Constructor#newInstance(Object ... initargs)
方法时传入构造方法参数值,就可以构造一个实例。
此处并没有将 NioServerSocketChannel 进行实例化,后续 ServerBootstrap 调用 bind 方法时会通过调用
newChannel
方法创建NioServerSocketChannel
对象。
4. option方法
option 方法来自父类 AbstractBootstrap,用于设置 NioServerSocketChannel 底层 java.nio.channels.ServerSocketChannel
的TCP参数。
这里 AbstractBootstrap 只是将这些配置值保存起来,后续初始化 Channel 时才会使用它们。
1 | private final Map<ChannelOption<?>, Object> options = new LinkedHashMap<ChannelOption<?>, Object>(); |
4.1. 常用的ChannelOption
SO_RCVBUF / SO_SNDBUF
TCP参数。每个TCP socket(套接字)在内核中都有一个发送缓冲区和一个接收缓冲区,这两个选项就是用来设置TCP连接的这两个缓冲区大小的。TCP的全双工的工作模式以及TCP的滑动窗口便是依赖于这两个独立的缓冲区及其填充的状态。TCP_NODELAY
TCP参数。表示是否立即发送数据,默认值为True(Netty默认为true,而操作系统默认为false)。该值用于设置是否关闭Nagle算法,该算法将小的碎片数据连接成更大的报文(或数据包)来最小化所发送报文的数量,如果需要发送一些较小的报文,则需要禁用该算法。Netty默认禁用该算法,从而最小化报文传输的延时。SO_KEEPALIVE
TCP参数。表示底层TCP协议的心跳机制。true为连接保持心跳,默认值为false。启用该功能时,TCP会主动探测空闲连接的有效性。可以将此功能视为TCP的心跳机制,需要注意的是:默认的心跳间隔是7200s,即2小时。Netty默认关闭该功能。SO_REUSEADDR
此为TCP参数。设置为true时表示地址复用,默认值为false。有四种情况需要用到这个参数设置:- 当有一个有相同本地地址和端口的socket1处于
TIME_WAIT
状态时,而我们希望启动的程序的socket2要占用该地址和端口。例如在重启服务且保持先前端口时; - 有多块网卡或用IP Alias技术的机器在同一端口启动多个进程,但每个进程绑定的本地IP地址不能相同;
- 单个进程绑定相同的端口到多个socket(套接字)上,但每个socket绑定的IP地址不同;
- 完全相同的地址和端口的重复绑定。但这只用于UDP的多播,不用于TCP。
- 当有一个有相同本地地址和端口的socket1处于
SO_LINGER
此为TCP参数。表示关闭socket的延迟时间,默认值为-1,表示禁用该功能:- -1表示socket.close()方法立即返回,但操作系统底层会将发送缓冲区全部发送到对端;
- 0表示socket.close()方法立即返回,操作系统放弃发送缓冲区的数据,直接向对端发送RST包,对端收到复位错误。
- 正整数值表示调用socket.close()方法的线程被阻塞,直到延迟时间到来、发送缓冲区中的数据发送完毕,若超时,则对端会收到复位错误。
SO_BACKLOG
此为TCP参数。表示服务器端接收连接的队列长度,如果队列已满,客户端连接将被拒绝。Linux中默认为128。如果连接建立频繁,服务器处理新连接较慢,可以适当调大这个参数。SO_BROADCAST
此为TCP参数。表示设置广播模式。
5. attr方法
attr 方法来自父类 AbstractBootstrap,用于配置 NioServerSocketChannel 的自定义属性,attr 方法配置的属性,后续可以在Pipineline中使用。
这里 AbstractBootstrap 只是将这些配置值保存起来,后续初始化 Channel 时才会使用它们。
1 | private final Map<AttributeKey<?>, Object> attrs = new ConcurrentHashMap<AttributeKey<?>, Object>(); |
6. childHandler方法
ServerBootstrap 启动类方法带有 child 前缀的均是设置 NioSocketChannel 属性的。
childHandler 方法是 ServerBootstrap 中的,用于设置 NioSocketChannel PipeLine 中的 ChannelHandler,即: 用来处理 已经连接客户端 Channel
的 I/O 读写事件,如编解码器就设置在此处。
注意:childHandler 添加的 ChannelHandler 是在 client 连接成功、触发IO事件后才开始执行的。
1 | public ServerBootstrap childHandler(ChannelHandler childHandler) { |
7. handler方法
handler 方法来自父类 AbstractBootstrap,用于设置 NioServerSocketChannel PipeLine 中的 ChannelHandler,如 示例代码(点击跳转) 中添加了 LoggingHandler。
1 | public B handler(ChannelHandler handler) { |
在服务端初始化 NioServerSocketChannel 过程中,会将 handler 添加到 NioServerSocketChannel 的 pipeline 中,源码位置: ServerBootstrap#init
。
在实际项目使用的过程中,并不会向 Netty 服务端 NioServerSocketChannel 添加额外的ChannelHandler,NioServerSocketChannel 只需专心做好最重要的本职工作 接收客户端连接 即可。示例代码中添加
LoggingHandler
只是为了展示ServerBootstrap
的配置方法。
8. bind方法
bind 方法会校验相关参数配置,然后绑定端口;bind 方法内逻辑很多,后续再分析其流程逻辑。
1 | public ChannelFuture bind() { |
9. 示例代码
1 |
|
如觉得示例代码不合胃口,可以食用官方源码中的Test:
io.netty.bootstrap.ServerBootstrapTest#testHandlerRegister