BootStrap

Netty 中的 BootStrap 分为两种:一种是客户端的 BootStrap;一种是服务端的 ServerBootStrap

  • 客户端的 BootStrap

    初始化客户端,该 BootStrap 只有一个 EventLoopGroup,用于连接远程主机

  • 服务端的 ServerBootStrap

    用于绑定本地端口,一般存在两个 EventLoopGroup(一个用于包含单例的 ServerChannel,用于接收和分发请求;另一个是包含了所有创建的 Channel,处理服务器接受到的所有来自客户端的连接)。

启动流程

BootStrap 的启动流程(以 ServerBootStrap 为例):

Netty 组件介绍_ios

具体的代码如下所示:

NioEventLoopGroup group = new NioEventLoopGroup(); // 创建一个 NioEventLoopGroup
ServerBootstrap bootstrap = new ServerBootstrap(); 
bootstrap.group(group)
    .channel(NioServerSocketChannel.class) // 设置 Channel 的类型为 NioServerSocketChannel
    .localAddress(new InetSocketAddress(9098)) // 监听本地的 9098 端口
    .option(ChannelOption.TCP_NODELAY, true) // 设置启用 Nagle 算法
    .option(ChannelOption.SO_BACKLOG, 1024) // 设置最大等待连接队列长度
    .childHandler(new ChannelInitializer<SocketChannel>() {
        @Override
        protected void initChannel(SocketChannel ch) throws Exception {
            // 对当前的 Channel 中的 pieline 添加一系列的 Handler
            ch.pipeline().addLast(new ProtobufDecoder(null));
            ch.pipeline().addLast(new ProtobufEncoder());
        }
    });

Channel

以客户端连接到服务端为例

客户端 Channel 的初始化过程

  1. 通过 BootStrap 实例对象设置对应的 Channel的类型,即上文的 bootStrap.channel(NioSocketChannel.class) 就是将当前的 BootStrapChannel 类型设置为 NioSocketChannel。这个过程只是指定了 Channel 的类型,同时设置了对应的 ChannelFactory,并没有真正实例化 Channel

  2. 通过调用 Bootstrapconnect()方法完成 Channel 的实例化。具体调用链(在 Boostrap):connect() ——> doResolveAndConnect() ——> initAndRegister() ——> channelFactory.newChannel() 完成 Channel 的初始化工作

  3. Channel 的初始化过程中,首先会触发 NioSocketChannel 的构造器,使用默认的 selector 来创建一个 NioSocketChannel,然后依次调用父类的方法进行一系列的初始化操作。

实例化的具体流程如下图所示:Netty 组件介绍_初始化_02

服务端 Channel 的初始化过程

服务端的 Channel 初始化与客户端的 Channel 类似,不同的地方在于服务端的 Channel 是绑定到对应的主机和端口,而 客户端的 Channnel 是需要连接到服务器的 Channel。

客户端 Channel 的注册过程

在调用 BootStrapbind() 方法的过程中,会调用到 initRegister() 方法,在这个方法中会对 Channel 进行初始化;在调用 register() 方法之后,会再次调用 EventLoopGroup 对象的 register(Channel channel) 方法将 Channel 注册到对应的 EventLoopGroup 中。具体源代码如下所示:

// 移除了处理异常等不相关的代码
final ChannelFuture initAndRegister() {
    Channel channel = null;
    // 使用对应的 ChannelFactory 对象创建一个 Channel
    channel = channelFactory.newChannel();
    init(channel);

    // 将 Channel 注册到对应的 EventLoopGroup 中
    ChannelFuture regFuture = config().group().register(channel);
    
    return regFuture;
}

调用 EventLoopGroupregister(Channel channel) 方法会调用 AbstractUnsaferegister(EventLoop eventLoop, ChannelPromise promise) 方法来进行注册。具体源代码如下所示:

public final void register(EventLoop eventLoop, final ChannelPromise promise) {
    // 省略一部分检查的代码
    AbstractChannel.this.eventLoop = eventLoop;
    
    if (eventLoop.inEventLoop()) {
        register0(promise);
    }
    // 省略一部分代码。。。
}

register0 源代码如下所示:

private void register0(ChannelPromise promise) {
    // 省略一些无用的代码
    doRegister();
    // 省略一些无用的代码
}

由于当前使用的是 NioSocketChannel,因此会调用到 AbstractNioChanneldoRegister() 方法,具体的源代码如下所示:

protected void doRegister() throws Exception {
    boolean selected = false;
    for (;;) {
        try {
            /*
            	javaChannel() :由于这里注册的 Channel 是 NioSocketChannel,因此会得到当前正要注册的 NioSocketChannel
            	通过调用 Channel 的 register() 方法,将这个 Channel 注册到对应的 Selector 中,这里的 selector 对应着 NioEventLoop,即分配到的 EventLoop 对象,到此 Channel 的注册完成
            */
            selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
            return;
        } catch (CancelledKeyException e) {
           // 省略一部分异常捕获的代码
        }
    }
}

总的流程图如下所示:Netty 组件介绍_服务端_03

服务端 Channel 的注册过程

同样地,服务端的 Channel 的注册和客户端的也十分类似,不同的地方在于服务端的 Channel 会注册到“主” EventLoopGroup ,而客户端的 Channel 则只是注册到一个普通的 EventLoopGroup

客户端 Channel 的连接过程

Netty 组件介绍_ios_04

服务端 Channel 接受连接的过程

服务端的 Channel 在启动时会首先创建一个 NioServerSocketChannel 并注册到 “主” EventLoopGroup,用于创建对应的处理对应的连接请求。处理连接的请求在初始化 NioServerSocketChannel 的时候就已经准备好了,初始化服务端的 Channel 的源代码如下所示:

// 该代码位于 ServerBootStrap
@Override
void init(Channel channel) {
    // 获取当前 Channel 的 Pipeline
    ChannelPipeline p = channel.pipeline();
    
    // 这里的 childGroup 是 “从”—EvenLoopGroup,用于真正处理请求
    final EventLoopGroup currentChildGroup = childGroup;
    final ChannelHandler currentChildHandler = childHandler;
    final Entry<ChannelOption<?>, Object>[] currentChildOptions = newOptionsArray(childOptions);
    final Entry<AttributeKey<?>, Object>[] currentChildAttrs = newAttributesArray(childAttrs);

    p.addLast(new ChannelInitializer<Channel>() {
        @Override
        public void initChannel(final Channel ch) {
            final ChannelPipeline pipeline = ch.pipeline();
            ChannelHandler handler = config.handler();
            // 将在 bootStrap 中添加的 ChannelHandler 对象添加到 Pipeline 的末尾
            if (handler != null) {
                pipeline.addLast(handler);
            }

            ch.eventLoop().execute(new Runnable() {
                @Override
                public void run() {
                    // ServerBootstrapAcceptor 中重写了 channelRead() 方法,每个连接的请求都会首先调用这个方法以读取请求的内容
                    pipeline.addLast(new ServerBootstrapAcceptor(
                        ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
                }
            });
        }
    });
}

ServerBootstrapAcceptorchannelRead() 的源代码如下所示:

@Override
@SuppressWarnings("unchecked")
public void channelRead(ChannelHandlerContext ctx, Object msg) {
    final Channel child = (Channel) msg;
    
    // childHandler 由 ServerBootStrap 对象进行指定
    child.pipeline().addLast(childHandler);
    
    /* 
    	注意这里的 childGroup 对应的是 “从”—EventLoopGroup,由于 “从”—EventLoopGroup 是处理请求的,
    	因此在这里得到的请求(即 msg)会为它新创建一个 Channel 注册到“从”—EventLoopGroup 的某个 EventLoop 中
    	
    	这个请求转发到 “从”—EventLoopGroup 的工作是由 “主”—EventLoopGroup 来完成的
    */
    childGroup.register(child).addListener();
    
    // 省略部分异常检查代码。。。。
}

channelRead() 的工作已经介绍完了,现在问题是 “主”—EventLoopGroup 是如何处理请求的了。这个和使用 NIO 进行网络编程有关系,当绑定当前的一个 NioServerSocketChannel时,Netty 的底层会监听对应的事件。当 NioServerSocketChannel 的状态为 SelectionKey.OP_ACCEPT时,表示当前的 NioServerSocketChannel 是可以接收连接请求的,当一个请求到达时,便会执行对应的请求,NioServerSocketChannel 执行对应请求的源代码如下所示:

/* 
	因为每个 EventLoop 都是通过单个线程的方式来处理对应的任务的,同样地,NioServerSocketChannel 也是通过 EventLoop 来进行任务处理的
	
	该方法位于 NioEventLoop 中,因为初始化 NioServerSocketChannel 时指定了 NioEventLoop 来处理每个任务
*/
@Override
protected void run() {
    for (;;) {
        // 省略一大段异常处理和其它不是很关键的代码
        processSelectedKeys();
    }
}

processSelectedKeys 源代码如下所示:

private void processSelectedKeys() {
    if (selectedKeys != null) {
        processSelectedKeysOptimized();
    } else {
        processSelectedKeysPlain(selector.selectedKeys());
    }
}

尽管存在一个判断条件,但实际上,由于当前处理的 Channel 是 NioServerSocketChannel,最终都会调用 processSelectedKey 方法进行进一步的处理。具体的源代码如下所示:

private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
    // 省略一大段异常检查的代码
    int readyOps = k.readyOps();
    // 这里就是 NioServerSocketChannel 在可以读取时要进行处理的代码块。当当前的 Channel 是可读的或者是可接收请求是则执行对应的逻辑
    if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
        /* 
        	这里的 Unsafe 对象由 NioServerSocketChannel 在实例化时创建,
        	具体由 AbstractNioMessageChannel 通过重写 newSafe() 方法来实现
        	
        	这里的 Unsafe 类为 AbstractNioMessageChannel 的内部类
         */
        unsafe.read();
    }
}

unsafe.read()方法的源代码:

@Override
public void read() {
    // 核心代码如下,已经省略大部分其它不太重要的代码 
    do {
        /* 
        	doReadMessages 由具体的子类来实现,在这里具体的子类为 NioServerSocketChannel
        	
        	每次调用 doReadMessages 都将创建一个新的 SocketChannel,即一个新的连接放入 readBuf 列表中
        */
        int localRead = doReadMessages(readBuf);
        if (localRead == 0) {
            break;
        }
        if (localRead < 0) {
            closed = true;
            break;
        }

        allocHandle.incMessagesRead(localRead);
    } while (continueReading(allocHandle));
    
    int size = readBuf.size();
    // 将读取到的数据进行相应的处理
    for (int i = 0; i < size; i ++) {
        /*
        	由于只有一个服务线程处理请求数据,因此就不会因为同时有多个请求进行访问而造成数据混乱
        */
        readPending = false;
        pipeline.fireChannelRead(readBuf.get(i));
    }
    
    readBuf.clear();
    allocHandle.readComplete();
    pipeline.fireChannelReadComplete();
}

doReadMessages() 方法源代码如下所示:

@Override
protected int doReadMessages(List<Object> buf) throws Exception {
    // 根据当前的 NioServerSocketChannel 创建一个新的 SocketChannel
    SocketChannel ch = SocketUtils.accept(javaChannel());
    if (ch != null) {
        // 可以看到,每次读取时都会为当前读取的数据段分配一个新的 NioSocketChannel 来进行相应的任务处理
        buf.add(new NioSocketChannel(this, ch)); // 新分配的 NioSocketChnnel 的
        return 1;
    }
    // 省略部分异常检测代码
    
    return 0;
}

至此,NioServerSocketChannel 是如何分发请求到 NioSocketChannel 就已经非常清楚了

接收请求的具体流程如下所示:

Netty 组件介绍_服务端_05

ChannelPipeline

ChannelPipelineChannelHandler 链的容器,用于存储一系列的 ChannelHandler

每当一个新的 Channel 被创建了,都会建立一个新的 ChannelPipeline,并且这个 ChannelPipeline 会绑定到 Channel 上。具体对应关系如下图所示:

Netty 组件介绍_ios_06

ChannelPipeline 是双向的,但是同一时刻只能有一个方向的任务进行,如下图所示:

Netty 组件介绍_服务端_07

当一个入站事件被触发时,将会按照 ChannelInboundHandler 的顺序进行对应的处理。Netty 一般会认为入站的开始位置为 ChannelPiepeline 的开始位置,及上图上 SocketChannelChannelPipeline 的开始位置,因此在添加对应的 ChannelHandler 时需要注意这一点。

ChannelPipeline 的初始化

初始化 ChannelPipeline 的源代码如下所示:

/*
	这里的 Channel 是之前初始化的 NioServerSocketChannel
*/
protected DefaultChannelPipeline(Channel channel) {
    this.channel = ObjectUtil.checkNotNull(channel, "channel");
    succeededFuture = new SucceededChannelFuture(channel, null);
    voidPromise =  new VoidChannelPromise(channel, true);

    tail = new TailContext(this); // 上问 ChannelPipeline 示意图中的 head 节点,这是一个哑节点
    head = new HeadContext(this); //  Pipeline 中的尾节点,同样也是一个哑节点

    // 组成一个双向链表,注意上文提到一个 Pipeline 可以是双向的,因此这里的数据结构采用双向链表
    head.next = tail;
    tail.prev = head;
}

即初始化 ChannelPipeline 时,会维护一个由 ChannelHandlerContext 组成的双向链表,这个链表的作用是对相应的 Channel 进行i相关的事件处理。

ChannelHandler

服务端的 ChannelHandler

由于服务端存在两种类型的 EventLoopGroup,一个用于接收和分发请求,一个用于真正处理请求,因此 ChannelHandler 也分为两种,一类是用于接收请求时使用,一类是用于处理请求时使用。

ServerBootStrap通过 handler(ChannelHandler handler) 方法来指定接收请求是要执行的 ChannelHandler,这里的ChannelHandler添加是发生在初始化 Channel 的过程中(注意是初始化 Channel 而不是实例化 Channel)。具体的源代码如下所示:

@Override
void init(Channel channel) { // 这里的 Channel 是实例化之后的 Channel,即 NioServerSocketChannel
    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);

    p.addLast(new ChannelInitializer<Channel>() {
        @Override
        public void initChannel(final Channel ch) {
            final ChannelPipeline pipeline = ch.pipeline();
            // 这里的 handler 是通过 ServerBootStrap 调用 handler(...) 来指定的
            ChannelHandler handler = config.handler();
            if (handler != null) {
                pipeline.addLast(handler);
            }

            ch.eventLoop().execute(new Runnable() {
                @Override
                public void run() {
                    pipeline.addLast(new ServerBootstrapAcceptor(
                        ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
                }
            });
        }
    });
}

初始化之后,服务端 NioServerSocketChannel 的 Pipeline 的底层 Handler 结构如下图所示:

Netty 组件介绍_初始化_08

当接收到一个新的客户端请求时,会调用 ServerBootstrapAcceptor.channelRead 方法,具体源代码如下图所示:

 public void channelRead(ChannelHandlerContext ctx, Object msg) {
     // 这里的 child 是分配到 workGroup 中的一个 Channel,不是服务端处理连接的 NioServerSocketChannel
     final Channel child = (Channel) msg;

     // 这里的 chilHandler 是在 ServerBootStrap 对象中通过 childHandler() 方法设置的
     child.pipeline().addLast(childHandler);
     
     // 省略一部分不重要的代码
 }

EventLoop

EventLoop 的初始化

NioEvenLoopGroup有几个构造器,但是最终都是调用父类 MultithreadEventLoopGroup 的构造器

调用父类的 MultithreadEventLoopGroup 构造器初始化:

private static final int DEFAULT_EVENT_LOOP_THREADS;

static {
    // 初始化 EventLoop 线程数,这里设置的线程数为处理器的数量 * 2
    DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt(
        "io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));

    if (logger.isDebugEnabled()) {
        logger.debug("-Dio.netty.eventLoopThreads: {}", DEFAULT_EVENT_LOOP_THREADS);
    }
}

// 确定 EventLoop 的线程数,再交给父类进行构造
protected MultithreadEventLoopGroup(int nThreads, Executor executor, Object... args) {
    super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, executor, args);
}

再次调用父类的构造函数进行初始化:

protected MultithreadEventExecutorGroup(int nThreads, 
                                        Executor executor,
                                        EventExecutorChooserFactory chooserFactory, 
                                        Object... args) {
    if (executor == null) {
        executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
    }

    children = new EventExecutor[nThreads];

    for (int i = 0; i < nThreads; i ++) {
        children[i] = newChild(executor, args);
    }

    chooser = chooserFactory.newChooser(children);
}

主要任务:

  • 创建一个大小为 nThreadsEventExecutor 数组

  • 通过调用 newChild 来初始化 children 数组的每个元素

  • 根据 nThreads 的大小,创建不同的 EventExecutorChooser,如果 nThreads 是 2 的整数幂,则使用 PowerOfTwoEventExecutorChooser,否则,使用 GenericEventExecutorChooser。它们的功能够一样,都是从 children 数组中选出一个合适的 EventExecutor 实例

    源代码如下:

    public EventExecutorChooser newChooser(EventExecutor[] executors) {
        if (isPowerOfTwo(executors.length)) {
            return new PowerOfTwoEventExecutorChooser(executors);
        } else {
            return new GenericEventExecutorChooser(executors);
        }
    }
    

总的初始化流程:

  • EventLoopGroup(实际上是 MultithreadEventExecutorGroup)内部维护了一个类型为 EventExecutor 的 children 数组,其大小为 nThreads,这样就创建了一个线程池
  • MultithreadEventLoopGroup 中会确定要选择的 EventLoop 线程数,默认为可用的处理器大小 * 2
  • MultithreadEventExecutorGroup 会调用 newChild 方法来初始化 children 数组的每个元素

具体的流程如下所示:

Netty 组件介绍_bootstrap_09