Netty Bootstrap组件
Bootstrap引导类
一个 Netty 应用通常由一个 Bootstrap 开始,主要作用是配置整个 Netty 程序,串联各个组件,而客户端和服务端这两种应用程序间通用的引导步骤有AbstractBootstrap处理;
引导类的层次结构
Netty 中 Bootstrap 类是客户端程序的启动引导类,ServerBootstrap 是服务端启动引导类;
为何引导类要实现Cloneable接口?
有时可能会需要创建多个具有类似配置或者完全相同配置的Channel(如链式调用那里添加配置);为了支持这种模式而又不需要为每个Channel都创建并配置一个新的引导类实例,AbstractBootstrap被标记为Cloneable的引导类实例;
注意,这种方式只会创建引导类实例的EventLoopGroup的一个浅拷贝,所以被浅拷贝的EventLoopGroup将在所有克隆的Channel实例之间共享;这是可以接受的,因为通常这些克隆的Channel的生命周期都很短暂;如创建一个Channel以进行一次HTTP请求;
对于AbstractBootstrap的子类型 B 是其父类型的一个类型参数,因此可以返回到运行时实例的引用以支持方法的链式调用(也就是所谓的流式语法);
父子通道的概念
在Netty中,每一个NioSocketChannel通道所封装的是Java NIO通道,再往下就对应到操作系统底层的socket文件描述符,理论上讲,操作系统底层的socket文件描述符分为两类:
- 用于接受客户端连接
用于接受客户端连接的socket描述符,处于服务器端,它负责接收客户端的套接字连接;
- 用于数据传输
用于数据传输的socket描述符,负责传输数据;如同一条TCP的Socket传输链路,在服务端和客户端,它们都分别会有一个与之相对应的数据传输类型的socket文件描述符;
在Netty中,异步非阻塞的服务端监听通道为NioServerSocketChannel,封装的Linux底层的文件描述符是用于接受客户端连接的socket描述符;而异步非阻塞的传输通道NioSocketChannel,封装的是用于数据传输的socket描述符;
在Netty中,将有接收关系的ServerSocketChannel和SocketChannel称为父子通道;其中,NioServerSocketChannel负责服务器连接监听和接收的监听通道,称为父通道(Parent Channel),而NioSocketChannel对应于每一个接收到的传输类通道,称为子通道(Child Channel);
EventLoopGroup事件轮询组的概念
Reactor反应器模式的具体实现分为单线程实现版本和多线程实现版本;
多线程版本的Reactor模型如下图
参考:[https://gee.cs.oswego.edu/dl/cpjslides/nio.pdf]
在Netty中,一个 EventLoop相当于一个子反应器(SubReactor),一个NioEventLoop子反应器拥有了一个事件轮询线程,同时拥有一个 Java NIO选择器;
Netty中的Reactor反应器模式实现,不是单线程版本的反应器模式,而是多线程版本的反应器模式;Netty多线程版本的反应器模式实现是使用 EventLoopGroup轮询组;多个EventLoop线程放在一起,可以组成一个EventLoopGroup轮询组;EventLoopGroup轮询组就是一个多线程版本的反应器,其中的单个 EventLoop线程对应于一个子反应器(SubReactor);
Netty的程序开发不会直接使用单个 EventLoop事件轮询器,而是使用 EventLoopGroup轮询组;EventLoopGroup的构造函数有一个参数,用于指定内部的线程数;在构造器初始化时,会按照传入的线程数量,在内部构造多个Thread线程和多个EventLoop子反应器(一个线程对应一个EventLoop子反应器),进行多线程的 IO事件查询和分发;
EventLoopGroup的无参数的构造函数,没有传入线程数量或者传入的数量为0,默认的EventLoopGroup内部线程数量为最大可用的CPU处理器数量的2倍,假设电脑使用的是4核的CPU,那么在内部会启动8个EventLoop线程,相当8个子反应器(SubReactor)实例;
服务端为了及时接受(Accept)到新连接,在服务端,一般有两个独立的反应器,一个反应器负责新连接的监听和接受,另一个反应器负责 IO事件轮询和分发,两个反应器相互隔离,对应到Netty服务器程序中,则需要设置两个EventLoopGroup轮询组,一个组负责新连接的监听和接受,另外一个组负责 IO传输事件 的轮询与分发,两个轮询组的职责具体如下:
- 负责新连接的监听和接收的EventLoopGroup轮询组中的反应器(Reactor),完成查询通道的新连接I/O事件查询;
- 另一个轮询组中的反应器(Reactor),完成查询所有子通道的I/O事件,并且执行对应的Handler处理器完成I/O处理(如:数据的输入和输出);
Bootstrap启动流程步骤
Bootstrap的启动流程,即Netty组件的组装、配置,以及 Netty服务器或者客户端的启动流程;
- 首先创建一个服务器端的引导类实例;
ServerBootstrap b = new ServerBootstrap();
- 创建反应器轮询组,并设置到ServerBootstrap引导类实例;
//创建reactor 线程组
EventLoopGroup bossLoopGroup = new NioEventLoopGroup(1);
EventLoopGroup workerLoopGroup = new NioEventLoopGroup();
//设置reactor 线程组
b.group(bossLoopGroup, workerLoopGroup);
在设置反应器轮询组之前,创建了两个 NioEventLoopGroup轮询组,一个负责处理连接监听I/O事件为bossLoopGroup;另一个负责数据传输事件和处理为workerLoopGroup;在两个轮询组创建完成后,就可以配置给引导类实例,它一次性地给引导类配置了两大轮询组;
注:如果不需要进行新连接事件和输出事件进行分开监听,就不一定非得配置两个轮询组,可以仅配置一个EventLoopGroup反应器轮询组,具体的配置如下:
b.group(workerGroup);
在这种模式下,新连接监听I/O事件和数据传输I/O事件可能被挤在了同一个线程中处理;这样会带来一个风险:新连接的接受被更加耗时的数据传输或者业务处理所阻塞;因此,在服务器端建议设置成两个轮询组的工作模式;
- 设置通道的I/O类型,Netty不止支持 Java NIO,也支持阻塞式的OIO(OIO为阻塞式I/O,如果Bootstrap的I/O模型需要指定为BIO类型,则可以配置为OioServerSocketChannel类即可);
//设置nio类型的channel
b.channel(NioServerSocketChannel.class);
- 设置监听端口;
//设置监听端口
b.localAddress(serverPort);
这一操作主要是设置服务器的监听地址;
- 设置传输通道的配置选项;
//设置通道的参数
b.option(ChannelOption.SO_KEEPALIVE, true);
b.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
b.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
b.childOption(ChannelOption.TCP_NODELAY, true);
Bootstrap的option选项设置方法,对于服务端来说,这个方法是用于给父通道(Parent Channel)设置一些与传输协议相关的选项;如果要给子通道(Child Channel)设置一些通道选项,则需要用另一个childOption的设置方法;
- 装配子通道的Pipeline流水线;
每一个通道都用一条ChannelPipeline流水线,它的内部有一个双向的链表;装配流水线的方式:将业务处理器ChannelHandler实例包装之后加入双向链表中;
装配子通道的Handler流水线调用引导类的childHandler方法,该方法需要传入一个ChannelInitializer通道初始化类的实例作为参数;每当父通道成功接收一个连接,并创建成功一个子通道后,就会初始化子通道,此时这里配置的ChannelInitializer实例就会被调用;
在ChannelInitializer通道初始化类的实例中,有一个initChannel初始化方法,在子通道创建后会被执行到,向子通道流水线增加业务处理器;
//装配子通道流水线
b.childHandler(new ChannelInitializer<SocketChannel>() {
//有连接到达时会创建一个channel
@Override
protected void initChannel(SocketChannel ch) throws Exception {
// pipeline管理子通道channel中的Handler
ch.pipeline().addLast(new NettyDiscardHandler());
}
});
为何仅装配子通道的流水线,而不需要装配父通道的流水线?
父通道NioServerSocketChannel的内部业务处理是固定的:即接受新连接后,创建子通道,然后初始化子通道,因此不需要特别的配置,由Netty自行进行装配;如果需要完成特殊的父通道业务处理,可以类似地使用ServerBootstrap的handler方法,为父通道设置ChannelInitializer初始化器;
注:在装配流水线时需要注意ChannelInitializer处理器有一个泛型参数SocketChannel,它代表需要初始化的通道类型,这个类型需要和引导类中设置的传输通道类型,保持一一对应起来;
- 开始绑定服务器新连接的监听端口;
//开始绑定端口
ChannelFuture channelFuture = b.bind();
channelFuture.addListener(l -> {
if (l.isSuccess()) {
logger.info(" 服务器端口绑定 正常: {}", channelFuture.channel().localAddress());
} else {
logger.info(" 服务器端口绑定 失败: {}", channelFuture.channel().localAddress());
}
});
//通过调用sync同步方法阻塞直到绑定成功
channelFuture.sync();
logger.info(" 服务器启动成功,监听端口: ", channelFuture.channel().localAddress());
Bootstrap#bind方法的作用:返回一个端口绑定 Netty的异步任务ChannelFuture(ChannelFuture可以给异步任务增加回调监听器),阻塞channelFuture异步任务,直到端口绑定任务执行完成;
Netty中所有的I/O操作都是异步执行的,这就意味着任何一个I/O操作都会立刻返回,但在返回的时候,异步任务还没有真正执行,如下图;
通过该异步任务实例,既可以实现同步阻塞一直到 ChannelFuture异步任务执行完成,也可以为其增加事件监听器的方式注册异步回调逻辑,以获得Netty中的I/O操作的真正结果;
- 使用ChannelFuture阻塞,直到监听通道关闭;
//等待通道关闭的异步任务结束
//服务监听通道会一直等待通道关闭的异步任务结束
ChannelFuture closeFuture = channelFuture.channel().closeFuture();
closeFuture.sync();
阻塞当前线程直到通道关闭,可以使用通道的closeFuture方法,以获取通道关闭的异步任务;当通道被关闭时,closeFuture实例的sync方法会返回;
- 关闭EventLoopGroup;
//优雅关闭EventLoopGroup,释放掉所有资源包括创建的线程
workerLoopGroup.shutdownGracefully();
bossLoopGroup.shutdownGracefully();
关闭Reactor反应器轮询组,同时会关闭内部的SubReactor子反应器线程,内部的Selector选择器、内部的轮询线程以及负责查询的所有的子通道;在子通道关闭后,会释放掉底层的资源,如Socket文件描述符等;
ChannelOption通道选项
无论是NioServerSocketChannel父通道类型,还是NioSocketChannel子通道类型,都可以设置一系列的ChannelOption通道选项,ChannelOption包括了底层连接的详细信息;常见的选项如下:
- SO_SNDBUF和SO_RCVBUF
此为TCP传输选项,每个TCP Socket(套接字)在内核中都有一个发送缓冲区(SO_SNDBUF)和一个接收缓冲区(SO_RCVBUF),这两个选项就是用来设置 TCP连接的这两个缓冲区大小的;TCP的全双工工作模式以及 TCP的滑动窗口对两个独立的缓冲区都有依赖;
- TCP_NODELAY
TCP_NODELAY的值,设置为true表示关闭延迟,设置为false表示开启延迟,其值与是否开启 Nagle 算法是相反的;在Netty中,TCP_NODELAY的值默认为true,而操作系统默认为false;
如果要求高实时性,有数据发送时就马上发送,就将该选项设置为 true(关闭 Nagle算法);如果要减少发送次数减少网络交互,就设置为 false(启用 Nagle算法),等累积一定大小的数据后再发送;
- SO_KEEPALIVE
SO_KEEPALIVE为TCP传输选项,表示是否开启 TCP协议的心跳机制,true为连接保持心跳,默认值为false;
启用该功能时,TCP会主动探测空闲连接的有效性,可以将此功能视为 TCP的心跳机制;
注:默认的心跳间隔是7200秒;在Netty中默认关闭该功能;
- SO_REUSEADDR
此为TCP传输选项,如果为 true时表示地址复用,默认值为 false;有四种情况需要用到这个参数设置:
- 当有一个地址和端口相同的连接socket1处于TIME_WAIT状态时,而又希望启动一个新的连接socket2要占用该地址和端口;
- 有多块网卡或用IP Aliasing(https://en.wikipedia.org/wiki/IP_aliasing)的机器在同一端口启动多个进程,但每个进程绑定的本地 IP地址不能相同;
- 同一进程绑定相同的端口到多个Socket(套接字),但每个Socket绑定的IP地址不同;
- 完全相同的地址和端口的重复绑定,但这只能用于UDP,不用于TCP;
- SO_LINGER
此为TCP传输选项,选项可以用来控制socket.close()方法被调用后的行为,包括延迟关闭时间;
如果此选项设置为-1,表示socket.close()方法在调用后立即返回,但操作系统底层会将发送缓冲区的数据全部发送到对端(SO_LINGER的默认值为 -1,表示禁用该功能);
如果此选项设置为0,表示socket.close()方法在调用后会立即返回,但是操作系统会放弃发送缓冲区数据,而是直接向对端发送 RST包,对端将收到复位错误;
如果此选项设置为非0正整数值,表示调用socket.close()方法的线程被阻塞,直到延迟时间到来,发送缓冲区中的数据发送完毕,若超时,则对端会收到复位错误;
- SO_BACKLOG
此为TCP传输选项,表示服务器端接收连接的队列长度,如果队列已满,客户端连接将被拒绝;
服务端在处理客户端新连接请求时(三次握手)是顺序处理的,同一时间只能处理一个客户端连接,多个客户端来的时候,服务端将不能处理的客户端连接请求放在队列中等待处理,队列的大小通过 SO_BACKLOG指定;
- SO_BROADCAST
此为TCP传输选项,表示设置为广播模式;
在某些常用的属性和数据不可用时,Netty提供了AttributeMap抽象接口(一个由Channel 和引导类提供的集合)以及AttributeKey(一个用于插入和获取属性值的泛型类);
io.netty.util.AttributeMap
io.netty.util.AttributeKey
使用这些Netty内置工具便可以安全地将任何类型的数据项与客户端和服务端Channel(包含ServerChannel 的子通道)相关联;
//创建一个AttributeKey以标识该属性
final AttributeKey<Integer> id = new AttributeKey<Integer>("ID");
//存储该id属性
b.attr(id,123456);
引导客户端
Bootstrap 类负责为客户端和使用无连接协议的应用程序创建Channel;
Bootstrap类的API
名称 | 描述 |
Bootstrap group(EventLoopGroup group) | 设置用于处理Channel所有事件的EventLoopGroup; |
Bootstrap channel(Class<? extends C> channelClass) Bootstrap channelFactory(ChannelFactory<? extends C> channelFactory) |
channel方法指定了Channel的实现类;如果该实现类没提供默认的构造函数,可以通过调用channelFactory方法来指定一个工厂类,它将会被bind方法调用; |
Bootstrap localAddress(SocketAddress localAddress) | 指定Channel应该绑定到的本地地址;如果没有指定,则将由操作系统创建一个随机的地址,或者可以通过bind或者connect方法指定localAddress; |
<T> Bootstrap option(ChannelOption<T> option, T value) |
设置ChannelOption,其将被应用到每个新创建的Channel的ChannelConfig; 这些选项将会通过bind或者connect方法设置到Channel,不管哪个先被调用; 这个方法在Channel已经被创建后再调用将不会有任何的效果; 支持的ChannelOption取决于使用的Channel类型; |
<T> Bootstrap attr(Attribute<T> key, T value) | 指定新创建的Channel 的属性值。这些属性值是通过bind或者connect方法设置到Channel 的,具体取决于谁最先被调用;这个方法在Channel被创建后将不会有任何的效果; |
Bootstrap handler(ChannelHandler handler) | 设置将被添加到ChannelPipeline以接收事件通知的ChannelHandler; |
Bootstrap clone() | 创建一个当前Bootstrap的克隆,其具有和原始的Bootstrap 相同的设置信息; |
Bootstrap remoteAddress(SocketAddress remoteAddress) | 设置远程地址,或者可以通过connect方法来指定它; |
ChannelFuture connect() | 连接到远程节点并返回一个ChannelFuture,其将会在连接操作完成后接收到通知; |
ChannelFuture bind() | 绑定Channel并返回一个ChannelFuture,其将会在绑定操作完成后接收到通知,在那之后必须调用Channel#connect方法来建立连接; |
使用如下:
查看代码
public class NettyClient {
public static void main(String[] args) {
final int port = 19000 ;
final String local = "127.0.0.1" ;
EventLoopGroup group = new NioEventLoopGroup();
try {
// 创建一个BootStrap类的实例以创建和连接新的Channel
Bootstrap bootstrap = new Bootstrap();
// 链式写法
// 设置EventLoopGroup,提供用于处理Channel事件的EventLoop
bootstrap.group(group)
// 指定要使用的Channel实现
.channel(NioSocketChannel. class )
// 设置用于用于处理Channel事件的Handler
.handler( new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast( new NettyClientHandler());
}
});
// 连接服务端
ChannelFuture channelFuture = bootstrap.connect(local, port).sync();
channelFuture.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
group.shutdownGracefully();
}
}
}
除了 connect方法以外, BootStrap的其他方法将通过每次方法调用所返回Bootstrap实例的引用;
在引导的过程中,在调用bind或者connect方法之前,必须调用以下方法来设置所需的组件(如果不这样做, 则将会导致 IllegalStateException):
- group方法;
- channel或者channelFactory方法;
- handler方法需要通过该方法配置ChannelPipeline;
引导服务器
ServerBootstrap 是服务端启动引导类;
ServerBootstrap在bind方法被调用时创建了一个ServerChannel,并且该 ServerChannel 管理了多个子 Channel;
ServerChannel的实现负责创建子 Channel,这些子Channel 代表了已被接受的连接(已连接的客户端);
负责引导 ServerChannel 的 ServerBootstrap 提供了的方法,以简化将设置应用到已被接受的子Channel的ChannelConfig 的任务;
ServerBootstrap类的API
名称 | 描述 |
ServerBootstrap group(EventLoopGroup group) ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) |
设置ServerBootstrap要用的EventLoopGroup;这个EventLoopGroup将用于ServerChannel和被接受的子Channel的I/O处理; |
ServerBootstrap channel(Class<? extends C> channelClass) | 设置将要被实例化的ServerChannel类; |
ServerBootstrap channelFactory(ChannelFactory<? extends C> channelFactory) | 如果不能通过默认的构造函数创建Channel,那么可以提供一个ChannelFactory; |
ServerBootstrap localAddress(SocketAddress localAddress) |
指定ServerChannel应该绑定到的本地地址; 如果没有指定,则将由操作系统使用一个随机地址,或者可以通过bind方法来指定该localAddress; |
<T> ServerBootstrap option(ChannelOption<T> option, T value) |
指定要应用到新创建的ServerChannel 的ChannelConfig的ChannelOption; 这些选项将会通过bind方法设置到Channel,在bind方法被调用之后,设置或者改变ChannelOption都不会有任何的效果;所支持的ChannelOption 取决于所使用的Channel 类型; |
<T> ServerBootstrap childOption(ChannelOption<T> option, T value) | 指定当子Channel被接受时,应用到子Channel的ChannelConfig的ChannelOption;所支持的ChannelOption 取决于所使用的Channel的类型; |
<T> ServerBootstrap attr(AttributeKey<T> key, T value) |
指定ServerChannel上的属性,属性将会通过bind方法设置给Channel;在调用bind方法之后改变它们将不会有任何的效果; |
<T> ServerBootstrap childAttr(AttributeKey<T> childKey, T value) |
将属性设置给已经被接受的子Channel,接下来的调用将不会有任何的效果; |
ServerBootstrap handler(ChannelHandler handler) | 设置被添加到ServerChannel 的ChannelPipeline 中的ChannelHandler |
ServerBootstrap childHandler(ChannelHandler childHandler) |
设置将被添加到已被接受的子Channel 的ChannelPipeline中的ChannelHandler; handler方法和childHandler方法之间的区别:前者所添加的ChannelHandler由接受子Channel 的ServerChannel 处理,而childHandler方法所添加的ChannelHandler 将由已被接受的子Channel处理,其代表一个绑定到远程节点的套接字; |
ServerBootstrap clone() | 克隆一个设置和原始的ServerBootstrap相同的ServerBootstrap; |
ChannelFuture bind() | 绑定ServerChannel并且返回一个ChannelFuture,其将会在绑定操作完成后收到通知(带着成功或者失败的结果); |
使用如下:
查看代码
public class NettyServer {
private final static Logger logger = LoggerFactory.getLogger(NettyServer. class );
public static void main(String[] args) {
// 创建两个线程组bossGroup和workerGroup,含有子线程NioEventLoop的个数默认为cpu核数的两倍
// bossGroup只处理连接请求,业务处理交由wokerGroup
EventLoopGroup bossGroup = new NioEventLoopGroup( 1 );
EventLoopGroup workerGroup = new NioEventLoopGroup();
final int port = 19000 ;
try {
// 创建服务的启动对象
ServerBootstrap bootstrap = new ServerBootstrap();
// 设置两个线程组
bootstrap.group(bossGroup, workerGroup)
// 指定使用NioServerSocketChannel作为服务器的通道实现
.channel(NioServerSocketChannel. class )
// 初始化服务器连接队列大小,服务端处理客户端连接请求是顺序处理的,所以同一时间只能处理一个客户端连接
// 多个客户端同时来的时候,服务端将不能处理的客户端连接请求放在队列中等待处理
.option(ChannelOption.SO_BACKLOG, 1024 )
.childHandler( new ChannelInitializer<SocketChannel>() {
//创建通道初始化对象,设置初始化参数
@Override
protected void initChannel(SocketChannel ch) throws Exception {
//对workerGroup的SocketChannel设置处理器codec
ch.pipeline().addLast( new NettyServerHandler());
}
});
logger.info( "netty server start..." );
// 通过配置好ServerBootStrap的实例绑定该Channel
// 启动服务器(并绑定端口),bind是异步操作,sync方法是等待异步操作执行完毕,这里会阻塞住
ChannelFuture channelFuture = bootstrap.bind(port).sync();
// 添加监听器
channelFuture.addListener( new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (future.isSuccess()) {
logger.info( "监听端口" + port + "成功" );
} else {
logger.info( "监听失败..." );
}
}
});
channelFuture.channel().closeFuture().sync();
} catch (Exception e) {
logger.error( "异常.." , e);
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
从Channel引导客户端
如果服务器正在处理一个客户端的请求,这个请求需要它充当第三方系统的客户端;当一个应用程序(如一个代理服务器)必须要和组织现有的系统(如Web 服务或者数据库)集成时,就可能发生这种情况; 在这种情况下,将需要从已经被接受的子通道中引导一个客户端Channel;
如果创建新的BootStrap实例,并且每个新创建的客户端Channel定义另一个EventLoop,这会产生额外的线程,以及在已被接受的子 Channel 和客户端 Channel 之间交换数据时不可避免的上下文切换;
通过将已被接受的子通道(已连接的客户端) 的EventLoop传递给Bootstrap的group方法来共享该EventLoop;因为分配给EventLoop的所有Channel都使用同一个线程,所以这避免了额外的线程创建和Channel之间数据交换时的上下文切换;如下图;
子通道handler中共享EventLoop,在子通道handler的写法如下:
// 创建一个Bootstrap类的类型以连接到远程
Bootstrap bootstrap = new Bootstrap();
// 使用与分配给已被接受的子Channel相同的EventLoop
bootstrap.group(ctx.channel().eventLoop());
在引导过程中添加多个ChannelHandler
在引导的过程中调用了handler或者childHandler方法来添加单个的ChannelHandler,这对于简单的应用程序来说可能已经足够了,但是它不能满足更加复杂的需求,如一个必须要支持多种协议的应用程序将会有很多的ChannelHandler,而不会是一个庞大而又笨重的类;
开发人员可以根据需要,通过在ChannelPipeline 中将它们链接在一起来部署尽可能多的ChannelHandler;
如果在引导的过程中开发人员只能设置一个ChannelHandler,那应该如何处理?
Netty提供了了一个特殊的ChannelInboundHandlerAdapter子类ChannelInitializer,使用ChannelInitializer可向流水线中装配业务处理器;
io.netty.channel.ChannelInitializer
ChannelInitializer中initChannel方法是一个抽象方法,需要开发人员自己实现;
这个方法可用于一种将多个ChannelHandler 添加到一个ChannelPipeline 中的简便方法;
开发人员只需要简单地向Bootstrap 或ServerBootstrap 的实例提供你的ChannelInitializer实现即可,并且一旦Channel被注册到了它的EventLoop之后,就会调用initChannel版本;在该方法返回之后,ChannelInitializer的实例将会从ChannelPipeline中移除它自己;
使用例子如下:
b.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
// pipeline管理子通道channel中的Handler
ch.pipeline().addLast(new NettyDiscardHandler());
}
});