Netty-服务端启动流程 源码分析

Netty-服务端启动流程 源码分析

预备知识

在看这篇 Netty启动流程 源码解析 之前, 最好请先了解 NioEventLoopGroup 和 NioEventLoop 这两个组件。可以参考我之前的文章 [Netty-组件 (NioEventLoopGroup、NioEventLoop)源码分析] ,其中主要是从构造方法入手 来分析 这两个组件中 关键的 属性变量等。 而其中具体的 核心方法,以及其它属性 我会通过服务端的启动流程慢慢展开。

ChannelFutrue

在分析启动流程前, 我们需要先了解一下 ChannelFutrue

ChannelFuture 是 Netty 为了 感知异步任务的执行结果,所封装的一个任务接口。

该类 与 java.util.concurrent 包下的 FutureTask很像,他俩同样都实现了Future接口,不同点是FutrueTask多实现了Runnable接口用于提交给线程执行的入口。

该接口源码很简单, 简单介绍下主要的几个功能:

    // 用于保存 当前任务所关联的 Channel
    Channel channel();
	
    // --------------- 任务监听器相关 ---------------------------------
	// 添加任务监听器
    @Override
    ChannelFuture addListeners(GenericFutureListener<? extends Future<? super Void>>... listeners);

	// 移除任务监听器
    @Override
    ChannelFuture removeListener(GenericFutureListener<? extends Future<? super Void>> listener);


  	//-------------- 任务具体操作相关 ----------------------
    @Override
    ChannelFuture sync() throws InterruptedException;

    @Override
    ChannelFuture syncUninterruptibly();

    @Override
    ChannelFuture await() throws InterruptedException;

    @Override
    ChannelFuture awaitUninterruptibly();
	
	// 取消任务
	boolean cancel(boolean mayInterruptIfRunning);
   
	// --------------- 任务执行结果相关 -----------------------
	// 任务是否成功
    boolean isSuccess();

	// 任务是否取消
    boolean isCancellable();

	// 任务失败的 错误原因
    Throwable cause();

Netty示例代码

众所周知, Netty服务端标准的代码中 会先创建两个 NioEventLoopGroup (Boss组,Worker组) 。
然后通过 ServerBootStrap 来注册相关的组件 ,最终绑定 ip , port 后启动服务器。

大致的标准启动代码如下:

        // 1. 创建 boss组 和 worker组
		EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
		// 2. 创建bootStrap启动器
        ServerBootstrap b = new ServerBootstrap();
		// 3. bootStrap中设置 boss组 和 worker组
        b.group(bossGroup, workerGroup)
            // 4. bootStrap设置ServerSocketChannel
            .channel(NioServerSocketChannel.class)
            // 5. 设置配置项
            .option(ChannelOption.SO_BACKLOG, 100)
            // 6. 给ServerSocketChannel添加 handler
            .handler(new LoggingHandler(LogLevel.INFO))
            // 7. 给SocketChannel添加handler
            .childHandler(new ChannelInitializer<SocketChannel>() {
                @Override
                public void initChannel(SocketChannel ch) throws Exception {
                    ChannelPipeline p = ch.pipeline();
                    p.addLast(serverHandler);
                }
            });

        // 8. 启动服务器
        ChannelFuture f = b.bind(PORT).sync();

从上面的代码可知,最终启动服务端的入口 则是 b.bind(PORT) 方法。

因此我们就可以从 该方法入口,来逐步分析 Netty 服务端启动时 到底做了哪些事情。

思考

在看源码前, 我们可以先思考一下:

1.其中有哪些事情是必须要做的?

2.如果让我们来实现该Netty的启动,我们需要怎么去处理?

回答问题1:

Netty实际上是 在 Nio 上又套了一层框架, 因此基本的服务启动流程 是按照Nio的方式来的

下面是Nio 启动服务前必做的操作代码:

		// 1. 创建 ServerSocketChannel
		ServerSocketChannel ssc = ServerSocketChannel.open();

		// 2. 创建 Selector 多路复用器
        Selector selector = Selector.open();

		// 3. 设置ServerSocketChannel 为非阻塞
        ssc.configureBlocking(false);

		// 4. 将ServerSocketChannel注册到Selector上
        ssc.register(selector, SelectionKey.OP_ACCEPT);

		// 5. ServerSocketChannel绑定端口
        ssc.bind(new InetSocketAddress(8080));

问题2请结合 之前一篇 Netty-Reactor线程模型(NIO) 来思考。

接下来 我们需要带着这两个问题,来进入Netty的源码分析中。

1、doBind 核心入口

从示例代码中的bind方法, 往里面追踪,则会来到 #AbstractBootstrap doBind 该方法。 从 bind() 到 dobind() 的跟进过程没什么好说的,代码也非常浅显易懂。 那么我们重点就以 doBind() 方法 为核心入口。

    //真正完成 bind 工作的方法, 非常关键
    private ChannelFuture doBind(final SocketAddress localAddress) {

		//---------------------- 初始化注册Channel------------------------
        final ChannelFuture regFuture = initAndRegister();

        final Channel channel = regFuture.channel();


        if (regFuture.cause() != null) {
            return regFuture;
        }

        
        // ----------------------- 绑定Channel-----------------------
        
        // 当register0 已经执行完后, regFuture状态就是Done
        if (regFuture.isDone()) {
            // At this point we know that the registration was complete and successful.
            ChannelPromise promise = channel.newPromise();
            doBind0(regFuture, channel, localAddress, promise);
            return promise;
        } else {

            final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel);

            //这里向register0 相关的promise对象 添加一个监听器对象  register0任务成功或失败都会回调该监听器中的方法
            //监听器回调线程 是 eventLoop线程  不是当前主线程
            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();
						
                        // 当Channel注册完成, 会执行该方法
                        doBind0(regFuture, channel, localAddress, promise);
                    }
                }
            });

            //主线程返回一个 与bind 操作相关的 promise对象
            return promise;
        }
    }

该方法实际上包含两部分:

1. **初始化注册Channel**
2. **给Channel绑定端口**

而其中 给Channel 绑定端口,需要建立在 Channel 初始化注册完毕之后 , 因此我们先来分析Channel的初始化与注册。

2 、初始化注册Channel

上面 doBind 方法中,跟踪 initAndRegister方法。 顾名思义,该方法主要的功能是 初始化Channel和注册Channel。

在 JDK 的NIO 代码中,可以按照 初始化 和注册来划分

1. 初始化
    ServerSocketChannel ssc = ServerSocketChannel.open();
	ssc.configureBlocking(false);
 	Selector selector = Selector.open();

2. 注册
   ssc.register(selector, SelectionKey.OP_ACCEPT);  	

在明确了上面 初始化和注册的操作目的后,我们来看下 initAndRegister方法 做了哪些事

    final ChannelFuture initAndRegister() {
        Channel channel = null;
        try {
            
            // 反射创建 Channel
            // 服务端创建 NioServerSocketChannel
            // 客户端创建 NioSocketChannel
            channel = channelFactory.newChannel();
			
            // ------------初始化逻辑-------------------
            // 模板模式
            // 初始化Channel (根据服务端与客户端不同 调用子类BootStrap实现方法)
            init(channel);
        } catch (Throwable t) {
            //... 省略
        }
		
        // ---------------注册逻辑------------------
        ChannelFuture regFuture = config().group().register(channel);
        if (regFuture.cause() != null) {
            if (channel.isRegistered()) {
                channel.close();
            } else {
                channel.unsafe().closeForcibly();
            }
        }
        return regFuture;
    }

上述代码中 主要分为三块逻辑:

  1. 创建 Channel
  2. 初始化Channel
  3. 注册Channel

创建Channel 很简单,就是根据 反射逻辑创建Channel对象。

初始化Channel 以及 注册Channel在下面会逐个分析

2.1、初始化Channel

    void init(Channel channel) {
        // 给Channel 设置 Options 和 Attributes
        setChannelOptions(channel, newOptionsArray(), logger);
        setAttributes(channel, newAttributesArray());

        // 获取该Channel的 pipeline
        ChannelPipeline p = channel.pipeline();

        // 获取初始化赋值的workerGroup (childGroup是 workerGroup)
        final EventLoopGroup currentChildGroup = childGroup;

 		// 获取初始化赋值的 childHandler
        final ChannelHandler currentChildHandler = childHandler;

        // 客户端Socket选项信息
        final Entry<ChannelOption<?>, Object>[] currentChildOptions = newOptionsArray(childOptions);

        // Netty的channel都是实现了 AttributeMap接口的, 可以在启动类内配置一些自定义数据,这样的话 创建出来的Channel实例 就包含这些数据了。
        final Entry<AttributeKey<?>, Object>[] currentChildAttrs = newAttributesArray(childAttrs);
		
        // 向 NioServerSocketChannel的 管道pipeline中添加处理器
        // ChannelInitializer 它本身不是个Handler, 只是通过 适配器 实现了 Handler接口
        // 它存在的意义 就是为了延迟初始化 Pipeline 什么时候初始化呢?
        //     当Pipeline 上的Channel激活后,真正的添加handler 逻辑才会执行
        p.addLast(new ChannelInitializer<Channel>() {
            @Override
            public void initChannel(final Channel ch) {
                final ChannelPipeline pipeline = ch.pipeline();
                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));
                    }
                });
            }
        });
    }

上述代码不是很难理解, 基本就是将 之前 给 BootStrap 赋的值,添加到 NioServerSocketChannel上。

赋值的变量如下:(注意:下面属性都是专属于 ServerSocketChannel的 ,因为同样的属性对于SocketChannel也有一份)

  1. Options
  2. Attr
  3. Handler

其中 Handler的赋值最为特殊, 它将其包装成了 ChannelInitializer 放进了 Pipeline 中。 这里可以把ChannelInitializer 理解成一个 压缩包最终会在Channel 激活后,将ChannelInitialer中的Handler解压出来放入pipeline中

p.addLast() 向管道添加Handler

该方法属于 Pipeline组件 中的知识,后面会专门出一篇对pipeline的解析,本次就根据方法简单追踪即可。

跟着上面的 p.addLast() 方法追踪 会来到下面的代码

    // 参数1: group  null
	// 参数2: name   null
	// 参数3: handler  ChannelInitializer压缩包
	public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {

        final AbstractChannelHandlerContext newCtx;

        synchronized (this) {
            
            // 根据 Handler上的 @Sharable 注解,检查是否是共享处理器
            checkMultiplicity(handler);
			
            // 再将 ChannelInitializer 包装成 AbstractChannelHandlerContext
            newCtx = newContext(group, filterName(name, handler), handler);
			
            // 真正添加到pipeline中的方法  (通过操作指针入队)
            addLast0(newCtx);


            // 检查Channel是否注册过 (第一次进来没注册过会进入该方法)
            if (!registered) {
                // 设置包装后的 newCtx的状态为 ADD_PENDING  等待添加状态
                newCtx.setAddPending();
                // 将newCtx包装成 task添加到等待队列中去
                callHandlerCallbackLater(newCtx, true);
                return this;
            }

            EventExecutor executor = newCtx.executor();
            if (!executor.inEventLoop()) {
                callHandlerAddedInEventLoop(newCtx, executor);
                return this;
            }
        }
        callHandlerAdded0(newCtx);
        return this;
    }

上述代码针对 第一次初始化 NioServerSocketChannel 并添加 ChannelInitializer 到pipeline中 的逻辑主要的步骤:

  1. 将 ChannelInitialzer 包装成 newCtx
  2. 将 newCtx 设置成 ADD_PENDING 状态
  3. 将 newCtx 包装成 task 添加到 等待队列中

初始化总结:

Channel初始化主要做了如下几个事:

  1. 添加初始的一些属性 (Options, Attrs 等)
  2. 向该Channel的 pipeline中添加 ChannelInitializer压缩包

2.2、注册Channel

此时回到 initAndRegister方法中的 注册Channel逻辑

        // ---------------注册逻辑------------------

		// config() 返回 该BootStrap的 config属性。 该config相当于门面,能够通过该config访问到BootStrap内部的其它属性值.
		// config.group() 返回的是之前设置的 BossGroup(NioEventLoopGroup)
		// 最终就是调用 NioEventLoopGroup 的 register方法来注册 Channel
        ChannelFuture regFuture = config().group().register(channel);

        if (regFuture.cause() != null) {
            if (channel.isRegistered()) {
                channel.close();
            } else {
                channel.unsafe().closeForcibly();
            }
        }

上述核心代码就一行 ChannelFuture regFuture = config().group().register(channel);

config() : 获取BootStrap的config属性 (门面属性,通过其可访问到BootStrap其他的属性)

config().group() : 最终会获取到 bossGroup (NioEventLoopGroup)

那么下面我们进 追踪进 NioEventLoopGroup 的 register 方法

  1. 首先进入到 MultithreadEventLoopGroup #register
    public ChannelFuture register(Channel channel) {
        
        // 1. next() 从该 NioEventLoopGroup中 轮询算法选择一个 NioEventLoop 
        // 2. 调用 NioEventLoop的register方法
        return next().register(channel);
    }
  1. 继续跟进到 SingleThreadEventLoop #register
    public ChannelFuture register(Channel channel) {
        // 将Channel 和 当前NioEventLoop 包装成 ChannelPromise 并调用下面的register方法
        return register(new DefaultChannelPromise(channel, this));
    }


    public ChannelFuture register(final ChannelPromise promise) {
        ObjectUtil.checkNotNull(promise, "promise");

        /**
         * promise.channel() = NioServerSocketChannel / NioSocketChannel
         *
         * NioServerSocketChannel.unsafe() =  NioMessageUnsafe / NioByteUnsafe
         *
         * unsafe.register(NioEventLoop, DefaultChannelPromise)
         */
        //参数1: NioEventLoop 单线程 线程池
        //参数2: promise 结果封装..外部可以注册监听 进行异步操作获取结果
        promise.channel().unsafe().register(this, promise);
        return promise;
    }

最终会调用该 Channel的 Unsafe 来执行 相关 register 注册逻辑。 该unsafe 为父类 AbstractUnsafe

  1. 继续跟进到 AbstractUnsafe #register
        public final void register(EventLoop eventLoop, final ChannelPromise promise) {
            ObjectUtil.checkNotNull(eventLoop, "eventLoop");

            //初次进来是false  防止Channel重复注册
            if (isRegistered()) {
                //1.设置promise结果为失败 2.回调监听器进行失败的逻辑
                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 获取到Channel作用域, 这个Channel就是unsafe的外层对象。
            //AbstractChannel.this => NioServerSocketChannel对象
            //绑定个关系  后续Channel上的事件 或者 任务 都会依赖当前EventLoop线程去处理
            AbstractChannel.this.eventLoop = eventLoop;



            //判断当前线程 是不是 当前eventLoop 自己这个线程 
            if (eventLoop.inEventLoop()) {
                // 若当前线程是 eventLoop线程,则直接调用 register0 方法
                register0(promise);
            } else {
                // 若不是,则向 eventloop线程中 提交 register0 任务
                try {
					
                    // 异步任务一:
                    // 将注册的任务,提交到eventLoop 工作队列内了 .. 带着promise过去了
                    eventLoop.execute(new Runnable() {
                        @Override
                        public void run() {
                            register0(promise);
                        }
                    });
                } catch (Throwable t) {
                   // ...省略
                }
            }
        }

上述代码 中由于 当前线程不是 eventLoop线程,因此向eventLoop线程池中提交了 异步任务1(register0).

其中两条判断逻辑 最终都指向了 register0 方法, 继续追踪进去,看看Channel注册 到底做了哪些事情.

  1. 最终会来到 AbstractUnsafe #register0 中 (Channel注册核心逻辑)
        // 这个方法一定是 当前Channel关联的EventLoop线程执行
        //参数 promise : 可以异步从中获取结果
        private void register0(ChannelPromise promise) {
            try {
                // 校验判断
                if (!promise.setUncancellable() || !ensureOpen(promise)) {
                    return;
                }
                boolean firstRegistration = neverRegistered;

				//******* 执行 JDK NIO原生Channel的注册逻辑代码*****:
                //   javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
                doRegister();
                
                
                //表示已经注册了
                neverRegistered = false;
                //表示当前Channel已经注册到多路复用器中了
                registered = true;

    

                // 这里会向当前EventLoop线程提交异步任务2  : 对ChannelInitailzer进行拆包
                pipeline.invokeHandlerAddedIfNeeded();

                //这一步会去回调 注册相关的 promise 上注册的Listener监听器
                safeSetSuccess(promise);

                //向当前Channel的Pipeline 发起注册完成事件  关注的Handler 可以做一些事情
                pipeline.fireChannelRegistered();


                //目前咱们是 NioServerSocketChannel
                // 咱们在这一步完成绑定了吗?  绑定操作一定是 当前EventLoop线程去做的
                // 在这一步的时候,绑定一定是 没完成的。 绑定操作是异步任务3  该操作是异步任务1 中间还差一个任务呢
                // 这一步isActive()条件是不成立的。
                if (isActive()) {

                    //客户端 NioSocketChannel 这一步是成立的 所以会进来
                    if (firstRegistration) {

                        // 传播channelActivie()方法   实际上就是让SocketChannel 开始监听事件了
                        pipeline.fireChannelActive();
                    } else if (config().isAutoRead()) {
                        beginRead();
                    }
                }
            } catch (Throwable t) {
                // Close the channel directly to avoid FD leak.
                closeForcibly();
                closeFuture.setClosed();
                safeSetFailure(promise, t);
            }
        }

上述代码中:

doRegister() 方法是真正完成 NIO Channel 注册到 selector 上的方法。

pipeline.invokeHandlerAddedIfNeeded() 方法最终会对 之前添加进 pipeline中的 ChannelInitializer进行拆包, 并提交异步任务2.

2.3、拆压缩包

下面我们关注,ChannelInitializer拆包的过程:

    private void callHandlerAddedForAllHandlers() {
        final PendingHandlerCallback pendingHandlerCallbackHead;
        synchronized (this) {
            assert !registered;
            registered = true;
            // 取出 等待队列的 头节点 task
            pendingHandlerCallbackHead = this.pendingHandlerCallbackHead;
            // 将头节点task 置空
            this.pendingHandlerCallbackHead = null;
        }
		
        PendingHandlerCallback task = pendingHandlerCallbackHead;
        //循环执行  等待队列中的 task
        while (task != null) {
            task.execute();
            task = task.next;
        }
    }

跟进 task.execute() 会来到 PendingHandlerAddedTask #execute 方法

        void execute() {
            //拿到 eventLoop 线程
            EventExecutor executor = ctx.executor();

            // 判断当前线程 是否是 eventLoop线程
            if (executor.inEventLoop()) {
                // 是eventLoop线程, 则直接执行 (当前必走该分支)
                callHandlerAdded0(ctx);
            } else {
                // 若不是, 则将该任务提交给eventLoop线程池
                try {
                    executor.execute(this);
                } catch (RejectedExecutionException e) {
                    // ...
                }
            }
        }
    }

接着来到 DefaultChannelPipeline #callHandlerAdded0 方法

    // 参数1  ctx 该ctx就是包装了 ChannelInitializer的newCtx
	private void callHandlerAdded0(final AbstractChannelHandlerContext ctx) {
        try {
            //执行CTX的 callHandlerAdded()方法
            ctx.callHandlerAdded();
        } catch (Throwable t) {
           // ...
        }
    }

继续来到 AbstractChannelHandlerContext #callHandlerAdded 方法

    final void callHandlerAdded() throws Exception {
        //CAS方式设置 ctx的状态为 complete  (原来是 Add Pending 状态)
        if (setAddComplete()) {
            //handler() 拿到handler  此handler是 ChannelInitializer压缩包
            // 执行ChannelInitializer 的 handlerAdded() 方法
            handler().handlerAdded(this);
        }
    }

由于 Ctx 包装了 ChannelInitializer, 因此 handler() 方法就会拿到所包装的 ChannelInitilizer

跟进到 ChannelInitializer #handlerAdded 方法中

    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        // 判断所属Channel是否已经注册过了
        if (ctx.channel().isRegistered()) {
            
            // 调用模板方法, initChannel()  执行用户自定义的重写逻辑
            // 将ChannelInitializer中的Handler取出并放入pipeline中。
            if (initChannel(ctx)) {
				// 成功拆包后, 将ChannelInitializer自己从 pipeline中移除
                removeState(ctx);
            }
        }
    }

接着就会来到 Channel初始化时候 的那段代码:

会执行 initChannel 方法

 p.addLast(new ChannelInitializer<Channel>() {
            @Override
            public void initChannel(final Channel ch) {
                
                // 获取 pipeline
                final ChannelPipeline pipeline = ch.pipeline();
                
                // 获取 向BootStrap设置的 Server端的 handler,如:LoggingHandler
                ChannelHandler handler = config.handler();
                if (handler != null) {
                    // 添加到 pipeline中
                    pipeline.addLast(handler);
                }
                
                // 向当前Channel所属的eventLoop线程池,提交 异步任务2
                ch.eventLoop().execute(new Runnable() {
                    @Override
                    public void run() {
                        pipeline.addLast(new ServerBootstrapAcceptor(
                                ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
                    }
                });
            }
        });

上述代码 主要就是

1.  向pipeline中 添加 BootStrap初始化设置的Handler,  
2.  **向eventLoop线程池中 提交异步任务2  (重点)**

2.4、总结

Channel的 初始化注册做了哪些事情:

  1. 反射创建Channel (NioSeverSocketChannel / NioSocketChannel)
  2. 初始化Channel
  3. 设置 Options,Attrs 等基本属性值
  4. 向pipeline中添加 ChannelInitializer 压缩包
  5. 注册Channel
    1. 封装成 Promise 任务对象,并调用 Channel的 unsafe对象的 register方法,提交异步任务1(register0)
    2. register0方法
      1. 完成JDK NIO的 Channel注册
      2. 解压缩包,执行ChannelInitializer 的 initChannel() 方法,该方法中会提交异步任务2 (p.addLast(Acceptor))

3、绑定Channel

回到 一开始 的 doBind 方法 , 至此 Channel的 初始化和注册已经分析完毕,下面我们来分析 Channel的端口绑定逻辑.

Channel 的端口绑定逻辑,必须建立在 Channel已经完成了初始化和注册后才能执行。 而核心逻辑就是下面一行代码:

   doBind0(regFuture, channel, localAddress, promise);

跟进doBind0 方法

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


        // 异步任务3 提交
        channel.eventLoop().execute(new Runnable() {
            @Override
            public void run() {
                if (regFuture.isSuccess()) {
                    // 绑定端口, 并通知监听器
                    channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
                } else {
                    promise.setFailure(regFuture.cause());
                }
            }
        });
    }

上述代码中,核心的方法 channel.bind(localAddress, promise) ,他会调用 Channel的 pipeline 的 bind方法,让pipeline中的 Handler,从尾部向头部依次执行 bind 方法

下面会来到 HeadContext #bind 方法中

        public void bind(
                ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) {
            // 调用所属Channel的 unsafe 对象的 bind方法
            unsafe.bind(localAddress, promise);
        }

最终核心逻辑就在 AbstractUnsafe #bind 方法中

实际上逻辑也非常简单,就是使用NIO Channel 的 绑定端口方法

 javaChannel().socket().bind(localAddress, config.getBacklog());

4、激活Channel

绑定Channel的操作实际上很简单,实际上就是调用 NIO 的 Channel 绑定方法

但是在Netty中,Channel绑定完成后,还有一个重要的操作就是激活Channel.

invokeLater(new Runnable() {
    @Override
    public void run() {
        // 激活操作, HeadContext 响应做具体的激活工作
        pipeline.fireChannelActive();
    }
});

pipeline.fireChannelActive(); 向管道中发送 ChannelActive 事件,最终只有HeadContext响应

**接下来看 HeadContext #channelActive **

        public void channelActive(ChannelHandlerContext ctx) {
            // 向后面的HandlerContext节点传递 channelActive事件 (后面的节点都是空实现)
            ctx.fireChannelActive();
			
            // 激活操作  Channel开启读事件响应
            readIfIsAutoRead();
        }


        private void readIfIsAutoRead() {
            if (channel.config().isAutoRead()) {
                // 向channel开启读 
                channel.read();
            }
        }

channel.read(); :会调用 该channel 的pipeline的read方法,从尾节点向头节点传递

最终还是会来到HeadContext #read 方法

 		public void read(ChannelHandlerContext ctx) {
            // 调用 Unsafe 的 beginRead 
            unsafe.beginRead();
        }

接着追踪到 该Channel的 unsafe 中 beginRead 方法

一步步进入会来到 AbstractNioUnsafe #doBeginRead

    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);
        }
    }

5、总结

这里总结下 Netty 服务端 启动做了哪些事情:

  1. 初始化 和 注册 NioServerSocketChannel
  2. 通过反射创建NioServerSocketChannel
  3. 初始化Channel
    1. 设置 Options
    2. 设置 Attrs
    3. 向Channel的pipeline中添加 ChannelInitializer 压缩包 (其中包含 用户自定义服务端handler 和 处理连接事件的AcceptorHandler) , 此时会加入到 pipeline的 等待队列
  4. 注册Channel
    1. 注册Channel 封装成 Promise 任务(其中有 Channel对象, EventLoop对象)
    2. 提交 异步任务1 (注册Channel)
    3. 完成 JDK NIO 的Channel 注册到 selector多路复用器上。
    4. 解压缩 pipeline中的等待队列中的 ChannelInitializer ,此时会提交异步任务2(向pipeline中添加AcceptorHandler 连接事件处理器 )
  5. 绑定Channel
    1. 绑定Channel 是必须建立在 注册Channel 完成的基础上才会执行, 这是通过Promise的 监听器回调机制实现的, 响应回调则会 提交 异步任务 3(绑定Channel)
    2. 绑定Channel 的 核心方法也就是 JDK NIO 的 bind 方法,绑定 ip 和 端口
  6. 激活Channel
    1. 激活Channel 同样是 在绑定Channel完成后 执行的, 会提交 异步任务4(激活Channel)
    2. 激活Channel 的核心逻辑就是 给该Channel 在多添加一个 读感兴趣事件到selector上,此时Channel感兴趣的事件为 (连接事件 + 读事件)
posted @ 2022-02-04 13:13  s686编程传  阅读(78)  评论(0编辑  收藏  举报