Netty接收请求源码剖析

Netty接收请求源码剖析

​ ——基于 Netty 4.1.39

  • 在前面的服务器启动源码分析中,可以得知:服务器端的 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 模式启动 客户端。追踪服务端代码的执行,过程如下:

在这里插入图片描述

  • 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<Object> 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<Object> buf) 方法。

    在这里插入图片描述

    • 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()
  • 源码追踪:

    在这里插入图片描述

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

    在这里插入图片描述

    在这里插入图片描述

    • 在 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()
  • 源码追踪:

    在这里插入图片描述

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

      在这里插入图片描述

四、总结
  • (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 中的读事件。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值