Netty组件剖析

Bootstrap

Bootstrap是Netty提供的一个便利的工厂类,可以通过它来完成 Netty的客户端或服务端的Netty组件的组装,以及Netty程序的初始化和 启动执行。

Netty的官方解释是,完全可以不用这个Bootstrap类,可以一 点点去手动创建通道、完成各种设置和启动注册到EventLoop反应器, 然后开始事件的轮询和处理,但是这个过程会非常麻烦。

通常情况下, 使用这个便利的Bootstrap工具类的效率会更高。

在Netty中有两个引导类,分别用于服务器(ServerBootstrap)和客户端(Bootstrap)。

这两个引导类仅是使用的地方不同,它们大致的配置和使用方法都是相同的。下面以ServerBootstrap类作为重点介绍对象。

在介绍ServerBootstrap的服务器启动流程之前,首先介绍一下涉及 的两个基础概念:父子通道、EventLoopGroup(事件轮询线程组)。

父子通道

在Netty中,每一个NioSocketChannel通道所封装的都是Java NIO通道,再往下就对应到了操作系统底层的socket文件描述符。

理论上来说,操作系统底层的socket文件描述符分为两类:

  • 连接监听类型

    连接监听类型的socket描述符处于服务端,负责接收客户端的套接字连接;

    在服务端,一个“连接监听类型”的 socket描述符可以接受(Accept)成千上万的传输类的socket文件描述符。

  • 数据传输类型

    数据传输类的socket描述符负责传输数据。

    同一条TCP的Socket传输链路在服务器和客户端都分别会有一个与之相对应的数据传输类型的socket文件描述符。

在Netty中,异步非阻塞的服务端监听通道NioServerSocketChannel 所封装的Linux底层的文件描述符是“连接监听类型”的socket描述符;

异步非阻塞的传输通道NioSocketChannel所封装的Linux的文件描述符是“数据传输类型”的socket描述符。

在Netty中,将有接收关系的监听通道和传输通道叫作父子通道。 其中,负责服务器连接监听和接收的监听通道(如 NioServerSocketChannel)也叫父通道(Parent Channel),对应于每一 个接收到的传输类通道(如NioSocketChannel)也叫子通道(Child Channel)。

EventLoopGroup

在前面介绍Reactor模式的具体实现时,分为单线程实现版本和多线程实现版本。

Netty中的Reactor模式实现不是单线程版本的,而是多线程版本的。

实际上,在Netty中一个EventLoop相当于一个子反应器 (SubReactor),一个NioEventLoop子反应器拥有了一个事件轮询线 程,同时拥有一个Java NIO选择器。

Netty是如何完成多线程版本的Reactor模式实现的呢?

答案是使用 EventLoopGroup(事件轮询组)。

多个EventLoop线程放在一起,可以 组成一个EventLoopGroup。反过来说,EventLoopGroup就是一个多线程 版本的反应器,其中的单个EventLoop线程对应于一个子反应器 (SubReactor)。

Netty的程序开发不会直接使用单个EventLoop(事件轮询器),而 是使用EventLoopGroup。EventLoopGroup的构造函数有一个参数,用于指定内部的线程数。

在构造器初始化时,会按照传入的线程数量在内部构造多个线程和多个EventLoop子反应器(一个线程对应一个EventLoop 子反应器),进行多线程的IO事件查询和分发。

如果使用EventLoopGroup的无参数构造函数,没有传入线程数量或者传入的数量为0,那么EventLoopGroup内部的线程数量到底是多少呢?默认的EventLoopGroup内部线程数量为最大可用的CPU处理器数量 的2倍。

假设电脑使用的是4核的CPU,那么在内部会启动8个EventLoop 线程,相当于8个子反应器实例。

从前文可知,为了及时接收新连接,在服务端,一般有两个独立的反应器,一个负责新连接的监听和接收,另一个负责IO事件轮询和分发,并且两个反应器相互隔离。

对应到Netty服务器程序中,则需要设置两个EventLoopGroup,一个组负责新连接的监听和接受,另外一个组 负责IO传输事件的轮询与分发,两个轮询组的职责具体如下:

(1)负责新连接的监听和接收的EventLoopGroup中的反应器完成 查询通道的新连接IO事件查询。这些反应器有点像负责招工的包工头, 因此,该轮询组可以形象地称为“包工头”(Boss)轮询组。

(2)负责IO事件轮询和分发的反应器完成查询所有子通道的IO事 件,并且执行对应的Handler处理器完成IO处理——例如数据的输入和 输出(有点儿像搬砖),这个轮询组可以形象地称为“工人”(Worker) 轮询组。 Netty的EventLoopGroup与EventLoop之间、EventLoop与Channel之间的关系如图所示。

至此,介绍完了两个重要的基础概念:父子通道与 EventLoopGroup。有了这些基础知识作为铺垫,接下来可以正式介绍 ServerBootstrap的启动流程了。

Bootstrap启动流程

Bootstrap的启动流程也就是Netty组件的组装、配置,以及Netty服务器或者客户端的启动流程。

在本节中对启动流程进行了梳理,大致分成8个步骤。本书仅仅演示的是服务端引导类的使用,用到的引导类为 ServerBootstrap。正式使用前,首先创建一个服务端的引导类实例。

第1步:创建反应器轮询组,并设置到ServerBootstrap引导类实例, 大致的代码如下:

//创建反应器轮询组 
//boss轮询组 
EventLoopGroup bossLoopGroup = new NioEventLoopGroup(1); 

//worker轮询组 
EventLoopGroup workerLoopGroup = new NioEventLoopGroup(); 

//step1:为引导类实例设置反应器轮询组 
b.group(bossLoopGroup, workerLoopGroup);

如果不需要分开监听新连接事件和输出事件,就不一定非得配置两个轮询组,可以仅配置一个EventLoopGroup反应器轮询组。

具体的配置 方法是调用b.group(workerGroup)。

在这种模式下,新连接监听IO事件和 数据传输IO事件可能被挤在了同一个线程中处理。

这样会带来一个风险:新连接的接收被更加耗时的数据传输或者业务处理所阻塞。所以, 在服务端,建议设置成两个轮询组的工作模式。

第2步:设置通道的IO类型。

Netty不止支持Java NIO,也支持阻塞式的OIO。

下面配置的是Java NIO类型的通道类型:

//step2:设置传输通道的类型为NIO类型
b.channel(NioServerSocketChannel.class);

如果确实指定Bootstrap的IO模型为BIO类型,可以配置为 OioServerSocketChannel.class类。

NIO的优势巨大,因此通常不会在 Netty中使用BIO。

第3步:设置监听端口,代码大致如下:

//step3:设置监听端口
b.localAddress(new InetSocketAddress(port));

这是最为简单的一步操作,主要是设置服务器的监听地址。

第4步:设置传输通道的配置选项,代码大致如下:

//step4:设置通道的参数
b.option(ChannelOption.SO_KEEPALIVE, true); 
b.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);

这里调用了Bootstrap的option()选项设置方法。

对于服务器的 Bootstrap而言,这个方法的作用是:给父通道(Parent Channel)设置一 些与传输协议相关的选项。如果要给子通道(Child Channel)设置一些 通道选项,则需要调用childOption()设置方法。 可以设置哪些通道选项(ChannelOption)呢?在上面的代码中,设置了一个底层TCP相关的选项ChannelOption.SO_KEEPALIVE。该选项表示是否开启TCP底层心跳机制,true为开启,false为关闭。

第5步:装配子通道的Pipeline。

每一个通道都用一条 ChannelPipeline流水线,它的内部有一个双向的链表。装配流水线的方式是:将业务处理器ChannelHandler实例包装之后加入双向链表中。

如何装配Pipeline流水线呢?装配子通道的Handler流水线调用引导 类的childHandler()方法,该方法需要传入一个ChannelInitializer通道初始化类的实例作为参数。

每当父通道成功接收到一个连接并创建成功一个 子通道后,就会初始化子通道,此时这里配置的ChannelInitializer实例就会被调用。

在ChannelInitializer通道初始化类的实例中,有一个initChannel初始 化方法,在子通道创建后会被执行,向子通道流水线增加业务处理器。 装配子通道的Pipeline流水线的大致代码如下:

//step5:装配子通道流水线
b.childHandler(new ChannelInitializer() { //有连接到达时会创建一个通道的子通道,并初始化
    protected void initChannel(SocketChannel ch) { //这里可以管理子通道中的Handler业务处理器
        //向子通道流水线添加一个Handler业务处理器 
        ch.pipeline().addLast(new NettyDiscardHandler()); 
    } 
});

为什么仅装配子通道的流水线,而不需要装配父通道的流水线呢? 原因是:父通道(NioServerSocketChannel)的内部业务处理是固定的: 接收新连接后,创建子通道,然后初始化子通道,所以不需要特别的配置,由Netty自行进行装配。如果需要完成特殊的父通道业务处理,可以类似地调用ServerBootstrap的handler(ChannelHandler handler)方法,为 父通道设置ChannelInitializer初始化器。

在装配流水线时需要注意的是,ChannelInitializer处理器有一个泛型参数SocketChannel,它代表需要初始化的通道类型,这个类型需要和前面的引导类中设置的传输通道类型一一对应起来。

第6步:开始绑定服务器新连接的监听端口,代码大致如下:

//step6:开始绑定端口,通过调用sync()同步方法阻塞直到绑定成功
ChannelFuture channelFuture = b.bind().sync(); 
Logger.info(" 服务器启动成功,监听端口: " + channelFuture.channel().localAddress());

b.bind()方法的功能是返回一个端口绑定Netty的异步任务channelFuture。

在这里,并没有给channelFuture异步任务增加回调监听器,而是阻塞channelFuture异步任务,直到端口绑定任务执行完成。

在Netty中,所有的IO操作都是异步执行的,这就意味着任何一个 IO操作都会立即返回,返回时异步任务还没有真正执行。

什么时候执行完成呢?

Netty中的IO操作都会返回异步任务实例(如channelFuture实例)。

通过该异步任务实例,既可以实现同步阻塞一直到channelFuture 异步任务执行完成,也可以通过为其增加事件监听器的方式注册异步回调逻辑,以获得Netty中的IO操作的真正结果。

上面所使用的是同步阻 塞一直到channelFuture异步任务执行完成的处理方式。

至此,服务器正式启动。

说明:Future异步回调或者同步阻塞,涉及高并发的核心模式——异步回调模式,是高并发开发非常重要的基础性知识。

第7步:自我阻塞,直到监听通道关闭,代码大致如下:

//step7:自我阻塞,直到通道关闭的异步任务结束 
ChannelFuture closeFuture = channelFuture.channel().closeFuture(); 
closeFuture.sync();

如果要阻塞当前线程直到通道关闭,可以调用通道的closeFuture() 方法,以获取通道关闭的异步任务。

当通道被关闭时,closeFuture实例的sync()方法会返回。

第8步:关闭EventLoopGroup,代码大致如下:

//step8:释放掉所有资源,包括创建的反应器线程
workerLoopGroup.shutdownGracefully();
bossLoopGroup.shutdownGracefully();

关闭反应器轮询组,同时会关闭内部的子反应器线程,也会关闭内部的选择器、内部的轮询线程以及负责查询的所有子通道。

在子通道关闭后,会释放掉底层的资源,如Socket文件描述符等。

ChannelOption

ChannelOption 无论是对于NioServerSocketChannel父通道类型还是对于 NioSocketChannel子通道类型,都可以设置一系列的ChannelOption(通 道选项)。ChannelOption类中定义了一系列选项,下面介绍一些常见的选项。

  1. SO_RCVBUF和SO_SNDBUF 这两个为TCP传输选项

    每个TCP socket(套接字)在内核中都有 一个发送缓冲区和一个接收缓冲区,这两个选项就是用来设置TCP连接 的两个缓冲区大小的。TCP的全双工工作模式以及TCP的滑动窗口对两 个独立的缓冲区都有依赖。

  2. TCP_NODELAY 此为TCP传输选项,如果设置为true就表示立即发送数据。

    TCP_NODELAY用于开启或关闭Nagle算法。如果要求高实时性,有数据发送时就马上发送,就将该选项设置为true(关闭Nagle算法);如果 要减少发送次数、减少网络交互,就设置为false(开启Nagle算法), 等累积一定大小的数据后再发送。关于TCP_NODELAY的值,Netty默 认为true,而操作系统默认为false。 Nagle算法将小的碎片数据连接成更大的报文(或数据包)来最小化所发送报文的数量,如果需要发送一些较小的报文,则需要禁用该算法。 Netty默认禁用Nagle算法,报文会立即发送出去,从而最小化报文 传输的延时。 说明 TCP_NODELAY的值设置为true表示关闭延迟,设置为false表示开 启延迟。其值与是否开启Nagle算法是相反的。

  3. SO_KEEPALIVE 此为TCP传输选项,表示是否开启TCP的心跳机制。

    true为连接保持心跳,默认值为false。启用该功能时,TCP会主动探测空闲连接的有效性。需要注意的是:默认的心跳间隔是7200秒,即2小时。Netty默认关闭该功能。

  4. SO_REUSEADDR 此为TCP传输选项,为true时表示地址复用,默认值为false。

    有四 种情况需要用到这个参数设置:

    • 当有一个地址和端口相同的连接socket1处于TIME_WAIT状态时, 而又希望启动一个新的连接socket2要占用该地址和端口。
    • 有多块网卡或用IP Alias技术的机器在同一端口启动多个进程, 但每个进程绑定的本地IP地址不能相同。
    • 同一进程绑定相同的端口到多个socket(套接字)上,但每个 socket绑定的IP地址不同。
    • 完全相同的地址和端口的重复绑定,但这只用于UDP的多播,不用于TCP。
  5. SO_LINGER 此为TCP传输选项,可以用来控制socket.close()方法被调用后的行为,包括延迟关闭时间。

    如果此选项设置为-1,就表示socket.close()方法在调用后立即返回,但操作系统底层会将发送缓冲区的数据全部发送 到对端;

    如果此选项设置为0,就表示socket.close()方法在调用后会立即返回,但是操作系统会放弃发送缓冲区数据,直接向对端发送RST包, 对端将收到复位错误;

    如果此选项设置为非0整数值,就表示调用 socket.close()方法的线程被阻塞,直到延迟时间到来,发送缓冲区中的数据发送完毕,若超时,则对端会收到复位错误。

    SO_LINGER的默认值为-1,表示禁用该功能。

  6. SO_BACKLOG 此为TCP传输选项,表示服务端接收连接的队列长度,如果队列已满,客户端连接将被拒绝。

    服务端在处理客户端新连接请求时(三次握 手)是顺序处理的,所以同一时间只能处理一个客户端连接,多个客户 端到来的时候,服务端将不能处理的客户端连接请求放在队列中等待处理,队列的大小通过SO_BACKLOG指定。

    具体来说,服务端对完成第二次握手的连接放在一个队列(暂时称 A队列),如果进一步完成第三次握手,再把连接从A队列移动到新队 列(暂时称B队列),接下来应用程序会通过调用accept()方法取出握手成功的连接,而系统则会将该连接从B队列移除。

    A和B队列的长度之和 是SO_BACKLOG指定的值,当A和B队列的长度之和大于 SO_BACKLOG值时,新连接将会被TCP内核拒绝。

    所以,如果 SO_BACKLOG过小,accept速度可能会跟不上,A和B队列全满,导致新客户端无法连接。SO_BACKLOG对程序支持的连接数并无影响,影响的只是还没有被 accept取出的连接数,也就是三次握手的排队连接数。 如果连接建立频繁,服务器处理新连接较慢,那么可以适当调大这个参数。

  7. SO_BROADCAST 此为TCP传输选项,表示设置为广播模式。

Channel

本节首先为大家介绍一下Channel(通道)的主要成员和方法,然后为大家介绍一下Netty所提供的一个专门的单元测试通道—— EmbeddedChannel(嵌入式通道)。

Channel的主要成员和方法

通道是Netty的核心概念之一,代表网络连接,由它负责同对端进行网络通信,既可以写入数据到对端,也可以从对端读取数据。

Netty通道的抽象类AbstractChannel的构造函数如下:

    protected AbstractChannel(Channel parent) {
        this.parent = parent;//父通道
        id = newId();
        unsafe = newUnsafe();//新建一个底层的NIO通道,完成实际的IO操作
        pipeline = newChannelPipeline();//新建一条通道流水线DefaultChannelPipeline
    }

Netty在对通道进行初始化的时候,将pipeline属性初始化为 DefaultChannelPipeline的实例。

以上代码表明每个通道都拥有一条 ChannelPipeline处理器流水线。

AbstractChannel内部有一个parent父通道属性,保持通道的父通道。

对于连接监听通道(如NioServerSocketChannel)来说,其parent属 性的值为null;

对于传输通道(如NioSocketChannel)来说,其parent属 性的值为接收到该连接的监听通道。

几乎所有的Netty通道实现类都继承了AbstractChannel抽象类,都拥有上面的parent和pipeline两个属性成员。

接下来,介绍一下通道接口中所定义的几个重要方法。

(1)ChannelFuture connect(SocketAddress address) 此方法的作用为连接远程服务器。

方法的参数为远程服务器的地址,调用后会立即返回,其返回值为执行连接操作的异步任务 ChannelFuture。

此方法在客户端的传输通道使用。

(2)ChannelFuture bind(SocketAddress address) 此方法的作用为绑定监听地址,开始监听新的客户端连接。此方法 在服务器的新连接监听和接收通道时调用。

(3)ChannelFuture close() 此方法的作用为关闭通道连接,返回连接关闭的ChannelFuture异步任务。

如果需要在连接正式关闭后执行其他操作,则需要为异步任务设置回调方法;

或者调用ChannelFuture异步任务的sync()方法来阻塞当前线程,一直等到通道关闭的异步任务执行完毕。

(4)Channel read() 此方法的作用为读取通道数据,并且启动入站处理。

具体来说,从内部的Java NIO Channel通道读取数据,然后启动内部的Pipeline流水线,开启数据读取的入站处理。此方法的返回通道自身用于链式调用。

(5)ChannelFuture write(Object o)此方法的作用为启程出站流水处理,把处理后的最终数据写到底层通道(如Java NIO通道)。

此方法的返回值为出站处理的异步处理任 务。

(6)Channel flush() 此方法的作用为将缓冲区中的数据立即写出到对端。

调用前面的 write()出站处理时,并不能将数据直接写出到对端,write操作的作用在大部分情况下仅仅是写入操作系统的缓冲区,操作系统会根据缓冲区的情况决定什么时候把数据写到对端。执行flush()方法会立即将缓冲区的 数据写到对端。

上面的6种方法仅仅是比较常见的通道方法。在Channel接口中以及 各种通道的实现类中还定义了大量的通道操作方法。在一般的日常开发 中,如果需要用到,请直接查阅Netty API文档或者Netty源代码。

EmbeddedChannel

在Netty的实际开发中,底层通信传输的基础工作Netty已经替大家完成。实际上,更多的工作是设计和开发ChannelHandler业务处理器。

处理器开发完成后,需要投入单元测试。

一般单元测试的大致流程是: 先将Handler业务处理器加入到通道的Pipeline流水线中,接下来先后启 动Netty服务器、客户端程序,相互发送消息,测试业务处理器的效果。

这些复杂的工序存在一个问题:如果每开发一个业务处理器都进行 服务器和客户端的重复启动,那么整个的过程是非常烦琐和浪费时间的。

如何解决这种徒劳、低效的重复工作呢?Netty提供了一个专用通道,即EmbeddedChannel(嵌入式通道)。

EmbeddedChannel仅仅是模拟入站与出站的操作,底层不进行实际 传输,不需要启动Netty服务器和客户端。

除了不进行传输之外, EmbeddedChannel的其他事件机制和处理流程和真正的传输通道是一模一样的。

因此,使用EmbeddedChannel,开发人员可以在单元测试用例中方便、快速地进行ChannelHandler业务处理器的单元测试。

最为重要的两个方法为writeInbound()和writeOutbound()方法。

Handler

在Reactor经典模型中,反应器查询到IO事件后会分发到Handler业务处理器,由Handler完成IO操作和业务处理。

整个IO处理操作环节大致包括从通道读数据包、数据包解码、业务 处理、目标数据编码、把数据包写到通道,然后由通道发送到对端。

整个的IO处理操作环节的前后两个环节(包括从通道读数据包和由通道发送到对端),由Netty的底层负责完成,不需要用户程序负责。

用户程序主要涉及的Handler环节为数据包解码、业务处理、目标数据编码、把数据包写到通道中。

前面已经介绍过,从应用程序开发人员的角度来看有入站和出站两 种类型操作。

入站处理触发的方向为自底向上,从Netty的内部(如通道)到 ChannelInboundHandler入站处理器。

出站处理触发的方向为自顶向下,从ChannelOutboundHandler出站处理器到Netty的内部(如通道)。

按照这种触发方向来区分,IO处理操作环节前面的数据包解码、业 务处理两个环节属于入站处理器的工作;

后面目标数据编码、把数据包 写到通道中两个环节属于出站处理器的工作。

ChannelInboundHandler

当对端数据入站到Netty通道时,Netty将触发 ChannelInboundHandler入站处理器所对应的入站API,进行入站操作处理。

  1. channelRegistered() 当通道注册完成后,Netty会调用fireChannelRegistered()方法,触发 通道注册事件,而在通道流水线注册过的入站处理器的 channelRegistered()回调方法会被调用。
  2. channelActive() 当通道激活完成后,Netty会调用fireChannelActive()方法,触发通道激活事件,而在通道流水线注册过的入站处理器的channelActive()回调方法会被调用。
  3. channelRead() 当通道缓冲区可读时,Netty会调用fireChannelRead()方法,触发通道可读事件,而在通道流水线注册过的入站处理器的channelRead()回调 方法会被调用,以便完成入站数据的读取和处理。
  4. channelReadComplete() 当通道缓冲区读完时,Netty会调用fireChannelReadComplete()方法,触发通道缓冲区读完事件,而在通道流水线注册过的入站处理器的 channelReadComplete()回调方法会被调用。
  5. channelInactive() 当连接被断开或者不可用时,Netty会调用fireChannelInactive()方 法,触发连接不可用事件,而在通道流水线注册过的入站处理器的 channelInactive()回调方法会被调用。
  6. exceptionCaught() 当通道处理过程发生异常时,Netty会调用fireExceptionCaught()方 法,触发异常捕获事件,而在通道流水线注册过的入站处理器的 exceptionCaught()方法会被调用。注意,这个方法是在ChannelHandler中 定义的方法,入站处理器、出站处理器接口都继承了该方法。

上面介绍的并不是ChannelInboundHandler的全部方法,仅仅介绍了 其中几种比较重要的方法。在Netty中,入站处理器的默认实现为 ChannelInboundHandlerAdapter,在实际开发中只需要继承 ChannelInboundHandlerAdapter默认实现,重写自己需要的回调方法即可。

ChannelOutboundHandler

当业务处理完成后,需要操作Java NIO底层通道时,通过一系列的 ChannelOutboundHandler出站处理器完成Netty通道到底层通道的操作, 比如建立底层连接、断开底层连接、写入底层Java NIO通道等。

再强调一下,Netty出站处理的方向是通过上层Netty通道去操作底 层Java IO通道,主要出站(Outbound)的操作如下:

(1)bind() 监听地址(IP+端口)绑定:完成底层Java IO通道的IP地址绑定。 如果使用TCP传输协议,这个方法用于服务端。

(2)connect() 连接服务端:完成底层Java IO通道的服务端的连接操作。如果使用 TCP传输协议,那么这个方法将用于客户端。

(3)write() 写数据到底层:完成Netty通道向底层Java IO通道的数据写入操 作。此方法仅仅是触发一下操作,并不是完成实际的数据写入操作。

(4)flush() 将底层缓存区的数据腾空,立即写出到对端。

(5)read () 从底层读数据:完成Netty通道从Java IO通道的数据读取。

(6)disConnect() 断开服务器连接:断开底层Java IO通道的socket连接。如果使用 TCP传输协议,此方法主要用于客户端。

(7)close() 主动关闭通道:关闭底层的通道,例如服务端的新连接监听通道。

上面介绍的并不是ChannelOutboundHandler的全部方法,仅仅介绍了其中几个比较重要的方法。在Netty中,它的默认实现为 ChannelOutboundHandlerAdapter。在实际开发中,只需要继承 ChannelOutboundHandlerAdapter默认实现,重写自己需要的方法即可。

ChannelInitializer

在前面已经讲到,Channel和Handler业务处理器的关系是:一条 Netty的通道拥有一条Handler业务处理器流水线,负责装配自己的 Handler业务处理器。装配Handler的工作发生在通道开始工作之前。

现在的问题是:如果向流水线中装配业务处理器呢?这就得借助通道的初始化处理器——ChannelInitializer。

initChannel()方法是ChannelInitializer定义的一个抽象方法,这个抽象方 法需要开发人员自己实现。

在通道初始化时,会调用提前注册的初始化处理器的initChannel() 方法。比如,在父通道接收到新连接并且要初始化其子通道时,会调用初始化器的initChannel()方法,并且会将新接收的通道作为参数,传递给此方法。

一般来说,initChannel()方法的大致业务代码是:拿到新连接通道作为实际参数,往它的流水线中装配Handler业务处理器。