Loading

[08] 服务端启动流程解析

1. 服务端启动示例

@Slf4j
public class NettyServer {

  public static void main(String[] args) {
    NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
    NioEventLoopGroup workerGroup = new NioEventLoopGroup();
    ServerBootstrap serverBootstrap = new ServerBootstrap();
    try {
      serverBootstrap = serverBootstrap
          .group(bossGroup, workerGroup)
          .channel(NioServerSocketChannel.class)
          .handler(new SimpleServerHandler())
          .childHandler(new ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel ch) throws Exception {}
          });
      ChannelFuture f = serverBootstrap.bind(6677).sync();
      f.channel().closeFuture().sync();
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      bossGroup.shutdownGracefully();
      workerGroup.shutdownGracefully();
    }
  }

  private static class SimpleServerHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
      log.info("channelActive...");
    }

    @Override
    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
      log.info("channelRegistered...");
    }

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
      log.info("handlerAdded...");
    }
  }
}

上面写了一个比较完整的服务端启动例子,绑定在 6677 端口,使用 NIO 模式。我们再来回顾以下每个方法的作用:

  1. EventLoopGroup:服务端的线程模型外观类。从字面意思可以了解到,Netty 的线程模式是「事件驱动型」的,也就是说,这个线程要做的事情就是不停地检测 IO 事件、处理 IO 事件、执行任务,不断重复这三个步骤;
  2. ServerBootStrap:服务端的一个启动辅助类。通过给它设置一些列参数来绑定端口启动服务;
  3. .group(bossGroup, workerGroup):设置服务端的线程模型。可以先想象以下:在一个工厂里,我们需要两种类型的人干活,一种是老板,一种是工人。老板负责从外面接活,把接到的活分配给工人。放到这里,bossGroup 的作用就是不断地接收新的连接,将新连接交给 workerGroup 来处理;
  4. .channel(NioServerSocketChannel.class):设置服务端的 IO 类型为 NIO。Netty 通过指定 Channel 的类型来指定 IO 类型。Channel 在 Netty 里是一大核心概念,可以先简单理解为:一个 Channel 就是一个连接或者一个服务端 bind 动作;
  5. .handler(new SimpleServerHandler()):表示在服务端启动过程中,需要经历哪些流程。这里 SimpleServerHandler 最终的顶层接口是 ChannelHandler,是 Netty 的一大核心概念,表示数据流经过的处理器,可以理解为流水线上的每一道关卡;
  6. .childHandler(new ChannelInitializer<SocketChannel>() {...}:这里的方法体主要用于设置一系列 Handler 来处理每个连接的数据,也就是上面所说的,老板接到一个活之后,告诉每个工人这个活的固定步骤;
  7. ChannelFuture f = serverBootstrap.bind(6677).sync():绑定端口同步等待。这里就是真正的启动过程了,绑定端口 6677,等服务端启动完毕,才会进入下一行代码;
  8. f.channel().closeFuture().sync():等待服务端关闭端口绑定,这里的的作用其实是让程序不会退出;
  9. bossGroup.shutdownGracefully()workerGroup.shutdownGracefully():关闭两组事件循环,关闭之后,main 方法就结束了。

最终控制台的输出如下:

handlerAdded...
channelRegistered...
channelActive...

2. 服务端启动的核心步骤

在上面的示例代码中,是通过 ServerBootstrap 这个辅助类来实现服务端启动的,给这个启动类设置一些参数,然后通过它的〈外观接口〉来实现启动。重点落在这行代码上:serverBootstrap.bind(6677).sync()

public ChannelFuture bind(int inetPort) {
  // 通过端口号创建一个 InetSocketAddress 对象,然后继续调用重载方法 bind()
  return bind(new InetSocketAddress(inetPort));
}

public ChannelFuture bind(SocketAddress localAddress) {
  // 验证服务启动需要的必要参数
  validate();

  // ...

  // ======== ↓ Step Into ↓ ========
  return doBind(localAddress);
}

private ChannelFuture doBind(final SocketAddress localAddress) {
  // ======== ↓ Step Into ↓ ========
  final ChannelFuture regFuture = initAndRegister();

  final Channel channel = regFuture.channel();

  // ...

  // =====> 4. 绑定服务端端口
  doBind0(regFuture, channel, localAddress, promise);

  return promise;

}

final ChannelFuture initAndRegister() {
  Channel channel = null;

  // =====> 1. 创建服务端 Channel
  channel = channelFactory.newChannel();

  // =====> 2. 初始化服务端 Channel
  init(channel);

  // =====> 3. 注册服务端 Channel
  ChannelFuture regFuture = config().group().register(channel);

  // ...

  return regFuture;
}

initAndRegister() 中的 3 个主要方法与 doBind0() 方法一起组合成了服务端启动的 4 个过程。

  1. 创建服务端 Channel;
  2. 初始化服务端 Channel;
  3. 注册服务端 Channel;
  4. 绑定服务端端口;

接下来,详细分析这 4 个过程。

3. 创建服务端 Channel

我们首先需要了解一下 Channel 的定义,Netty 官方对 Channel 的描述如下:

A nexus to a network socket or a component which is capable of I/O operations such as read, write, connnect, and bind.
可以把 Channel 理解为一个网络连接,或者具有 IO 操作能力(读、写、连接、绑定)的组件。

这里的 Channel,由于是在服务启动的时候创建的,可以和 Socket 编程中的 ServerSocket 对应,也就是上述 Netty 官方定义中的 IO 组件的概念。


通过前面的分析,我们已经知道 Channel 是通过 ChannelFactory 创建出来的,ChannelFactory 的接口很简单。

public interface ChannelFactory<T extends Channel> extends io.netty.bootstrap.ChannelFactory<T> {
    @Override
    T newChannel();
}

AbstractBootstrap

public B channel(Class<? extends C> channelClass) {
    // ...
    return channelFactory(new ReflectiveChannelFactory<C>(channelClass));
}

在本节的示例程序中,我们调用 serverBootstrap.channel(NioServerSocketChannel.class) 方法,将 NioServerSocketChannel 作为 ReflectiveChannelFactory 的构造方法的参数创建出一个 ReflectiveChannelFactory。

然后回到本节最开始创建 Channel 的代码。

channel = channelFactory.newChannel();

ReflectiveChannelFactory

public class ReflectiveChannelFactory<T extends Channel> implements ChannelFactory<T> {

  private final Class<? extends T> clazz;

  public ReflectiveChannelFactory(Class<? extends T> clazz) {
    // ...
    this.clazz = clazz;
  }

  @Override
  public T newChannel() {
    // ...
    return clazz.getConstructor().newInstance();
    // ...
  }

  // ...
}

看到 ReflectiveChannelFactory#newChannel 具体实现就明白了,就是通过反射的方式来创建一个对象,而这个 class 就是我们在 ServerBootstrap 中传入的 NioServerSocketChannel.class。

所以,绕了一圈,最终创建 Channel 相当于调用默认构造函数创建一个 NioServerSocketChannel 对象。

3.1 创建 JDK 底层 Channel

接下来分析 NioServerSocketChannel 的默认构造函数,看一下创建 NioServerSocketChannel 的细节。

private static final SelectorProvider DEFAULT_SELECTOR_PROVIDER = SelectorProvider.provider();

public NioServerSocketChannel() {
    this(newSocket(DEFAULT_SELECTOR_PROVIDER));
}

private static ServerSocketChannel newSocket(SelectorProvider provider) {
    // ...
    return provider.openServerSocketChannel();
}

通过 SelectorProvider.openServerSocketChannel() 创建一个 ServerSocketChannel 对象,这个对象就是 JDK 领域的对象。

3.2 创建 Channel 配置类

接下来,Netty 把上述对象继续传递到以下方法。

public NioServerSocketChannel(ServerSocketChannel channel) {
    super(null, channel, SelectionKey.OP_ACCEPT);
    config = new NioServerSocketChannelConfig(this, javaChannel().socket());
}

这里第一行代码就跑到父类里了,第二行代码创建一个 NioServerSocketChannelConfig,其顶层接口为 ChannelConfig,Netty 官方的描述如下。

A set of configuration properties of a Channel.

可以猜到,ChannelConfig 也是 Netty 里的一个基本组件,不必深挖,只要记住,这个对象在创建 NioServerSocketChannel 对象的时候被创建即可。

3.3 设置 Channel 类型为非阻塞

我们继续追踪 NioServerSocketChannel 的父类。

AbstractNioMessageChannel

private final SelectableChannel ch;
protected final int readInterestOp;
volatile SelectionKey selectionKey;

protected AbstractNioMessageChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
    super(parent, ch, readInterestOp);
}

protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
    super(parent);
    this.ch = ch;
    this.readInterestOp = readInterestOp;
    // ...
    ch.configureBlocking(false);
    // ...
}

这里简单地将前面 provider.openServerSocketChannel() 创建出来的 ServerSocketChannel 保存到成员变量 ch,然后调用 ch.configureBlocking(false) 设置该 Channel 为非阻塞模式,写过 Java NIO 都懂-_-。

readInterestOp,即前面层层传入的 SelectionKey.OP_ACCEPT,表示这个服务端 Channel 关心的是 ACCEPT 事件,即处理新连接的接入。

3.4 创建 Channel 核心组件

接下来重点分析 super(parent),这里的 parent 其实是 null。

protected AbstractChannel(Channel parent) {
    this.parent = parent;
    id = newId();
    unsafe = newUnsafe();
    pipeline = newChannelPipeline();
}

在这个构造方法中,我们看到 Netty 创建了三大组件,分别赋值到成员变量。

(1)ChannelId

protected ChannelId newId() {
    return DefaultChannelId.newInstance();
}

id 是 Netty 中每条 Channel 的唯一标识,类似 Snowflake 算法,通过机器号、进程号、时间戳、随机数等方式生成。

(2)Unsafe

protected abstract AbstractUnsafe newUnsafe();

Unsafe operations that should never be called from user-code. These methods are only provided to implement the actual transport, and must be invoked from an I/O thread.

Unsafe 是 Netty 的又一核心概念,这里只需要知道 newUnsafe() 是抽象方法,最终调用的是 NioServerSocketChannel 或者其父类中对应的方法即可。

(3)ChannelPipeline

protected DefaultChannelPipeline newChannelPipeline() {
    return new DefaultChannelPipeline(this);
}

查看顶层接口 ChannelPipeline 的如下定义。

A list of ChannelHandlers which handles or intercepts inbound events and outbound operations of a Channel.

从该类的文档中可以看到,该接口也是 Netty 的一大核心组件,它包含了一个 ChannelHandler 链表,用于处理或者拦截 Channel 的 Inbound 事件和 outbound 操作。

3.5 小结

到这里,我们总算把服务端 Channel 的创建流程梳理完毕了。将这些细节串起来的时候,我们顺带提取出 Netty 的几大基本组件。

  • Channel
  • ChannelConfig
  • ChannelId
  • Unsafe
  • Pipeline
  • ChannelHandler

用于调用方法 bind(6677) 的第一步就是通过反射的方式创建了一个 NioServerSocketChannel(即服务端 Channel 对象),并且在创建过程中创建了一系列核心组件。

4. 初始化服务端 Channel

服务端 Channel 创建完成之后,就进入了初始化 Channel 的流程。

@Override
void init(Channel channel) throws Exception {
  // 1. 设置服务端Channel的Option与Attr

  // 2. 设置客户端Channel的Option与Attr

  // 3. 配置服务端启动逻辑
}

ServerBootstrap 的这个 init 方法有点长,这里就不展示出来了。下面是拆解步骤。

4.1 设置服务端 Channel 的 Option 与 Attr

final Map<ChannelOption<?>, Object> options = options0();
synchronized (options) {
    setChannelOptions(channel, options, logger);
}

final Map<AttributeKey<?>, Object> attrs = attrs0();
synchronized (attrs) {
    for (Entry<AttributeKey<?>, Object> e: attrs.entrySet()) {
        @SuppressWarnings("unchecked")
        AttributeKey<Object> key = (AttributeKey<Object>) e.getKey();
        channel.attr(key).set(e.getValue());
    }
}

关于 options0() 及 attrs0() 的作用,可以回顾服务端启动的 option() 和 attr() 方法。

通过上面的代码可以看到,这里先调用 options0() 及 attrs0(),然后将得到的 options 和 attrs 注入 ChannelConfig 或者 Channel,这里的 options0() 和 attrs0() 得到的对象都是我们在服务端启动流程的示例代码中自行设置的。

4.2 设置客户端 Channel 的 Option 与 Attr

final EventLoopGroup currentChildGroup = childGroup;
final ChannelHandler currentChildHandler = childHandler;
final Entry<ChannelOption<?>, Object>[] currentChildOptions;
final Entry<AttributeKey<?>, Object>[] currentChildAttrs;
synchronized (childOptions) {
    currentChildOptions = childOptions.entrySet().toArray(newOptionArray(childOptions.size()));
}
synchronized (childAttrs) {
    currentChildAttrs = childAttrs.entrySet().toArray(newAttrArray(childAttrs.size()));
}

这里和上面类似,只不过保存的是新连接对应的 Option 和 Attr,我们需要记住,自定义的这两个属性集合分别保存在两个成员变量中。

4.3 配置服务端启动逻辑

ChannelPipeline p = channel.pipeline();

p.addLast(new ChannelInitializer<Channel>() {
    @Override
    public void initChannel(final Channel ch) throws Exception {
        // 添加用户自定义 Handler
        final ChannelPipeline pipeline = ch.pipeline();
        ChannelHandler handler = config.handler();
        if (handler != null) {
            pipeline.addLast(handler);
        }

        // 添加一个特殊的 Handler,用于接收新连接
        ch.eventLoop().execute(new Runnable() {
            @Override
            public void run() {
                pipeline.addLast(new ServerBootstrapAcceptor(
                                        ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
            }
        });
    }
});

到了最后一步 —— pipeline.addLast(),用于定义服务端启动过程中需要执行哪些逻辑。在上述代码中,我们看到,Netty 把服务端启动过程中需要执行的逻辑分为两块,一块是添加用户自定义的处理逻辑到服务端启动流程,另一块是添加一个特殊的处理逻辑。

我们重点分析第二块逻辑。从名字上就可以看出来,ServerBootstrapAcceptor 是一个接入器,接受新请求,把新的请求传递到某个事件循环器。

4.4 小结

其实 init 并没有启动服务,只是初始化了一些基本配置和属性,以及在服务端的启动逻辑中加入了一个接入器,用来专门接受新请求。

5. 注册服务端 Channel

这步,我们先来分析这个方法:config().group().register(channel)

SingleThreadEventLoop

@Override
public ChannelFuture register(Channel channel) {
    return register(new DefaultChannelPromise(channel, this));
}

@Override
public ChannelFuture register(final ChannelPromise promise) {
    ObjectUtil.checkNotNull(promise, "promise");
    promise.channel().unsafe().register(this, promise);
    return promise;
}

我们跟进 AbstractChannel 内部的 AbstractUnsafe 类里。

@Override
public final void register(EventLoop eventLoop, final ChannelPromise promise) {
    // ...
    AbstractChannel.this.eventLoop = eventLoop;
    // ...
    register0(promise);
    // ...
}

这里依旧只专注于重点代码,先将 EventLoop 事件循环器绑定到服务端 Channel NioServerSokcetChannel 上,然后调用 register0()。

private void register0(ChannelPromise promise) {
  boolean firstRegistration = neverRegistered;
  // 1. 调用 JDK 底层注册 Selector
  doRegister();
  neverRegistered = false;
  registered = true;

  // 2. 回调 handlerAdded 事件
  // Ensure we call handlerAdded(...) before we actually notify the promise. This is needed
  // as the user may already fire events through the pipeline in the ChannelFutureListener.
  pipeline.invokeHandlerAddedIfNeeded();
  safeSetSuccess(promise);

  // 3. 传播 channelRegistered 事件
  pipeline.fireChannelRegistered();

  // 4. 其他逻辑
  // ...
}

5.1 调用 JDK 底层注册 Selector

AbstractNioChannel

@Override
protected void doRegister() throws Exception {
  boolean selected = false;
  for (;;) {
    try {
      selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
      return;
    } catch (CancelledKeyException e) {
      if (!selected) {
        // Force the Selector to select now as the "canceled" SelectionKey may still be
        // cached and not removed because no Select.select(..) operation was called yet.
        eventLoop().selectNow();
        selected = true;
      } else {
        // We forced a select operation on the selector before but the
        // SelectionKey is still cached for whatever reason. JDK bug ?
        throw e;
      }
    }
  }
}

在这个步骤中,我们可以看到关于 JDK 底层的操作:

首先拿到在前面过程中创建的 JDK 底层的 Channel,然后调用 JDK 的 register() 方法,将 this 也即 NioServerSocketChannel 对象当作 attachment 绑定到 JDK 的 Selector 上,这样后续从 Selector 拿到对应的事件之后,就可以把 Netty 领域的 Channel 拿出来。

5.2 回调 handlerAdded 事件

接着调用 invokeHandlerAddedIfNeeded(),于是,控制台打印出来的第一行内容如下。

handlerAdded...

关于具体是如何调用的,后面剖析 Pipeline 再说。

5.3 传播 channelRegistered 事件

调用 pipeline.fireChannelRegistered() 之后,控制台的显示如下。

handlerAdded...
channelRegistered...

同样,我们后面再详细分析与事件传播相关的逻辑。

5.4 其他逻辑

// Only fire a channelActive if the channel has never been registered. This prevents
// firing multiple channel actives if the channel is deregistered and re-registered.
if (isActive()) {
    if (firstRegistration) {
        // ===== 是这行代码触发的 active 事件吗? =====
        pipeline.fireChannelActive();
    } else if (config().isAutoRead()) {
        // This channel was registered before and autoRead() is set. This means
        // we need to begin read again so that we process inbound data.
        beginRead();
    }
}

读到这,你可能会想当然地以为,控制台上最后一行内容是由下面这行代码输出的。

pipeline.fireChannelActive();

这里不妨先看一下 isActive() 方法。

@Override
public boolean isActive() {
    return javaChannel().socket().isBound();
}

从目前的流程来看,我们并没有将一个 ServerSocket 绑定到一个 address 上,所以 isActive() 返回 false,不会调用 pipeline.fireChannelActive() 方法,那么最后一行内容到底是谁输出的呢?

  1. 先在最终输出字符串的这一行代码处打一个断点,然后 Debug,执行到这一行,IDEA 自动拉起了调用栈(左下)。我们唯一要做的,就是移动方向键,看到函数完整的调用栈;
  2. 如果回溯到的调用方法在是 Runnable#run 里,那么可以先去掉其他断点了,然后在提交 Runnable 对象方法的地方打一个断点,重新 Debug(右上)。

我们想看最初的调用,就得跳出来,断点打到 if (!wasActive && isActive()),因为 Netty 里很多任务执行都是异步线程调用的,如果我们要查看最先发起的方法调用,就必须查看 Runnable 被提交的地方,逐次递归下去,就能找到那行消失的代码。

最终,通过这种方式,找到了 pipeline.fireChannelActive() 发起调用的代码,刚好就是下面的服务端启动流程中最后一个步骤中的 doBind0() 方法(右下)。接下来我们就暂时挂起 active() 事件的传播解析,进入服务端启动的最后一个过程。

5.5 小结

服务端启动在这一步做的事,就是把前面创建的 JDK 的 Channel 注册到 Selector,并且把 Netty 领域的 Channel 当作一个 attachment 绑定上去,同时回调 handlerAdded 和 channelRegistered 事件。

6. 绑定服务端端口

private static void doBind0(
    final ChannelFuture regFuture, final Channel channel,
    final SocketAddress localAddress, final ChannelPromise promise) {

  // This method is invoked before channelRegistered() is triggered.  Give user handlers
  // a chance  to set up the pipeline in its channelRegistered() implementation.
  channel.eventLoop().execute(new Runnable() {
    @Override
    public void run() {
      // ...
      channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
      // ...
    }
  });
}

我们发现,在调用 doBind0() 方法的时候,是通过包装一个 Runnable 进行异步化的,至于为什么这么做,我们在后续分析 Netty 的线程模型时会介绍。

接下来进入 channel.bind() 方法。

AbstractChannel

@Override
public ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) {
    return pipeline.bind(localAddress, promise);
}

发现调用的是 pipeline 的 bind() 方法。

DefaultChannelPipeline

@Override
public final ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) {
    return tail.bind(localAddress, promise);
}

// ... 省略中间调用过程 ... -> DefaultChannelPipeline$HeadContext

@Override
public void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) throws Exception {
    unsafe.bind(localAddress, promise);
}

这里的 Unsafe 就是前面提到的 AbstractUnsafe,准确的说,是 NioMessageUnsafe。进入它的 bind() 方法:

@Override
public final void bind(final SocketAddress localAddress, final ChannelPromise promise) {
  // ...

  boolean wasActive = isActive();

  // 1. 调用 JDK 底层绑定端口
  doBind(localAddress);

  // ...

  // 2. 传播 channelActive 事件
  if (!wasActive && isActive()) {
    invokeLater(new Runnable() {
      @Override
      public void run() {
        pipeline.fireChannelActive();
      }
    });
  }

  safeSetSuccess(promise);
}

这段代码也可以分为两个步骤:① 实际绑定端口 ② 传播 active 事件

6.1 调用 JDK 底层绑定逻辑

doBind() 方法也很简单。

NioServerSocketChannel

@Override
protected void doBind(SocketAddress localAddress) throws Exception {
  if (PlatformDependent.javaVersion() >= 7) {
    javaChannel().bind(localAddress, config.getBacklog());
  } else {
    javaChannel().socket().bind(localAddress, config.getBacklog());
  }
}

在这里,Netty 甚至区分了不同版本的 JDK 方法。最终,执行完这里的几行代码后,本地对应的端口就可以接收新连接了。

6.2 传播 channelActive 事件

绑定完端口后,还会调用 pipeline.fireChannelActive() 方法,继续跟进。

DefaultChannelPipeline

@Override
public final ChannelPipeline fireChannelActive() {
    AbstractChannelHandlerContext.invokeChannelActive(head);
    return this;
}

AbstractChannelHandlerContext

static void invokeChannelActive(final AbstractChannelHandlerContext next) {
    EventExecutor executor = next.executor();
    if (executor.inEventLoop()) {
        // ===== ↓ Step Into ↓ =====
        next.invokeChannelActive();
    } else {
        executor.execute(() -> next.invokeChannelActive());
    }
}

private void invokeChannelActive() {
    if (invokeHandler()) {
        // [1] head:HeadContext
        // [2] NettyServer$SimpleServerHandler
        ((ChannelInboundHandler) handler()).channelActive(this);
    } else {
        fireChannelActive();
    }
}

@Override
public ChannelHandlerContext fireChannelActive() {
    invokeChannelActive(findContextInbound());
    return this;
}

HeadContext

@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
    ctx.fireChannelActive();

    readIfIsAutoRead();
}

这里的 ctx.fireChannelActive() 继续往下传播 active 事件,最终就会传播到示例代码 SimpleServerHandler 中的 channelActive 方法。至此,控制台的所有输出都得到了合理的解释。

但是,我们注意到,传播完 active 事件之后,还有一行代码 readIfIsAutoRead()。这行代码是做什么的呢?

6.3 注册 ACCEPT 事件

进入 readIfIsAutoRead 方法。

HeadContext

private void readIfIsAutoRead() {
    if (channel.config().isAutoRead()) {
        channel.read();
    }
}

DefaultChannelConfig

private volatile int autoRead = 1;

@Override
public boolean isAutoRead() {
    return autoRead == 1;
}

由此可见,isAutoRead 方法默认返回 true,于是进入以下方法。

AbstractChannel

@Override
public Channel read() {
    pipeline.read();
    return this;
}

省略中间的调用逻辑,最终会调用如下代码块:

AbstractNioChannel

@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(); // 0
    if ((interestOps & readInterestOp) == 0) {
        selectionKey.interestOps(interestOps | readInterestOp);
    }
}

这里的 this.selectionKey 就是我们在前面 register 步骤返回的对象。我们在 register 的时候,注册 ops 的值为 0,表示此时还不关注任何事件,只是建立绑定关系而已。回忆一下注册过程(AbstractNioChannel):

selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);

这里相当于把注册过的 ops 取了出来,通过了 if 条件,然后调用:

selectionKey.interestOps(interestOps | readInterestOp);

而这里的 readInterestOp 就是前面 newChannel 的时候传入的 SelectionKey.OP_ACCEPT,所以,在这一部分代码中,Netty 实际想做的就是,告诉 JDK 的 Selector,现在一切工作就绪,就差把 ACCEPT 事件注册到 Selector 上了。

6.4 小结

绑定服务端端口,最终会调用 JDK 绑定的 API 去进行实际绑定。绑定成功之后,会传播 channelActive 事件。最终,Netty 会把 ACCEPT 事件注册到 Selector 上。这样,后续就可以通过 Selector 监测新连接的接入,从而做一些处理。

7. 总结

  1. 创建 JDK 底层 Channel,创建对应 Config 对象,设置该 Channel 为非阻塞模式;
  2. 创建 Server 对应的 Channel,创建各大组件,包括 ChannelConfig、ChannelId、ChannelPipeline、ChannelHandler、Unsafe 等;
  3. 初始化 Server 对应的 Channel,设置 Option、Attr,以及设置子 Channel 的 Option、Attr,给 Server 的 Channel 添加连接接入器 ServerBootstrapAcceptor,用于接收新连接,并触发 addHandler、register 等事件;
  4. 调用 JDK 底层注册 Selector,将 Netty 领域的 Channel 当作 attachment 注册到 Selector;
  5. 调用 JDK 底层做端口绑定,并触发 active 事件。当 active 事件被触发,才真正做服务端口绑定。
posted @ 2022-04-12 08:02  tree6x7  阅读(143)  评论(0编辑  收藏  举报