Netty接收请求源码剖析

Netty接收请求源码剖析

​ ——基于 Netty 4.1.39

文章目录

      • Netty接收请求源码剖析
        • 一、监听accept事件,接受连接 & 创建一个NioSocketChannel
          • 1、Debug processSelectedKeys()
          • 2、doReadMessages(List\ buf)
        • 二、将NioSocketChannel注册到workerGroup
          • 1、ServerBootstrapAcceptor.channelRead()
          • 2、SingleThreadEventLoop.register()
        • 三、监听NioSocketChannel的Read事件
          • 1、AbstractNioChannel.doBeginRead()
        • 四、总结

  • 在前面的服务器启动源码分析中,可以得知:服务器端的 NioServerSocketChannel 实例将自己注册到了 bossGroup 上(讲得更细一些,是 bossGroup 中 EventLoop 的 Selector 上),监听客户端连接。

  • Netty 服务端接收客户端连接请求的总体流程为:监听 Accept 事件,接受连接–>创建一个新的 NioSocketChannel–>将新的 NioSocketChannel 注册到 workerGroup 上–>监听 NioSocketChannel 上的 Read 事件。下面追踪代码来验证这一过程。

一、监听accept事件,接受连接 & 创建一个NioSocketChannel

  • 前面说过,NioEventLoop 中的run方法死循环,会不断执行以下三个过程:
    • select:轮训注册在其中的 Selector 上的 Channel 的 IO 事件。
    • processSelectedKeys:在对应的 Channel 上处理 IO 事件。
    • runAllTasks:再去以此循环处理任务队列中的其他任务。
1、Debug processSelectedKeys()

PS: (本次仅以服务端接收客户端请求角度分析)

以 Debug 模式启动 Server 端,然后将断点放在 NioEventLoop 中 run 方法里面死循环代码块的 processSelectedKeys()语句上。再以 Run 模式启动 客户端。追踪服务端代码的执行,过程如下:

Netty接收请求源码剖析_第1张图片

  • unsafe.read() 源码追踪:

    • 实际调用的是 AbstractNioMessageChannel$NioMessageUnsafe.read 方法。NioMessageUnsafe 是一个定义在 AbstractNioMessageChannel 中的内部类。

      // NioMessageUnsafe 定义
      private final class NioMessageUnsafe extends AbstractNioUnsafe {
      	// 可以看做存放请求数据的容器
          private final List<Object> readBuf = new ArrayList<Object>();
      
          @Override
          public void read() {
              //...
          }
      }
      
    • read() 源码:

      @Override
      public void read() {
          assert eventLoop().inEventLoop();//(1)该断言用于检查当前线程是否在该eventLoop中
          final ChannelConfig config = config();
          final ChannelPipeline pipeline = pipeline();
          final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle();
          allocHandle.reset(config);
      
          boolean closed = false;
          Throwable exception = null;
          try {
              try {
                  do {
                      int localRead = doReadMessages(readBuf);//(2)doReadMessages用于读取bossGroup中EventLoop中的NioServerSocketChannel接收到的请求数据,并且把这些请求数据放入readBuf。调用doReadMessages结束后,readBuf中存放了一个处理客户端后续请求的NioSocketChannel。
                      if (localRead == 0) {
                          break;
                      }
                      if (localRead < 0) {
                          closed = true;
                          break;
                      }
      
                      allocHandle.incMessagesRead(localRead);
                  } while (allocHandle.continueReading());
              } catch (Throwable t) {
                  exception = t;
              }
      
              int size = readBuf.size();
              for (int i = 0; i < size; i ++) {
                  readPending = false;
                  pipeline.fireChannelRead(readBuf.get(i));//(3)这个fireChannelRead会依次触发服务端的NioServerSocketChannel的pipeline中所有入站Handler中channelRead()方法的执行。例如LoggingHandler中channelRead方法的执行会打印出日志。同时这里会触发ServerBootstrapAcceptor的channelRead()方法。详细见2.2节内容。
              }
              readBuf.clear();
              allocHandle.readComplete();
              pipeline.fireChannelReadComplete();
      
              if (exception != null) {
                  closed = closeOnReadError(exception);
      
                  pipeline.fireExceptionCaught(exception);
              }
      
              if (closed) {
                  inputShutdown = true;
                  if (isOpen()) {
                      close(voidPromise());
                  }
              }
          } finally {
              // Check if there is a readPending which was not processed yet.
              // This could be for two reasons:
              // * The user called Channel.read() or ChannelHandlerContext.read() in channelRead(...) method
              // * The user called Channel.read() or ChannelHandlerContext.read() in channelReadComplete(...) method
              //
              // See https://github.com/netty/netty/issues/2254
              if (!readPending && !config.isAutoRead()) {
                  removeReadOp();
              }
          }
      }
      
      • (1)该断言用于检查当前线程是否在该eventLoop中。
      • (2)doReadMessages用于读取bossGroup中EventLoop中的NioServerSocketChannel接收到的请求数据,并且把这些请求数据放入readBuf。调用doReadMessages结束后,readBuf中存放了一个处理客户端后续请求的NioSocketChannel。
      • (3)这个fireChannelRead会依次触发服务端的NioServerSocketChannel的pipeline中所有入站Handler中channelRead()方法的执行。例如LoggingHandler中channelRead方法的执行会打印出日志。同时这里会触发ServerBootstrapAcceptor的channelRead()方法。详细见2.2节内容。
2、doReadMessages(List buf)
  • 定义:

    /**
    * Read messages into the given array and return the amount which was read.
    */
    protected abstract int doReadMessages(List<Object> buf) throws Exception;
    
  • 源码追踪:

    NioServerSocketChannel 实现了 doReadMessages(List buf) 方法。

    Netty接收请求源码剖析_第2张图片

    • SocketUtils.accept(javaChannel()):调用服务端ServerSocketChannel的accept()方法产生一个处理客户端后续请求的SocketChannel。
    • new NioSocketChannel(Channel parent, SocketChannel socket):将这个 SocketChannel 对象封装成 NioSocketChannel 对象,并且添加到buf容器中。封装的时候会添加 Pipeline 等成分,同时设置了 readInterestOp 等属性。参考前面通过 channelFactory.newChannel() 创建 NioServerSocketChannel 的过程。

    二、将NioSocketChannel注册到workerGroup

    前面提到 fireChannelRead() 方法会触发 ServerBootstrapAcceptor 的 channelRead() 方法。

    1、ServerBootstrapAcceptor.channelRead()
    • 源码追踪:

      Netty接收请求源码剖析_第3张图片

      • 在 ServerBootstrapAcceptor.channelRead() 方法中,会对每个客户端的 NioSocketChannel 进行初始化。可以结合 1.3.1 节的内容(服务端 NioServerSocketChannel 的初始化)进行比较。
    2、SingleThreadEventLoop.register()
    • 源码追踪:

      Netty接收请求源码剖析_第4张图片

      Netty接收请求源码剖析_第5张图片

      • 在 SingleThreadEventLoop.register() 方法中,会通过 register0() 方法注册到 workerGroup 其中一个 eventLoop 的 selector。(ops: 0 在这里猜测是ready的意思,因为后面会在 AbstractNioChannel.doBeginRead() 方法中真正设置key感兴趣的ops)。参考 1.3.1 节的内容(服务端 NioServerSocketChannel 的注册)。

    三、监听NioSocketChannel的Read事件

    前面提到会在 AbstractNioChannel.doBeginRead() 方法中真正设置NioSocketChannel对应的key感兴趣的ops。

    1、AbstractNioChannel.doBeginRead()
    • 源码追踪:

      Netty接收请求源码剖析_第6张图片

      • doBeginRead() 方法会在channel首次注册激活或者每次readComplete之后发生(如果开启了isAutoRead,默认是开启的)。需要注意的是,即使读事件发生的时候,readyOps是0,同样可以进行read。

        Netty接收请求源码剖析_第7张图片

    四、总结

    • (1)服务器端 bossGroup 中的 EventLoop 轮训 Accept 事件、获取事件后在 processSelectedKey() 方法中调用 unsafe.read()方法,这个 unsafe 是内部类 io.netty.channel.nio.AbstractNioChannel.NioUnsafe 的实例,unsafe.read()方法由两个核心步骤组成:doReadMessages()和 pipeline.fireChannelRead()。
    • (2)doReadMessages() 用于创建 NioSocketChannel 对象,包装了 JDK 的 SocketChannel 对象,并且添加了 pipeline、unsafe、config 等成分。
    • (3)pipeline.fireChannelRead() 用于触发服务端 NioServerSocketChannel 的所有入站 Handler 的 channelRead() 方法,在其中的一个类型为 ServerBootstrapAcceptor 的入站 Handler 的 channelRead() 方法中将新创建的 NioSocketChannel 对象注册到 workerGroup 中的一个 EventLoop 上,该 EventLoop 开始监听 NioSocketChannel 中的读事件。

    你可能感兴趣的:(Netty,java框架学习笔记,java,Netty)