浅谈Netty源码-服务端启动流程

472 阅读9分钟

前言

上一篇文章写了Netty中的各个核心组件,这些核心组件是如何协作的,下面我们来看看

服务端启动代码

先来看一段服务端启动的代码

public class NettyServer {
​
    public static void main(String[] args) throws InterruptedException {
​
        // 1.创建两个线程组 bossGroup 和 workerGroup
        // 2.bossGroup只是处理连接请求,真正的和客户端业务处理,会交给workerGroup完成
        // 3.两个都是无限循环
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
​
        try {
            // 创建服务器端的启动对象,配置参数
            ServerBootstrap bootstrap = new ServerBootstrap();
​
            // 使用链式编程来进行设置
            // 设置两个线程组
            bootstrap.group(bossGroup, workerGroup)
                    // 使用NioServerSocketChannel作为服务器的通道实现
                    .channel(NioServerSocketChannel.class)
                    // 设置线程队列得到连接个数
                    .option(ChannelOption.SO_BACKLOG, 128)
                    // 设置保持活动连接状态
                    .childOption(ChannelOption.SO_KEEPALIVE, true)
                    // 创建一个通道测试对象
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            socketChannel.pipeline().addLast(new NettyServerHandler());
                        }
                    });
​
            System.out.println("server is ready");
            // 绑定一个端口并且同步,生成一个ChannelFuture对象
            // 启动服务器(并绑定端口)
            ChannelFuture cf = bootstrap.bind(6668).sync();
            // 对关闭通道进行监听
            cf.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
​
    }
}

服务端参数设置

bootstrap.group(bossGroup, workerGroup)
public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) {
    // 对父类AbstractBootstrap中的group进行赋值
    super.group(parentGroup);
    if (this.childGroup != null) {
      throw new IllegalStateException("childGroup set already");
    }
    this.childGroup = ObjectUtil.checkNotNull(childGroup, "childGroup");
    return this;
}

bootstrap.group方法主要是对属性进行了赋值,在AbstractBootstrap中有group(bossGroup)的成员变量,ServerBootstrap中有childGroup(workerGroup)的成员变量,这两个group有点像我们平时的工作,bossGroup是老板,拼命从外面接活(不断接收新的连接),childGroup是工人,拼命加班干活(处理所连接客户端的业务操作)

bootstrap.channel(NioServerSocketChannel.class)

设置完老板和工人后,为了跟外界客户有比较良好的沟通,我们需要客服专员,bootstrap.channel就是设置客服专员,每当有新的连接请求,就会新建Channel,客户端和服务端通过Channel进行通信

public B channel(Class<? extends C> channelClass) {
    return channelFactory(new ReflectiveChannelFactory<C>(
      ObjectUtil.checkNotNull(channelClass, "channelClass")
    ));
}
public class ReflectiveChannelFactory<T extends Channel> implements ChannelFactory<T> {
​
    private final Constructor<? extends T> constructor;
​
    // 此构造器是对成员变量进行赋值
    public ReflectiveChannelFactory(Class<? extends T> clazz) {
        ObjectUtil.checkNotNull(clazz, "clazz");
        try {
            this.constructor = clazz.getConstructor();
        } catch (NoSuchMethodException e) {
            throw new IllegalArgumentException("Class " + StringUtil.simpleClassName(clazz) +
                    " does not have a public non-arg constructor", e);
        }
    }
​
    // 对成员变量进行实例化
    @Override
    public T newChannel() {
        try {
            return constructor.newInstance();
        } catch (Throwable t) {
            throw new ChannelException("Unable to create Channel from class " + constructor.getDeclaringClass(), t);
        }
    }
​
    @Override
    public String toString() {
        return StringUtil.simpleClassName(ReflectiveChannelFactory.class) +
                '(' + StringUtil.simpleClassName(constructor.getDeclaringClass()) + ".class)";
    }
}

查看源码,会发现bootstrap.channel(NioServerSocketChannel.class)使用工厂模式,后续使用newChannel方法即可通过反射方式构造出NioServerSocketChannel实例

bootstrap.option(ChannelOption.SO_BACKLOG, 128)
         .childOption(ChannelOption.SO_KEEPALIVE, true)

option()方法就是设置变量参数,option是设置父类AbstractBootstrap中的参数,childOption是设置ServerBootstrap中的参数,下方是设置option的代码,也是非常简单

public <T> B option(ChannelOption<T> option, T value) {
    ObjectUtil.checkNotNull(option, "option");
    synchronized (options) {
      if (value == null) {
        options.remove(option);
      } else {
        options.put(option, value);
      }
    }
    return self();
}
​
​
public <T> ServerBootstrap childOption(ChannelOption<T> childOption, T value) {
    ObjectUtil.checkNotNull(childOption, "childOption");
    synchronized (childOptions) {
      if (value == null) {
        childOptions.remove(childOption);
      } else {
        childOptions.put(childOption, value);
      }
    }
    return this;
}
bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
  @Override
  protected void initChannel(SocketChannel socketChannel) throws Exception {
      socketChannel.pipeline().addLast(new NettyServerHandler());
  }
});

此处也是设置ServerBootstrap中的参数

doBind

我们都知道NIO有三大组件,Buffer、Channel、Selector,Netty是对NIO做了一层包装,让NIO更简单易用,那么NIO的操作流程跟Netty理论上应该是类似的,在NIO中,Channel需要注册到Selector中,在上一篇文章中,我写了NioEventLoopGroup在初始化的过程中,每一个NioEventLoop都会创建一个Selector,在NioServerSocketChannel初始化的过程,会创建Channel和ChannelPipeline,那么Channel和Selector是怎么关联起来的,玄机就在bind方法中,对bind方法一路追踪,真正干活的是doBind方法

private ChannelFuture doBind(final SocketAddress localAddress) {
    // 新建一个Channel对象
    // 注意,此处是一个异步操作
    final ChannelFuture regFuture = initAndRegister();
    final Channel channel = regFuture.channel();
    // 判断该对象是否抛出异常
    if (regFuture.cause() != null) {
        return regFuture;
    }
    // 判断initAndRegister方法是否执行完毕
    if (regFuture.isDone()) {
        // At this point we know that the registration was complete and successful.
        ChannelPromise promise = channel.newPromise();
        // 进行doBind操作
        doBind0(regFuture, channel, localAddress, promise);
        return promise;
    } else {
        // 如果initAndRegister方法没有执行完毕,则增加一个监听器,等待执行完毕回调下面的方法,思路跟上面代码是一致的
        // 判断是否产生异常,如果没有异常则进行doBind操作
        final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel);
        regFuture.addListener(new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture future) throws Exception {
                Throwable cause = future.cause();
                if (cause != null) {
                    promise.setFailure(cause);
                } else {
                    promise.registered();
​
                    doBind0(regFuture, channel, localAddress, promise);
                }
            }
        });
        return promise;
    }
}

在这里需要特别说明的是initAndRegister()是一个异步方法,一个EventLoop在整个生命周期中只会与一个Thread进行绑定,Channel实际上就是注册到该EventLoop的Selector中,注册这个动作只能由与EventLoop绑定的Thread执行(为什么要这样设计呢?假设每个线程都可以执行这个操作,多线程的各种问题自然就出来了,Netty需要额外考虑各种加锁,解决多线程的问题),那Netty是怎么做到只能由绑定线程执行注册操作呢?我们在Netty的代码中可以看到比较多的ch.eventLoop().execute()这种类型的代码,如果执行代码的线程是绑定线程,那就直接执行即可,如果不是,则使用提交任务的方式,异步使该线程执行,既然是异步,就会有该任务是否执行完成的问题,所以Netty自定义了ChannelFuture来解决这个异步的问题(详情可见我上一篇文章),在doBind方法中,initAndRegister()是一个异步方法,后面会判断该方法的异步执行是否完成,如果完成,则直接执行doBind0方法,如果未执行完成,则等待监听器回调

final ChannelFuture initAndRegister() {
    Channel channel = null;
    try {
      // 上面bootstrap.channel(NioServerSocketChannel.class)设置参数的时候会设置ReflectiveChannelFactory
      // 专门生产什么样的对象,这里就是具体使用到的地方,使用反射的方式实例一个NioServerSocketChannel对象
      channel = channelFactory.newChannel();
      // 对Channel做初始化参数设置
      init(channel);
    } catch (Throwable t) {
      ......
    }
    // 将Channel注册到Selector上
    ChannelFuture regFuture = config().group().register(channel);
    if (regFuture.cause() != null) {
      if (channel.isRegistered()) {
        channel.close();
      } else {
        channel.unsafe().closeForcibly();
      }
    }
    return regFuture;
}

再具体看看NioServerSocketChannel的初始化过程,里面有比较关键的设置

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 设置为非阻塞
serverSocketChannel.configureBlocking(false);
// channel注册到selector中,关心事件为 OP_ACCEPT
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

我们平时使用NIO的时候一般都会写如上两行代码进行Channel的设置,在Netty中,设置的地方在NioServerSocketChannel的父类中,在NioServerSocketChannel初始化的过程中,会层层初始化其父类,我们先观察一下该类的继承图

image.png

// 设置的地方在这两个父类中
protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
    super(parent);
    this.ch = ch;
    // 设置SelectionKey.OP_ACCEPT
    this.readInterestOp = readInterestOp;
    try {
      // 设置为非阻塞的
      ch.configureBlocking(false);
    } catch (IOException e) {
      try {
        ch.close();
      } catch (IOException e2) {
        logger.warn(
          "Failed to close a partially initialized socket.", e2);
      }
​
      throw new ChannelException("Failed to enter non-blocking mode.", e);
    }
}
​
protected AbstractChannel(Channel parent) {
    this.parent = parent;
    id = newId();
    unsafe = newUnsafe();
    // ChannelPipeline和Channel是一一搭配的,Channel创建的时候ChannelPipeline也会随之创建
    pipeline = newChannelPipeline();
}
​
protected DefaultChannelPipeline newChannelPipeline() {
    return new DefaultChannelPipeline(this);
}

回到initAndRegister()方法,看看init()方法具体干了什么

@Override
void init(Channel channel) {
    // 设置配置好的option参数
    setChannelOptions(channel, newOptionsArray(), logger);
    setAttributes(channel, newAttributesArray());
    // NioServerSocketChannel初始化过程中,AbstractChannel这个抽象类会创建ChannelPipeline
    // 此处获取到ChannelPipeline
    ChannelPipeline p = channel.pipeline();
​
    final EventLoopGroup currentChildGroup = childGroup;
    final ChannelHandler currentChildHandler = childHandler;
    final Entry<ChannelOption<?>, Object>[] currentChildOptions = newOptionsArray(childOptions);
    final Entry<AttributeKey<?>, Object>[] currentChildAttrs = newAttributesArray(childAttrs);
​
    // 为ChannelPipeline添加一个初始化的Handler
    // 当NioServerSocketChannel在EventLoop注册成功时,这个方法会被调用
    p.addLast(new ChannelInitializer<Channel>() {
      @Override
      public void initChannel(final Channel ch) {
        final ChannelPipeline pipeline = ch.pipeline();
        ChannelHandler handler = config.handler();
        // 如果用户自己设置handler不为空,添加进去
        if (handler != null) {
          pipeline.addLast(handler);
        }
        // 为NioServerSocketChannel当pipeline添加ServerBootstrapAcceptor
        // 为了保证这个添加操作一定是在EventLoop线程中执行,添加操作被包装成一个任务提交到EventLoop线程中
        ch.eventLoop().execute(new Runnable() {
          @Override
          public void run() {
            pipeline.addLast(new ServerBootstrapAcceptor(
              ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
          }
        });
      }
    });
}

回到initAndRegister()方法,继续看注册操作

@Override
public ChannelFuture register(Channel channel) {
    // 将Channel和EventLoop封装成DefaultChannelPromise对象,DefaultChannelPromise实现了ChannelPromise,拥有
    // ChannelFuture和Promise这两个接口的特性,详情可见我上一篇文章
    return register(new DefaultChannelPromise(channel, this));
}
@Override
public final void register(EventLoop eventLoop, final ChannelPromise promise) {
    ObjectUtil.checkNotNull(eventLoop, "eventLoop");
    // 如果该Channel已注册过,不重复注册
    if (isRegistered()) {
      promise.setFailure(new IllegalStateException("registered to an event loop already"));
      return;
    }
    if (!isCompatible(eventLoop)) {
      promise.setFailure(
        new IllegalStateException("incompatible event loop type: " + eventLoop.getClass().getName()));
      return;
    }
​
    AbstractChannel.this.eventLoop = eventLoop;
    // 一个EventLoop在整个生命周期都只会与一个Thread进行绑定,这个判断实际上就是判断当前线程是否是与EventLoop绑定的线程
    // 以此来辨别是不是该EventLoop,因为与该EventLoop绑定的线程此时可能没有执行权
    if (eventLoop.inEventLoop()) {
      // 进行注册
      register0(promise);
    } else {
      try {
        // 如果当前线程并不是跟EventLoop绑定的线程,则把注册包装成一个任务提交到EventLoop线程中
        // 程序会切换线程执行注册任务
        eventLoop.execute(new Runnable() {
          @Override
          public void run() {
            register0(promise);
          }
        });
      } catch (Throwable t) {
        logger.warn(
          "Force-closing a channel whose registration task was not accepted by an event loop: {}",
          AbstractChannel.this, t);
        closeForcibly();
        closeFuture.setClosed();
        safeSetFailure(promise, t);
      }
    }
}
private void register0(ChannelPromise promise) {
    try {
      // 检查该channel目前是否处于open状态
      if (!promise.setUncancellable() || !ensureOpen(promise)) {
        return;
      }
      boolean firstRegistration = neverRegistered;
      // 终于到了真正的注册操作
      doRegister();
      // 设置标识位,防止重复注册
      neverRegistered = false;
      registered = true;
​
      // 此处会执行之前init()方法中添加进Channel的初始化Handler
      pipeline.invokeHandlerAddedIfNeeded();
      // 设置注册结果为成功
      safeSetSuccess(promise);
      pipeline.fireChannelRegistered();
      if (isActive()) {
        if (firstRegistration) {
          pipeline.fireChannelActive();
        } else if (config().isAutoRead()) {
          beginRead();
        }
      }
    } catch (Throwable t) {
      // Close the channel directly to avoid FD leak.
      closeForcibly();
      closeFuture.setClosed();
      safeSetFailure(promise, t);
    }
}
@Override
protected void doRegister() throws Exception {
    boolean selected = false;
    for (;;) {
      try {
        // 将Channel注册到Selector上
        // 注意:此处关注的事件是0,后续会变更关注的事件
        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;
        }
      }
    }
}

到此处,Channel终于注册到了Selector上,但是此处设置的关注的事件是0,NIO的代码中,关注的事件是SelectionKey.OP_ACCEPT,这个地方是在哪里设置的呢?我们接着往下看

看完initAndRegister(),我们要走出来,回到doBind方法中,其中有一个关键的方法doBind0

@Override
public final void bind(final SocketAddress localAddress, final ChannelPromise promise) {
    assertEventLoop();
​
    .....
​
    boolean wasActive = isActive();
    try {
      // 进行端口绑定
      doBind(localAddress);
    } catch (Throwable t) {
      safeSetFailure(promise, t);
      closeIfClosed();
      return;
    }
​
    if (!wasActive && isActive()) {
      invokeLater(new Runnable() {
        @Override
        public void run() {
          // 进行关注事件的变更
          pipeline.fireChannelActive();
        }
      });
    }
​
    safeSetSuccess(promise);
}

因为代码的调用链非常长,我们直接看关键代码

@Override
protected void doBind(SocketAddress localAddress) throws Exception {
    // 判断java版本是否大于7
    if (PlatformDependent.javaVersion() >= 7) {
      // 绑定端口号
      javaChannel().bind(localAddress, config.getBacklog());
    } else {
      javaChannel().socket().bind(localAddress, config.getBacklog());
    }
}
@Override
public void channelActive(ChannelHandlerContext ctx) {
    ctx.fireChannelActive();
    // 此处进入
    readIfIsAutoRead();
}
​
@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);
    }
}

至此,我们在NIO中常写的几行代码,在Netty中全部展示出来了,不得不说,Netty的代码封装得很好(写得很绕)

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
Selector selector = Selector.open();
// 监听端口6666
serverSocketChannel.socket().bind(new InetSocketAddress(6666));
serverSocketChannel.configureBlocking(false);
// channel注册到selector中,关心事件为 OP_ACCEPT
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

参考资料

www.jianshu.com/p/47fbe16ec…