Netty源码(八)之新客户端接入的过程

前面几篇博客我已经介绍整个服务端的启动的过程,主要就是服务端的Reactor线程的启动流程,一些信息的初始化过程,这篇博客我主要介绍客户端的接入的过程,主要还是看服务端的Reactor的线程中干的事,主要的代码如下,我们继续重温一下,具体的代码如下:

public final class NioEventLoop extends SingleThreadEventLoop {
  //事件循环
  @Override
  protected void run() {
    for (;;) {
      try {
        try {
          //hasTasks()  若taskQueue or  tailTasks任务队列中有任务  返回true  没有则返回false
          //有任务返回selectNow的返回值   没任务返回-1
          switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {
            case SelectStrategy.CONTINUE:
              continue;
            case SelectStrategy.BUSY_WAIT:
              // fall-through to SELECT since the busy-wait is not supported with NIO
            case SelectStrategy.SELECT:
              //首先轮询注册到reactor线程对应的selector上的所有的channel的IO事件
              //wakenUp 表示是否应该唤醒正在阻塞的select操作,netty在每次进行新的loop之前,都会将wakeUp 被设置成false,标志新的一轮loop的开始
              select(wakenUp.getAndSet(false));
              if (wakenUp.get()) {
                selector.wakeup();
              }
              // 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();
          handleLoopException(e);
          continue;
        }
        cancelledKeys = 0;
        needsToSelectAgain = false;
        final int ioRatio = this.ioRatio;
        if (ioRatio == 100) {
          try {
            processSelectedKeys();
          } finally {
            // Ensure we always run tasks.
            runAllTasks();
          }
        } else {
          final long ioStartTime = System.nanoTime();
          try {
            //2.处理产生网络IO事件的channel
            processSelectedKeys();
          } finally {
            // Ensure we always run tasks.
            final long ioTime = System.nanoTime() - ioStartTime;
            //3.处理任务队列
            runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
          }
        }
      } catch (Throwable t) {
        handleLoopException(t);
      }
      // Always handle shutdown even if the loop processing threw an exception.
      try {
        if (isShuttingDown()) {
          closeAll();
          if (confirmShutdown()) {
            return;
          }
        }
      } catch (Throwable t) {
        handleLoopException(t);
      }
    }
  }
}

客户端的接入的代码,主要在processSelectedKeys();方法中,我们继续跟进去看看对应的代码,具体的代码如下:

public final class NioEventLoop extends SingleThreadEventLoop {
  private void processSelectedKeys() {
    if (selectedKeys != null) {
      processSelectedKeysOptimized();
    } else {
      processSelectedKeysPlain(selector.selectedKeys());
    }
  }
}

上面的代码能进,这个时候我们需要了解客户端的接入的过程,所以这儿应该selectedKeys不为空,所以会进入processSelectedKeysOptimized();方法中去。但是最上面的Reactor线程是一个死循环,所以有时候客户端没有连接的服务端,这个方法还是有可能进的,这个时候selectedKeys的就是为空,所以会进入下面的方法processSelectedKeysPlain(selector.selectedKeys());当我们打开对应的方法的时候发现和processSelectedKeysOptimized();方法差不多,无非多了一个判断。所以在这我们只需要跟进processSelectedKeysOptimized();方法即可,具体的代码如下:

public final class NioEventLoop extends SingleThreadEventLoop {
  private void processSelectedKeysOptimized() {
    //遍历selectedKeys
    for (int i = 0; i < selectedKeys.size; ++i) {
      final SelectionKey k = selectedKeys.keys[i];
      // null out entry in the array to allow to have it GC'ed once the Channel close
      // See https://github.com/netty/netty/issues/2363
      selectedKeys.keys[i] = null;
       //获取对应的附加对象
      final Object a = k.attachment();
      //由于我们前面添加的附加对象就是AbstractNioChannel
      if (a instanceof AbstractNioChannel) {
        processSelectedKey(k, (AbstractNioChannel) a);
      } else {
        @SuppressWarnings("unchecked")
        NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;
        processSelectedKey(k, task);
      }
      if (needsToSelectAgain) {
        // null out entries in the array to allow to have it GC'ed once the Channel close
        // See https://github.com/netty/netty/issues/2363
        selectedKeys.reset(i + 1);
        selectAgain();
        i = -1;
      }
    }
  }
}

上面的代码遍历selectedKeys,先获取对应的selectedKeys上的附加对象,由于我们之前添加的附加对象就是AbstractNioChannel,所以这儿的判断是直接会进的,我们直接跟进对应的方法processSelectedKey(k, (AbstractNioChannel) a);具体的代码如下:

public final class NioEventLoop extends SingleThreadEventLoop {
  private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
    final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
    // k.isValid()告知此键是否有效。
    if (!k.isValid()) {
      final EventLoop eventLoop;
      try {
        eventLoop = ch.eventLoop();
      } catch (Throwable ignored) {
        return;
      }
      if (eventLoop != this || eventLoop == null) {
        return;
      }
      // close the channel if the key is not valid anymore
      unsafe.close(unsafe.voidPromise());
      return;
    }
    try {
      //获取此键的 ready 操作集合。
      int readyOps = k.readyOps();
      if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
        int ops = k.interestOps();
        ops &= ~SelectionKey.OP_CONNECT;
        k.interestOps(ops);
        unsafe.finishConnect();
      }
      if ((readyOps & SelectionKey.OP_WRITE) != 0) {
        ch.unsafe().forceFlush();
      }
      if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
        unsafe.read();
      }
    } catch (CancelledKeyException ignored) {
      unsafe.close(unsafe.voidPromise());
    }
  }
}

上面走来会判断这个键是否有效,如果有效直接执行k.readyOps();获取已经准备好的事件,这个判断是什么事件,由于服务端我们只注册了OP_ACCEPT事件,所以这儿我们会进unsafe.read();方法,具体的代码如下:

public abstract class AbstractNioMessageChannel extends AbstractNioChannel {
  private final class NioMessageUnsafe extends AbstractNioUnsafe {
    private final List<Object> readBuf = new ArrayList<Object>();
    @Override
    public void read() {
      assert eventLoop().inEventLoop();
      //获取配置的信息
      final ChannelConfig config = config();
      //获取对应的pipeline
      final ChannelPipeline pipeline = pipeline();
      //类似ByteBuf
      final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle();
      //设置对应的配置
      allocHandle.reset(config);
      boolean closed = false;
      Throwable exception = null;
      try {
        try {
          do {
            //这个时候会执行这个方法
            int localRead = doReadMessages(readBuf);
            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));
        }
        readBuf.clear();
        allocHandle.readComplete();
        pipeline.fireChannelReadComplete();
        if (exception != null) {
          closed = closeOnReadError(exception);
          pipeline.fireExceptionCaught(exception);
        }
        if (closed) {
          inputShutdown = true;
          if (isOpen()) {
            close(voidPromise());
          }
        }
      } finally {
        if (!readPending && !config.isAutoRead()) {
          removeReadOp();
        }
      }
    }
  }
}

上面的代码会先来获取对应的配置的信息,以及对应的pipeline的信息,然后将获取到的配置信息设置到ByteBuf,最后会执行doReadMessages(readBuf);方法,我们继续跟进对应的代码

public class NioServerSocketChannel extends AbstractNioMessageChannel
                             implements io.netty.channel.socket.ServerSocketChannel {
  @Override
  protected int doReadMessages(List<Object> buf) throws Exception {
    //调用accept方法,同意连接
    SocketChannel ch = SocketUtils.accept(javaChannel());
    try {
      if (ch != null) {
        buf.add(new NioSocketChannel(this, ch));
        return 1;
      }
    } catch (Throwable t) {
      logger.warn("Failed to create a new channel from an accepted socket.", t);
      try {
        ch.close();
      } catch (Throwable t2) {
        logger.warn("Failed to close a socket.", t2);
      }
    }
    return 0;
  }
}

上面的代码会调用底层的accept的方法,这个获取的是客户端的socket,这个时候将客户端的socket封装成一个NioSocketChannel,当我们打开对应的代码,发现和我们之前创建NioServerSocketChannel差不多,这儿就不细说,可以看我之前写的博客,唯一不同的应该就是Unsafe的值不同了。这时候创建的UnsafeNioByteUnsafe,其他都差不多,创建的pipeline也是一样呢的,头节点是HeadContext,尾节点是TailContext。这个时候会将这个创建好的NioSocketChannel添加对应传进来的List集合中去。最后返回的1,我们再回到原来对应的代码地方,具体的代码如下:

public abstract class AbstractNioMessageChannel extends AbstractNioChannel {
  private final class NioMessageUnsafe extends AbstractNioUnsafe {
    private final List<Object> readBuf = new ArrayList<Object>();
    @Override
    public void read() {
      //省略一部分代码
      Throwable exception = null;
      try {
        try {
          do {
            //这个时候会执行这个方法
            int localRead = doReadMessages(readBuf);
            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));
        }
        readBuf.clear();
        allocHandle.readComplete();
        pipeline.fireChannelReadComplete();
        if (exception != null) {
          closed = closeOnReadError(exception);
          pipeline.fireExceptionCaught(exception);
        }
        if (closed) {
          inputShutdown = true;
          if (isOpen()) {
            close(voidPromise());
          }
        }
      } finally {
        if (!readPending && !config.isAutoRead()) {
          removeReadOp();
        }
      }
    }
  }
}

这个时候假设我们对应的事件遍历完成后,这个循环就会结束,这个时候会遍历原来添加了NioSocketChannel的集合,这个时候由于有客户端连接进来了,所以这个集合不会为空,这个时候会执行pipeline.fireChannelRead(readBuf.get(i));方法,记住这儿获取的是服务端的pipeline,这个方法是向下传播,我们跟进去看看

public class DefaultChannelPipeline implements ChannelPipeline {
  @Override
  public final ChannelPipeline fireChannelRead(Object msg) {
    //在新连接接入时 msg是NioSocketChannel
    AbstractChannelHandlerContext.invokeChannelRead(head, msg);
    return this;
  }
}

这个方法熟悉不,就是去执行头节点的channelRead方法,我们继续跟进对应invokeChannelRead(head, msg);方法,具体的代码如下:

abstract class AbstractChannelHandlerContext implements ChannelHandlerContext, ResourceLeakHint {
  static void invokeChannelRead(final AbstractChannelHandlerContext next, Object msg) {
    final Object m = next.pipeline.touch(ObjectUtil.checkNotNull(msg, "msg"), next);
    EventExecutor executor = next.executor();
    if (executor.inEventLoop()) {
      next.invokeChannelRead(m);
    } else {
      executor.execute(new Runnable() {
        @Override
        public void run() {
          next.invokeChannelRead(m);
        }
      });
    }
  }
  private void invokeChannelRead(Object msg) {
    if (invokeHandler()) {
      try {
        ((ChannelInboundHandler) handler()).channelRead(this, msg);
      } catch (Throwable t) {
        notifyHandlerException(t);
      }
    } else {
      fireChannelRead(msg);
    }
  }
}

这个时候会真正的去执行channelRead(this, msg);方法,我们跟进去看看对应的代码,具体的代码如下:

public class DefaultChannelPipeline implements ChannelPipeline {
  final class HeadContext extends AbstractChannelHandlerContext
    implements ChannelOutboundHandler, ChannelInboundHandler {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
      System.out.println(this.getClass() + " HeadContext.channelRead");
      ctx.fireChannelRead(msg);
    }
  }
}

这个时候又执行一波向下传播,其实代码和服务端的都是差不多,我们这儿假设我们之前没有往服务端中添加新的pipeline,这个时候就会调用ServerBootstrapAcceptor类中channelRead方法,具体的代码如下

public class ServerBootstrap extends AbstractBootstrap<ServerBootstrap, ServerChannel> {
  private static class ServerBootstrapAcceptor extends ChannelInboundHandlerAdapter {
    @Override
    @SuppressWarnings("unchecked")
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
      final Channel child = (Channel) msg;
      //将我们之前传入的pipeline设置到NioSocketChannel中去。
      child.pipeline().addLast(childHandler);
      //将options的参数也设置进去
      setChannelOptions(child, childOptions, logger);
      //将attr属性也设置进去
      for (Entry<AttributeKey<?>, Object> e : childAttrs) {
        child.attr((AttributeKey<Object>) e.getKey()).set(e.getValue());
      }
      try {
        //workGroup
        childGroup.register(child).addListener(new ChannelFutureListener() {
          @Override
          public void operationComplete(ChannelFuture future) throws Exception {
            if (!future.isSuccess()) {
              forceClose(child, future.cause());
            }
          }
        });
      } catch (Throwable t) {
        forceClose(child, t);
      }
    }
  }
}

上面将传进来的NioSocketChannel类设置好对应的pipelineoptions以及对应的attr,然后调用客户端的NioEventLoopregister的方法,我们继续跟进对应的方法,当我们打开对应的代码发现,和服务端的执行的流程是一样的,这个时候不得不感概Netty源码写的是真的好。这儿经过一连串的操作,创建了一个客户端的Reactor线程,服务端的感兴趣的事件注册是在调用doBind0方法的时候绑定的,那么这儿是在那绑定的?我们回到原来执行的方法的地方,继续跟进对应的代码,具体的代码如下:

public abstract class AbstractNioMessageChannel extends AbstractNioChannel {
  private final class NioMessageUnsafe extends AbstractNioUnsafe {
    private final List<Object> readBuf = new ArrayList<Object>();
    @Override
    public void read() {
      //省略一部分代码
      Throwable exception = null;
      try {
        //省略一部分代码
        readBuf.clear();
        allocHandle.readComplete();
        //执行服务端pipeline中ChannelReadComplete方法
        pipeline.fireChannelReadComplete();
        if (exception != null) {
          closed = closeOnReadError(exception);
          pipeline.fireExceptionCaught(exception);
        }
        if (closed) {
          inputShutdown = true;
          if (isOpen()) {
            close(voidPromise());
          }
        }
      } finally {
        if (!readPending && !config.isAutoRead()) {
          removeReadOp();
        }
      }
    }
  }
}

到此整个主Reactor线程就结束,但是我们工作Reactor的线程已经启动,但是没有注册到感兴趣的事件,这个时候需要我们继续看代码,由于调用的代码都是一样,所以前面我就直接不细说了,我们查看pipeline.fireChannelReadComplete();方法,注意这儿是工作的Reactor线程调用的方法。我们继续跟进对应的代码,具体的代码如下:

public class DefaultChannelPipeline implements ChannelPipeline {
  @Override
  public final ChannelPipeline fireChannelReadComplete() {
    AbstractChannelHandlerContext.invokeChannelReadComplete(head);
    return this;
  }
}

abstract class AbstractChannelHandlerContext implements ChannelHandlerContext, ResourceLeakHint {
  static void invokeChannelReadComplete(final AbstractChannelHandlerContext next) {
    EventExecutor executor = next.executor();
    if (executor.inEventLoop()) {
      next.invokeChannelReadComplete();
    } else {
      Tasks tasks = next.invokeTasks;
      if (tasks == null) {
        next.invokeTasks = tasks = new Tasks(next);
      }
      executor.execute(tasks.invokeChannelReadCompleteTask);
    }
  }
  private void invokeChannelReadComplete() {
    if (invokeHandler()) {
      try {
        ((ChannelInboundHandler) handler()).channelReadComplete(this);
      } catch (Throwable t) {
        notifyHandlerException(t);
      }
    } else {
      fireChannelReadComplete();
    }
  }
}

public class DefaultChannelPipeline implements ChannelPipeline {
  final class HeadContext extends AbstractChannelHandlerContext
    implements ChannelOutboundHandler, ChannelInboundHandler {
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) {
      System.out.println(this.getClass() + " HeadContext.channelReadComplete");
      ctx.fireChannelReadComplete();
      //调用对应的添加到感兴趣的事件的方法
      readIfIsAutoRead();
    }
  }
}

上面的代码和服务端是一样的,都是经过一连串的调用,调用每个pipelinechannelReadComplete();我们要找到的是添加感兴趣的事件,对应添加感兴趣的事件就是readIfIsAutoRead();我们继续跟进对应的代码,具体的代码如下:

public class DefaultChannelPipeline implements ChannelPipeline {
  private void readIfIsAutoRead() {
    if (channel.config().isAutoRead()) {
      channel.read();
    }
  }
}

public abstract class AbstractChannel extends DefaultAttributeMap implements Channel {
  @Override
  public Channel read() {
    pipeline.read();
    return this;
  }
}

public class DefaultChannelPipeline implements ChannelPipeline {
  @Override
  public final ChannelPipeline read() {
    tail.read();
    return this;
  }
}

abstract class AbstractChannelHandlerContext implements ChannelHandlerContext, ResourceLeakHint {
  @Override
  public ChannelHandlerContext read() {
    final AbstractChannelHandlerContext next = findContextOutbound(MASK_READ);
    EventExecutor executor = next.executor();
    if (executor.inEventLoop()) {
      next.invokeRead();
    } else {
      Tasks tasks = next.invokeTasks;
      if (tasks == null) {
        next.invokeTasks = tasks = new Tasks(next);
      }
      executor.execute(tasks.invokeReadTask);
    }
    return this;
  }
  
  private void invokeRead() {
    if (invokeHandler()) {
      try {
        ((ChannelOutboundHandler) handler()).read(this);
      } catch (Throwable t) {
        notifyHandlerException(t);
      }
    } else {
      read();
    }
  }
}

public class DefaultChannelPipeline implements ChannelPipeline {
  final class HeadContext extends AbstractChannelHandlerContext
    implements ChannelOutboundHandler, ChannelInboundHandler {
    @Override
    public void read(ChannelHandlerContext ctx) {
      unsafe.beginRead();
    }
  }
}

public abstract class AbstractChannel extends DefaultAttributeMap implements Channel {
  @Override
  public final void beginRead() {
    assertEventLoop();
    if (!isActive()) {
      return;
    }
    try {
      doBeginRead();
    } catch (final Exception e) {
      invokeLater(new Runnable() {
        @Override
        public void run() {
          pipeline.fireExceptionCaught(e);
        }
      });
      close(voidPromise());
    }
  }
}

public abstract class AbstractNioChannel extends AbstractChannel {
  @Override
  protected void doBeginRead() throws Exception {
    // Channel.read() or ChannelHandlerContext.read() was called
    final SelectionKey selectionKey = this.selectionKey;
    if (!selectionKey.isValid()) {
      return;
    }
    readPending = true;
    final int interestOps = selectionKey.interestOps();
    if ((interestOps & readInterestOp) == 0) {
      selectionKey.interestOps(interestOps | readInterestOp);
    }
  }
}

上面的代码经过一系列的调用终于调用到注册感兴趣的事件了。至此整个Netty的工作流程就讲完了,下篇博客我会介绍一下Netty中内置的pipeline,最后总结一下整个Netty的工作流程。具体如下图:

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值