• 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感兴趣的事件为 (连接事件 + 读事件)
  • 相关阅读:
    TPLINK TLWR710N设置详解
    hehe.....
    AS3写FTP登录过程
    QQ
    网页设计标准尺寸:
    女孩,你愿意做他的第几个女朋友
    監聽一個變量的值變化
    dispatchEvent
    10
    C#常用代码
  • 原文地址:https://www.cnblogs.com/s686zhou/p/15862719.html
Copyright © 2020-2023  润新知