欢送关注公众号: NullObject ,欢送扫码关注:

文章首发在集体博客 https://www.nullobject.cn,公众号NullObject同步更新。
本文基于 Java 8Netty 4.1.69.Final

Netty 是一个Java异步网络通信框架。Netty中基于 Java NIO 实现了异步IO通信,其中实现的NioServerSocketChannel/NioSocketChannel等相干的组件,实际上就是对Java NIO绝对组件的封装。本文将从Java NIO启动过程开始,对Netty中NIO Server的启动流程进行学习和剖析。

1.0 NIO Server启动流程

Netty中NioServerSocketChannel等组件是在Java NIO根底上封装的,因而,在开始学习Netty NIO启动流程之前,先理清Java NIO的应用步骤,以便接下来的剖析中更易于了解Netty Server启动和运行过程。

1.1 启动步骤概述

NIO Server 启动和初始化次要通过以下步骤:

  • 初始化选择器/多路复用器 Selector
  • 初始化服务器通道 ServerSocketChannel 并配置异步工作模式
  • 将ServerSocketChannel注册到Selector,获取对应的SelectionKey并注册须要关注的Accept事件
  • 绑定监听地址端口
  • 解决客户端连贯
  • 接收数据

启动流程图:

1.2 注册ServerSocketChannel

须要将服务端通道ssc注册到选择器selector上,并注册须要关注的事件:

mKey = mSSC.register(mSelector,0);mKey.interestOps(SelectionKey.OP_ACCEPT);

ServerSocketChannel#register 办法返回一个SelectionKey 对象,selectionKey对象用于示意以后SocketChannel及其关联的Selector的注册关系,外部持有SocketChannel和Selector单方对象援用,并负责保护以后通道关注的事件以及曾经产生/就绪事件信息; SelectionKey#interestOps 办法用于注册对应的Channel所须要关注的事件,可关注的事件封装在SelectionKey中:

  • OP_READ

    读事件 ,示意Selector检测到其所关联的Channel中 有可读事件产生 ,包含Channel数据读取就绪、已达到流末端、近程读取通道被敞开或Channel中产生谬误挂起等,随后Selector会将 OP_READ 事件增加到SelectionKey对象中,并标识该事件为就绪事件。

  • OP_WRITE

    写事件 ,示意Selector检测到其所关联的Channel中 有可写事件产生 ,包含Channel数据写出就绪、近程写入通道被敞开,或Channel中产生谬误挂起等,随后Selector会将 OP_WRITE 事件增加到对应的SelectionKey中,并标识该事件为就绪事件。

  • OP_CONNECT

    连贯事件,示意Selector检测到所关联的Channel中有连贯事件产生,包含Channel连贯结束就绪或Channel中产生谬误等,该事件通常产生在NIO Client胜利连贯服务器后,Selector会将OP_CONNECT 事件增加到对应的SelectionKey中,并标识该事件为就绪事件。

  • OP_ACCEPT

    承受事件 ,标识Selector检测到所关联的ServerSocketChannel已准备就绪承受新到来的客户端连贯申请,或通道中产生了谬误,随后Selector会将 OP_ACCEPT 事件增加到对应的SelectionKey中,并标识该事件为就绪事件。

1.3 Selector收集IO事件

Selector是NIO中的多路复用器,其能够同时治理多个IO通道。注册serverSocketChannel之后,开始循环调用selector.select()收集serverSocketChannel通道上产生的事件:

// 检测selector关联的所有Channel中已就绪的IO事件// count:产生的IO事件数量int count = mSelector.select();

select()办法会阻塞以后线程,直到收集到IO事件产生,才会持续运行。如果须要提前结束阻塞,能够调用其重载办法 mSelector.selector(interval) ,指定阻塞时长,当超过该时长没有收集到IO事件,则完结阻塞。或者在其余线程调用 mSelector.wakeup() 手动完结阻塞,继续执行,wakeup()执行后,select()办法会立刻返回。

1.4 解决客户端连贯申请

当检测到有 OP_ACCEPT 事件产生,示意有新的客户端连贯申请,须要调用 ServerSocketChannel#accept() 办法承受并建设客户端连贯,而后将该客户端连贯通道SocketChannel注册到Selector上,并注册读信息事件,以接管解决客户端发送的数据:

private void handleNewConnection(final SelectionKey key) throws IOException {    // 解决新接入的申请音讯    // Accept the new connection    ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();    // 承受新的连贯,创立SocketChannel示例,至此,相当于实现了TCP的三次握手,TCP物理链路正式建设。    SocketChannel socketChannel = serverSocketChannel.accept();    // 设置为异步非阻塞模式    socketChannel.configureBlocking(false);    // 将新连贯注册到多路复用器,关联一个bytebuffer附件以寄存接管到的数据    ByteBuffer buffer = ByteBuffer.allocate(INIT_READ_BUFFER_SIZE);    // Register the new connection to the selector    socketChannel.register(mSelector, SelectionKey.OP_READ, buffer);}

1.5 接收数据

当检测到 OP_READ 事件产生,示意通道中接管到对端发送的数据,或者与对方的连贯断开,这时能够从SocketChannel中尝试读取数据:

private void handleReadData(final SelectionKey key) throws IOException {    // Read the data    // 读取客户端的申请音讯    // 获取连贯通道实例    SocketChannel socketChannel = (SocketChannel) key.channel();    // 获取关联的buffer附件    ByteBuffer readBuffer = (ByteBuffer) key.attachment();    // 从TCP缓冲区读数据    int readBytes;    if ((readBytes = socketChannel.read(readBuffer)) > 0) {        // 解决读取到的数据        handleData(readBuffer);        // 决定是否须要扩容        if (readBuffer.position() == readBuffer.limit()) {            final ByteBuffer buffer = ByteBuffer.allocate(readBuffer.capacity() * 2);            readBuffer.flip();            buffer.put(readBuffer);            key.attach(buffer);        }    }    // 链路曾经敞开,此时须要敞开SocketChannel,开释资源    if (readBytes < 0) {        System.out.println("connection closed: " + socketChannel.getRemoteAddress());        // 对端链路敞开        key.cancel();        socketChannel.close();    }}

能够看到,相比于传统的Java BIO,NIO的应用过程要简单一些,但得益于其 同步非阻塞 的IO模型,使NIO解决高并发与海量连贯成为可能。而 Netty 对NIO做了进一步的优化和封装,简化了异步IO的开发流程。

理清了NIO的启动流程,接下来剖析Netty中对NIO每个关键步骤的封装和执行过程。

2.0 启动Netty Server

能够十分不便地启动一个Netty Server,通过简略的链式调用初始化相干组件,最初绑定端口即可启动Socket监听:

@Slf4jpublic class TestSourceServer {    public static void main(String[] args) {        final ServerBootstrap bootstrap = new ServerBootstrap();      // 执行IO事件的事件循环组        final NioEventLoopGroup bossGroup = new NioEventLoopGroup();      // 配置服务端启动器        bootstrap.group(bossGroup)                 .channel(NioServerSocketChannel.class)                 .childHandler(new ChannelInitializer<NioSocketChannel>() {                     @Override                     protected void initChannel(final NioSocketChannel ch) throws Exception {                         ch.pipeline().addLast(new LoggingHandler());                     }                 });      // 开始监听指定端口        bootstrap.bind(8080);    }}

ServerBootStrap 是Netty服务端的启动器,通过serverBootStrap对象的一系列链式调用初始化各个组件:

  • group(bossGroup) :配置用于执行IO事件/一般事件的事件循环组。
  • channel(NioServerSocketChannel.class) :配置须要应用的socket服务端组件,本文剖析的是NIO相干的启动流程,所以须要配置服务端组件为NioServerSocketChannel.class。
  • childHandler(initializer) :配置客户端连贯通道初始化器。服务端建设与客户端连贯通道SocketChannel后,Netty会通过调用链最终回调 initChannel 办法告诉用户socketChannel通道曾经创立好,此时用户能够在此对socketChannel进行一些业务上的初始化工作,比方设置音讯编解码器、增加日志处理器等。下面示例中,在每一次创立的客户端连贯通道中的事件处理管道上都增加了一个日志处理器LoggingHandler。

serverBootstrap对象在执行ind()办法绑定端口之前的所有链式调用,都是对服务端进行一些配置,真正启动服务端监听,是从执行bind()办法开始。

3.0 注册ServerSocketChannel

3.1 创立ServerSocketChannel对象

接下来以 debug 模式启动第1节中的示例,在bootstrap.bind(8080)一行开始执行,始终到 AbstractBootstrap#doBind() 办法( Line 272 ):

办法中第一行调用了一个initAndRegister()办法并返回一个ChannelFuture对象,从返回参数能够推断该办法外部执行了异步操作,并且依据办法名能够猜测,该办法内可能实现了 注册serverSocketChannel这一步骤。接着往下执行进入initAndRegister()办法一探到底:

能够看到,办法中先是通过 channelFactory 工厂对象创立了一个 channel对象Channel 是Netty中定义的形象接口,用于示意Netty中具备IO通信能力的组件,示例中的 NioServerSocketChannel 类便实现了Channel接口。接着往下执行,发现创立的channel对象正是在配置serverBootStrap时配置的NioServerSocketChannel类的实例:

在配置serverBootStrap步骤中,通过 bootstrap.channel(NioServerSocketChannel.class) 办法设置了实现服务端监听的具体组件,办法中创立了一个ReflectiveChannelFactory对象并将NioServerSocketChannel的类型信息传入,再将reflectiveChannelFactory对象赋值给 channelFactory 域,实现Server IO Channel 组件的配置:

而reflectiveChannelFactory对象中的newChannel的实现,则通过反射调用NioServerSocketChanel的无参构造方法,实现nioServerSocketChannel对象的创立。因而,接下来程序会运行跳转到NioServerSocketChannel类的无参构造方法:

NioServerSocketChannel类的构造方法中,又通过newSocket办法和调用了selectorProvider.openServerSocketChannel()办法,最终实现对Java NIO的 ServerSocketChannel 对象的创立:

这里间接通过selectorProvider.openServerSocketChannel()创立ServerSocketChannel对象,而在NIO启动步骤中调用ServerSocketChannel.open()创立的形式,其外部也是通过selectorProvider.openServerSocketChannel()办法来创立:

程序到这里, ①实现了创立ServerSocketChannel的步骤

3.2 执行channel注册

持续回到 abstractBootStrap.initAndRegister() 办法,获取到channel对象后,通过init(channel)办法对channel进行初始化,而后进行注册:

其中config().group()获取到的是在初始化serverBootStrap配置时设置的事件循环组对象bossGroup,config().group().register(channel)形象办法的实现位于NioEventLoopGroup的父类MultithreadEventLoopGroup中:

// MultithreadEventLoopGroup.java Line 85@Overridepublic ChannelFuture register(Channel channel) {  return next().register(channel);}

next()办法会从初始化bossGroup时创立的事件循环池(能够了解为线程池,一个事件循环对应一个线程,事件循环对应类是NioEventLoop)中抉择一个事件循环对象来执行register(channel)办法。nioEventLoop.register()形象办法的实现位于NioEventLoop的父类SingleThreadEventLoop类中:

接着运行到AbstractChannel$AbstractUnsafe.register办法 (AbstractChannel.java Line 465) ,而该办法最终又调用register0(promise)办法实现了注册的细节:

这里应用传入的eventLoop对象初始化了channel外部的eventLoop对象,并通过eventLoop.inEventLoop()办法判断是否将注册工作交由eventLoop事件循环对象来执行,理论就是判断eventLoop中的线程是否与以后线程为同一线程环境,来决定是间接调用 register0 办法,还是交给eventLoop。 留神 这里的eventLoop对象在nioBossGroup事件循环组对象创立时便曾经被创立进去,但eventLoop中所蕴含的子线程对象尚未被初始化,并没有真正启动线程,只有在第一次向eventLoop提交异步工作时,eventLoop才会初始化并启动线程,也就是将线程提早启动,这也是Netty中对应用线程资源的优化措施。接着进入 regster0 办法,外部又调用了 doRegster() 办法进行真正的注册:

通过 javaChannel() 获取后面步骤中创立的java NIO ServerSocketChannel对象,通过register办法将其注册到selector中,最初将以后channel对象作为附件保留到selectionKey(在后续解决IO事件中,会以从selectionKey获取selectionKey关联的channel对象),并将注册后果(SelectionKey)赋值给以后channl的selectionKey域。至此, ②Netty中ServerSocketChannel注册结束

4.0 创立Selector

在serverSocketChannel到selector时,看到selector对象是通过eventLoop().unwrappedSelector()获取的,那selector对象是何时创立的呢?首先通过eventLoop()获取以后channel对象的eventLoop对象,再通过eventLoop.unWrappedSelector()办法获取到nioEventLoop内的unwrappedSelector对象作为channel的selector,unwrappedSelector则在NioEventLoop中被初始化:先调用openSelector()并返回一个SelectorTuple对象,再通过selectorTuple.unwrappedSelector对unwrappedSelector进行赋值,openSelector()办法中,则最终调用了selectorProvider.openSelector()办法创立了selector,并将创立的selector包装到selectorTuple中返回:

其中provider就是SelectorProvider对象实例。至此, ③Selector创立实现

这里没有间接应用selectorProvider.openSelector()创立的selector,而是先将其封装了一个selectorTuple对象中再应用unwrappedSelector,是因为Netty在封装该步骤时做了性能上的一些优化,具体过程在NioEventLoop类的selector()办法实现中。

5.0 bind 开始监听

程序调用AbstractNioChannel.doRegister()办法实现serverSocketChannel注册后,接着开始绑定监听端口。回到AbstractChannel.register0()办法中,doRegister()执行结束,将registered标记为true,示意以后通道曾经注册结束,接着通过 pipeline.fireChannelRegistered() 办法将serverSocketChannel注册实现的事件发送到事件处理管道中以告诉下层代码解决。回到AbstractBootstrap.doBind()办法中,initAndRegister()执行完通道注册流程,doBind()持续向下运行,通过regFuture判断注册曾经实现还是在异步注册,不论如何,接下来都会持续调用doBind0办法进行端口绑定。doBind0办法中,则将绑定工作交给serverSocketChannel的事件循环来执行:

最终在AbstractChannel$AbstractUnsafe.bind办法中调用doBind实现端口绑定,NioServerSocketChannel继承了AbstractChannel并实现了doBind:

能够看到,NioServerSocketChannel.doBind实现中,调用了Java NIO ServerSocketChannel.bind()办法实现端口绑定, ④Netty Server绑定监听端口步骤至此实现

6.0 关注ACCEPT事件

AbstractChannel$AbstractUnsafe.bind()办法中实现端口绑定后,接着持续向像eventLoop中提交一个工作用于向serverSocketChannel的事件处理管道发送channelActive事件以告知下层代码解决:

pipeline.fireChannelActive()调用链中,执行到DefaultChannelPipeline&dollar;HeadContext.channelActive():

ctx.fireChannelActive()会将事件持续传递,而 readIfIsAutoRead() 往下运行则实现了注册OP_ACCEPT事件。readIfIsAutoRead()始终运行到AbstractNioChannel.doBeginRead()办法,并在该办法中实现注册OP_READ事件:

先从serverSocketChannel对应的selectionKey判断,如果channel尚未注册感兴趣的事件,则调用selectionKey.interestOps办法进行注册:

能够看到,未注册感兴趣事件前,channel以后注册的事件值为0,0示意未注册任何事件;serverSocketChannel须要注册的事件( readInterestOp域 )值未为 16 ,即对应OP_ACCEPT事件, readInterestOp 域在创立srverSocketChannel对象时通过向父类构造方法传入SelectionKey.OP_ACCEPT来赋值:

执行完doBeginRead, ⑤关注ACCEPT事件步骤实现。

至此,Netty NIO Server的启动和初始化过程完结,接下来开始监听和解决客户端连贯申请,接管客户端发送的数据等。

7.0 收集和解决IO事件

7.1 启动eventLoop线程

在NIO运行过程中,为了不阻塞主线程的运行,同时保障selector可能始终一直地收集到产生的IO事件,通常会将selector.select()和解决IO事件的代码放到子线程中执行。Netty中也是在子线程中收集和解决所产生的IO事件:将这些工作交给serverSocketChannel的eventLoop事件循环对象异步执行。上文中提到eventLoop在第一次执行工作时才会初始化外部线程对象并启动线程。回顾Netty的启动步骤,程序第一次向eventLoop提交工作是在serverSocketChannel的注册过程,进行真正注册阶段的代码,位于AbstractChannel&dollar;AbstractUnsafe.register()办法中( Line 483 ):

通过eventLoop.execute办法提交register0工作。这里eventLoop对象是NioEventLoop类的实例,其execute办法实现在NioEventLoop父类 SingleThreadEventExecutor 中:

再看公有的execute(runnable,boolean)办法,先是通过inEventLoop()办法判断提交工作的线程环境与以后事件循环的子线程环境是否为同一个事件循环的子线程。跟进去不难发现,首次提交工作时其实就是判断eventLoop外部线程是否曾经启动(因为线程还未启动,所以这里会返回false):

// SingleThreadEventExecutor(Line 558)// thread: Thread.currentThread()// this.thread: eventLoop外部线程,未执行过工作前,this.thread还未启动,this.thread值为null@Overridepublic boolean inEventLoop(Thread thread) {  return thread == this.thread;}

接着将工作增加到工作队列,判断如果以后eventLoop对象的线程尚未开始运行,则调用startThread()->doStartThread()办法对线程初始化:

能够看到,最终是通过eventLoop外部的线程池对象executor创立出线程,并赋值给eventLoop的thread对象,实现初始化工作,紧接着调用SingleThreadEventExecutor.run()办法,启动无线循环。NioEventLoop类中实现了SingleThreadEventExecutor.run()办法:

// NioEventLoop.java Line 435@Overrideprotected void run() {  int selectCnt = 0;  for (;;) {    try {      int strategy;      try {        strategy = selectStrategy.calculateStrategy(selectNowSupplier, hasTasks());        switch (strategy) {          case SelectStrategy.CONTINUE:            continue;          case SelectStrategy.BUSY_WAIT:            // fall-through to SELECT since the busy-wait is not supported with NIO          case SelectStrategy.SELECT:            long curDeadlineNanos = nextScheduledTaskDeadlineNanos();            if (curDeadlineNanos == -1L) {              curDeadlineNanos = NONE; // nothing on the calendar            }            nextWakeupNanos.set(curDeadlineNanos);            try {              if (!hasTasks()) {                strategy = select(curDeadlineNanos);              }            } finally {              // This update is just to help block unnecessary selector wakeups              // so use of lazySet is ok (no race condition)              nextWakeupNanos.lazySet(AWAKE);            }            // fall through          default:        }      } catch (IOException e) {        // If we receive an IOException here its because the Selector is messed up. Let's rebuild        // the selector and retry. https://github.com/netty/netty/issues/8566        rebuildSelector0();        selectCnt = 0;        handleLoopException(e);        continue;      }      selectCnt++;      cancelledKeys = 0;      needsToSelectAgain = false;      final int ioRatio = this.ioRatio;      boolean ranTasks;      if (ioRatio == 100) {        try {          if (strategy > 0) {            processSelectedKeys();          }        } finally {          // Ensure we always run tasks.          ranTasks = runAllTasks();        }      } else if (strategy > 0) {        final long ioStartTime = System.nanoTime();        try {          processSelectedKeys();        } finally {          // Ensure we always run tasks.          final long ioTime = System.nanoTime() - ioStartTime;          ranTasks = runAllTasks(ioTime * (100 - ioRatio) / ioRatio);        }      } else {        ranTasks = runAllTasks(0); // This will run the minimum number of tasks      }      if (ranTasks || strategy > 0) {        if (selectCnt > MIN_PREMATURE_SELECTOR_RETURNS && logger.isDebugEnabled()) {          logger.debug("Selector.select() returned prematurely {} times in a row for Selector {}.",                       selectCnt - 1, selector);        }        selectCnt = 0;      } else if (unexpectedSelectorWakeup(selectCnt)) { // Unexpected wakeup (unusual case)        selectCnt = 0;      }    } catch (CancelledKeyException e) {      // Harmless exception - log anyway      if (logger.isDebugEnabled()) {        logger.debug(CancelledKeyException.class.getSimpleName() + " raised by a Selector {} - JDK bug?",                     selector, e);      }    } catch (Error e) {      throw e;    } catch (Throwable t) {      handleLoopException(t);    } finally {      // Always handle shutdown even if the loop processing threw an exception.      try {        if (isShuttingDown()) {          closeAll();          if (confirmShutdown()) {            return;          }        }      } catch (Error e) {        throw e;      } catch (Throwable t) {        handleLoopException(t);      }    }  }}

7.2 收集IO事件

在子线程执行的有限循环中,eventLoop一直解决IO事件以及程序中提交到eventLoop队列中的非IO工作(NioEventLoop外部不仅会解决IO事件,同时还会执行一般工作:包含Netty程序外部提交的工作,或者用户提交的工作)。Netty NioEventLoop事件循环中解决IO事件,也是先调用selector收集产生的IO事件( 收集阶段 ),再遍历后果进行一一解决( 解决阶段 )。只不过因为Netty的封装和优化,这过程看起来要比Java NIO中的解决步骤要简单多一些。首先看 IO事件收集阶段 的执行过程,eventLoop中联合了selector的 select()select(interval)selectNow() 办法来实现收集IO事件,执行过程又分为以后 有一般工作没有一般工作 两种状况(通过hasTasks()判断)进行。

  • eventLoop中有一般工作待处理 :eventLoop工作队列以后有未解决的工作时,间接执行selector.selectNow()办法,selectNow()办法不论有没有IO事件产生,都会立刻返回收集后果,不会阻塞线程运行,这过程对应下图代码中的步骤 :selectStrategy.calculateStrategy办法中判断如果以后有未解决的一般工作,则调用selectNowSupplier.get()办法,该办法实现中调用了selector.selectNow()并返回收集后果,若没有待处理的一般工作,则将 SelectStrategy.SELECT返回赋值给strategy变量;

  • eventLoop工作队列中没有待处理工作: 这时程序会执行到swith的SELECT分支,对应上图中步骤 :此处又判断是否存在未解决的一般工作是因为提交和执行工作是异步执行,在步骤 执行到步骤 期间程序其余中央可能会提交工作进来。当工作队列仍旧为空时,调用select(interval)并返回收集后果给strategy变量。select(interval)办法进一步判断决定是执行selector.selectNow()还是selector.select(interval),selector.select(interval)执行如果在interval指定的工夫内没有事件产生则完结阻塞,返回后果:
// NioEventLoop.java Line 808private int select(long deadlineNanos) throws IOException {  if (deadlineNanos == NONE) {    return selector.select();  }  // Timeout will only be 0 if deadline is within 5 microsecs  long timeoutMillis = deadlineToDelayNanos(deadlineNanos + 995000L) / 1000000L;  return timeoutMillis <= 0 ? selector.selectNow() : selector.select(timeoutMillis);}

7.3 解决IO事件

从下面收集IO事件的过程能够看到, strategy 变量作用就是记录收集到的IO事件数量(strategy>0阐明有IO事件产生并收集到了)。接着到 IO事件处理阶段 ,对应的要害代码:

其中依据判断条件执行了processSelectedKeys()或runAllTasks()办法, processSelectedKeys()实现了接管和解决IO事件,runAllTask()则实现了执行eventLoop队列中的工作 。这里除了strategy,还有个要害的局部变量 ioRatio :因为解决IO事件是一个相当耗时的过程,为了均衡解决IO事件和执行队列工作的工夫,保障eventLoop队列中的非IO工作可能被执行,这段代码通过 ioRatio 变量来管制解决IO事件和执行队列工作所耗时间的比例。若本轮循环中收集到IO事件( strategy>0 ),则通过processSelectedKeys()一一解决收集到的IO事件。

7.3.1 解决连贯申请

processSelectedKeys() 办法的调用链中,会遍历产生的IO事件集,并交给 processSelectedKey(SelectionKey k, AbstractNioChannel ch) 办法解决:

先是获取到产生的IO事件,判断如果是OP_ACCEPT或是OP_READ事件则调用ch.unsafe().read()进行进一步解决。接下来在 processSelectedKey(SelectionKey k, AbstractNioChannel ch) 办法内打下断点,调试模式启动Netty Server端监听8080端口,并应用TCP客户端工具开启一个连贯申请:

点击开始连贯,返回Idea,发现程序曾经执行到办法第一行断点处,持续往下运行到 Line 718 处:

首先步骤 1 先获取IO通道对象ch的外部类NioUnsafe对象实例unsafe,留神此时serverSocketChannel接管到的该当是客户端连贯申请事件OP_ACCEPT(方才在TCP客户端点击发动了连贯申请),ch对应的是NioServerSocketChannel类, 此时ch对象示意的是服务端channel对象serverSocketChannel,对应的ch.unsafe()对象的实现位于NioServerSocketChannel的父类AbstractNioMessageChannel中 。接着步骤 2 获取SelectionKey中所有收集到的事件并赋值给readyOps变量,通过单步调试能够看到,此时readyOps的值是16,对应的正是OP_ACCEPT事件,因而程序会运行到 Line 719 ,执行unsafe.read()->doReadMessages(readBuf)解决客户端连贯申请:

SocketUtils.accept办法中实现了serverSocketChannel.accept()办法调用,承受客户端连贯申请并返回对应的NIO连贯通道对象,接着用NIO channel对象初始化Netty的NioSocketChannel客户端连贯通道对象,并将Netty socketChannel返回给下层调用。至此, ⑥承受客户端连贯申请,创立连贯通道步骤实现 。最初,将承受连贯事件通过pipeline.fireChannelRead办法发送至serverSocketChannel的事件处理管道,告诉下层程序和用户,能够对客户端连贯通道socketChannel做一些进一步的初始化工作(包含SocketChannel的注册和用户做的业务上的初始化内容)。接着往下执行,最终会回调到用户在初始化serverBootStrap时设置的childHandler对象办法中:

这里的NioSocketChannel对象,就是在承受客户端连贯时创立连贯通道。在这里通过ch.pipeline().addLast()办法,把自定义的handler增加到socketChanel的事件处理管道pipeline中,接下来便能够在handler中接管解决和发送音讯数据。

7.3.2 接管IO音讯

Netty Server承受并创立客户端连贯通道后,就能够相互收发音讯了。无关客户端连贯通道NioSocketChannel对象的创立和初始化,与服务端通道NioServerSocketChannel对象的初始化过程根本大致相同,这里不再赘述。从后面的步骤中晓得,eventLoop解决IO事件最终都会运行到 processSelectedKey(SelectionKey k, AbstractNioChannel ch) 办法来进行解决,在这个办法第一行打上断点,并服务端程序放弃运行,从TCP客户端发送字串“Hello”给服务器:

接着回到Idea会看到程序曾经运行断点处:

能够看到,这时的ch对象为客户端连贯通道NioSocketChannel的实例,收集到的IO事件值为1,对应OP_READ读事件(方才客户端向服务端发送了数据), 此时的unsafe.read()办法对应的实现类是位于NioSocketChannel父类AbstractNioByteChannel中的NioByteUnsafe类 ,接着运行能够看到,程序最初在NioSocketChannel.doReadBytes(byteBuf)办法中实现对socketChannel中数据的读取:

最初将读取到的音讯数据通过socketChannel.pipeline().fireChannelRead(byteBuf)办法将读到的数据传递给设置的handler处理器进行解决:

至此, ⑦Netty Server端接管并解决用户音讯步骤执行结束

8.0 小结

以上便是Netty Server从初始化配置,启动服务端,初始化NIO ServerSocketChannel、Selector等组件,监听端口,到承受建设客户端连贯通道,接管并解决客户端音讯的整个过程。本文的指标在于理清Netty Server启动和初始化的整体过程,文章中根据java NIO的启动流程对Netty Server的运行过程进行了梳理,整个剖析过程并没有太大难度。而文章中没有剖析阐明的诸如事件循环EventLoopGroup、EventLoop组件,Channel的事件处理管道Pipeline组件、事件处理器Handler相干组件、和音讯相干的Bytebuf组件等这些Netty的组成部分,是进一步学习了解Netty的要害,在后续对Netty的钻研中,将尝试对这些组件进行一一解读。