Netty服务端创建

时间:2024-04-06 07:06:12

1、Netty服务端创建时序图

Netty服务端创建

分步详细说明:

1)创建ServerBootstrap实例。ServerBootstrap是Netty服务端的启动辅助类,她提供了一系列的方法用于设置服务端启动相关的参数。

2)设置并绑定Reactor线程池。Netty的Reactor线程池是 EventLoopGroup,它实际上是EventLoop的数组。Eventloop的职责是处理所有注册到本线程多路复用器Selector上的Channel,Selector的轮询操作是由绑定的Eventloop线程run方法驱动,在一个循环体内循环执行。Eventloop的职责不仅仅是处理网络I/O事件,用户自定义的task和定时任务task也统一由Eventloop负责处理。

3)设置并绑定服务端Channel。作为NIO服务端,需要创建ServerSocketChannel,Netty对原生的NIO类库进行了封装,对应实现是NioServerSocketChannel。

Netty服务端创建

Netty服务端创建

4)链路建立的时候创建并初始化ChannelPipeline。ChannelPipeline并不是NIO服务端必须的,它本质就是一个负责处理网络事件的职责链,负责管理和执行ChannelHandler的执行策略调度ChannelHandler的执行。典型的网络事件如下:

①链路注册

②链路**

③链路断开

④接受到请求消息

⑤请求消息接受并处理完毕

⑥发送应答消息

⑦链路发生异常

⑧发送用户自定义事件

5)初始化ChannelPipeline完成之后,添加并设置ChannelHandler。ChannelHandler是Netty提供给用户定制和扩展的关键接口。利用ChannelHandler用户可以完成大多数的功能定制,例如消息编解码、心跳、安全认证、TSL/SSL认真、浏览控制和流量整形等。实用的系统ChannelHandler总结如下:

①系统编解码框架——ByteToMessageCodec

②通用基于长度的半包解码器——LengthFieldBasedFrameDecoder

③码流日志打印Handler——LoggingHandler

④SSL安全认证Handler——SslHandler

⑤链路空闲坚持Handler——IdleStateHandler

⑥流量整形Handler——ChannelTrafficShapingHandler

⑦Base64编码器——Base64Encoder和Base64Decoder

Netty服务端创建

6)绑定并启动监听端口。在绑定监听端口之前系统会做一系列的初始化和检测工作,完成之后,会启动监听端口,并将ServerSocketChannel注册到Selector上监听客户端连接。

Netty服务端创建

7)Selector轮询。由Reactor线程NioEventloop负责调度和执行Selector轮询操作,选择准备就绪的Channel集合。

Netty服务端创建

8)当轮询到准备就绪的Channel之后,就由Reactor线程NioEventloop执行ChannelPipeline的相应方法,最终调度并执行ChannelHandler。

Netty服务端创建

9)执行Netty系统ChannelHandler和用户添加定制的ChannelHandler。ChannelPipeline根据网络事件的类型,调度并执行CahnnelHandler。

Netty服务端创建

 

 

2、Netty服务端创建源码分析

首先通过构造函数创建ServerBootstrap实例,随后,通常会创建两个EventLoopGroup

Netty服务端创建

NioEventLoopGroup实际上就是Reactor线程池,负责调度和执行客户端的接入、网络读写事件的处理、用户自动以任务和定时任务的执行。通过ServerBootstrap的group方法将两个EventLoopGroup实例传入,

Netty服务端创建

其中父NioEventLoopGroup被传入了父类构造函数中,

Netty服务端创建

该方法会被客户端和服务端重用,用于设置工作I/O线程,执行和调度网络事件的读写。

线程组和线程类型设置完成后,需要设置服务端Channel用于端口监听和客户端链路接入。Netty通过Channel工厂类来创建不同类型的Channel,对于服务端,需要创建NioServerSocketChannel。所以,通过制定Channel类型的方式创建Channel工厂。

Netty服务端创建

Netty服务端创建

 

指定NioServerSocketChanne后,需要设置tcp的一些参数,作为服务端,主要是要设置tcp的backlog参数。

backlog指定了内核为此套接口排队的最大连接个数,对于给定的监听套接口,内核要维护两个队列:未连接队列和已连接队列,根据TCP三路握手过程中三个分节来分隔这两个队列。服务器处于listen状态时,收到客户端syn分节时在未完成队列中创建一个新的条目,然后用三路握手的第二个分节即服务器的syn响应客户端,此条目在第三个分节到;达前一直保留在未完成连接队列中,如果三路握手完成,该条目将从未未完成连接队列搬到已完成队列尾部。当进程调用accep时,从已完成队列中的头部取出一个条目给进程,当已完成队列为空时进程将睡眠,知道有条目在已完成队列中才唤醒。backlog被规定为两个对垒总和的最大值,大多数实现默认值为5,但是高并发web服务器中此值显然不够,Lighttpd中此值达到128*8.需要设置此值更大一些的原因是未完成连接队列的长度可能因为客户端syn的到达及等待三路握手第三个分节的到达延时而增大。Netty默认的backlog为100,当然,用户可以修改默认值,这需要根据实际场景和网络状况进行灵活设置。

TCP参数设置完成后,用户可以为启动辅助类和其父类分别指定Handler。两类Handler的用途不同:

子类中的Handler是客户端新接入的连接NioSocketChannel对应的ChannelPipeline的Handler; 父类中的Handler是客户端新接入的连接SocketChannel对应ChannelPipeline的Handler。

两者的区别如下

Netty服务端创建

本质区别就是:ServerBootstrap中的Handler是NioServerSocketChannel使用的,所有连接该监听端口的客户端都会执行它;父类AbstractBootstrap中的Handler是个工厂类,它为每个新接入的客户端创建一个新的Handler。

服务端启动的最后一步,就是绑定本地端口,启动服务。

Netty服务端创建

NO.1。首先创建Channel,createChannel由子类ServerBootstrap实现,创建新的NioServerSocketChannel。它有两个参数:参数1是从父类的NIO线程池中顺序获取一个NioEventLoop,它就是服务端用于监听和接收客户端的Reactor线程;参数2是所谓的workerGroup线程池,它就是处理I/O读写的Reactor线程组,

Netty服务端创建

NioServerSocketChannel创建成功后,对它进行初始化,初始化工作主要有以下三点。

1)设置Socket参数和NioServerSocketChannel的附加属性

2)将AbstractBootstrap的Handler添加到NioServerSocketChannel的ChannelPipeline中

3)将用于服务端注册的Handler ServerBootstrapAcceptor添加到ChannelPipeline中。代码如下

Netty服务端创建

注册NioServerSocketChannel到Reactor线程的多路复用器上,然后轮询客户端连接事件。

NioServerSocketChannel的注册。当NioServerSocketChannel初始化完成之后,需要将它注册到Reactor线程的多了复用器上监听新客户端的接入,

Netty服务端创建

首先判断是否是NioEventLoop自身发起的操作。如果是,则不存在并发操作,直接执行Channel注册;如果由其他线程发起,则封装成一个task放入消息队列中异步执行,此处,由于是由ServerBootstrap所在线程执行的注册操作,所有会将其封装成task投递到NioEventLoop中执行,

Netty服务端创建

将NioServerSocketChannel注册到NioEventLoop的Selector上,

Netty服务端创建

应该注册OP_ACCEPT(16)到多路复用器上,怎么参数是0?0表示只注册,不监听任网络操何作。这样做的原因如下:

1)注册方法是多态的,它既可以被NioServerSocketChannel用来监听客户端的连接接入,也可以注册SocketChannel用来监听网络读或者写操作;

2)通过SelectionKey的interestOps(int ops)方法可以方便地修改监听操作位。所以,此处注册需要获取SelectionKey并给AbstractNioChannel的成员变量selectionKey赋值。

注册成功之后,触发ChannelRegistered事件,

Netty服务端创建

当ChannelRegistered事件传递完成之后,判断ServerSocketChannel监听是否成功,如果成功,需要出发NioServerSocketChannel的ChannelActive事件,

Netty服务端创建

isActive()也是个多态方法。如果是服务端,判断监听是否启动;如果是客户端,判断TCP连接是否成功。ChannelActive事件在ChannelPipeline中传递,完成之后根据配置决定是否自动触发Channel的读操作,

Netty服务端创建

AbatractChannel的读操作触发ChannelPipeline的读操作,最终调用到TailContext的读方法,

Netty服务端创建

继续看AbatractUnsafe的beginRead方法,

Netty服务端创建

由于不同类型的Channel对读操作的准备工作不同,因此,beginRead也是个多态方法,对于NIO通信,无论是客户端还是服务端,都是要修改监听操作位为自身感兴趣的,对于NioServerSocketChannel感兴趣的操作是OP_ACCEPT(16),于是重新修改注册的操作位为OP_ACCEPT,

Netty服务端创建

在某些场景下,当前监听的操作类型和Channel关系的网络事件是一致的,不需要重复注册,所以增加了&操作的判断,只有两者不一致,才需要重新注册操作位。

JDK SelectionKey有4中操作类型,分别是:

① OP_READ = 1 << 0;

② OP_WRITE = 1 << 2;

③ OP_CONNECT = 1 << 3;

④ OP_ACCEPT = 1 << 4;

由于只有4种网络操作类型,所以用4bit就可以表示所有的网络操作位,由于java语言没有bit'类型,所以使用了整型来表示,每个操作位代表一种网络操作类型,分别为0001,0010,0100,1000,这样做的好处是可以非常方便地通过位操作来进行网络操作位的状态判断和状态修改,从而提升操作性能。由于创建NioServerSocketChannel将readInterestOpt设置成了OP_ACCEPT,所以,在服务端链路注册成功之后重新将操作位设置为监听客户端的网络连接操作,初始化NioServerSocketChannel的代码如下:

Netty服务端创建

 

3、客户端接入源码分析

负责处理网络读写、连接和客户端请求接入的Reactor线程就是NioEventLoop,下面我们分析下NioEventLoop是复合处理新的客户端连接接入的。当多路复用器检测到新的准备就绪的Channel时,默认执行processSelectedKeysOptimized方法,

Netty服务端创建

由于Channel的Attachment是NioServerSocketChannel,所以执行processSelectedKey方法,根据就绪的操作位,执行不同的操作。此处,由于监听的是连接操作,所以执行unsafe.read()方法。由于不同的Channel执行不同的操作,所以NioUnsage被设计成接口,由不同的Channel内部的NioUnsafe实现类负责具体实现。我们发现read()方法的实现有两个,分别是NioByteUnsafe和NioMessageByteUnsafe。对于NioServerSocketChannel,它使用的是NioMessageUnsafe,它的read方法如下:

Netty服务端创建

对doReadMessages方法进行分析,发现它事件就是接收新的客户端连接并创建NioSocketChennel,

Netty服务端创建

接收到新的客户端连接后,触发ChannelPipeline的ChannelRead方法,代码如下:

Netty服务端创建

 

执行HeadContext的fireChannelRed方法,事件在ChannelPipeline中传递,执行ServerBootstrapAcceptor的channelRead方法,

Netty服务端创建

该方法主要 分为如下三个步骤。

第一步:将启动时传入的childHandler加入到客户端SocketChannel的ChannelPipeline中;

第二步:设置客户端SocketChannel的TCP参数;

第三步:注册SocketChanne到多路复用器。

下面来看一下NioSocketChannel的register方法,

Netty服务端创建

Netty服务端创建

 

 

NioSocketChannel的注册方法与ServerSocketChannel的一致,也是将Channel注册到Reactor线程的多路复用器上。由于注册的操作位是0,所以,此时NioSocketChannel还不能读取客户端发送的消息。执行完注册操作之后紧接着会触发ChannelReadComplete事件。我们继续分析ChannelReadComplete在ChannelPipeline中的处理流程:Netty的Header和Tail本身不关注ChannelReadComplete事件就直接透传,执行完ChannelReadComplete后,接着执行Pipeline的read()方法,最终执行HeadHandler的read()方法。

HeadHandler read()方法用来将网络操作位修改为读操作。创建NioSocketChannel的时候已经将AbstractNioChannel的readInterestOp设置为OP_READ,这样,执行selectionKey.interestOps(interestOps | interestOp)操作时就会把操作位这位OP_READ.

Netty服务端创建