10 netty服务端启动流程

前置知识

selector介绍

多线程模式下selector的使用以及IO模型概念的区分

IDEA断点调试说明

1 服务端启动步骤概述

netty的启动关键逻辑可以用以下代码表示

package CodeAnalyze;

import io.netty.channel.socket.nio.NioServerSocketChannel;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
public class Example {
    public static void main(String[] args) {
        try {
             // 下面三行都是原生的NIO的API
             ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); // 服务器监听socket
             serverSocketChannel.configureBlocking(false);
             Selector selector = Selector.open();
             NioServerSocketChannel attachment = new NioServerSocketChannel();   // netty提供

             SelectionKey sscKey = serverSocketChannel.register(selector,0,attachment);

             // 绑定端口
             serverSocketChannel.bind(new InetSocketAddress(8080));
             // 关联事件
             sscKey.interestOps(SelectionKey.OP_ACCEPT);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

上述代码中服务端启动可以划分为以下3个步骤

  • step1:创建ServerSocketChannel对象
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); // 服务器监听socket
serverSocketChannel.configureBlocking(false);
  • step2:将ServerSocketChannel注册到selector选择器,从而方便后续监控channel的事件
NioServerSocketChannel attachment = new NioServerSocketChannel();   // netty提供
Selector selector = Selector.open();
SelectionKey sscKey = serverSocketChannel.register(selector,0,attachment);
  • step3:绑定端口并关联accept事件
// 绑定端口
serverSocketChannel.bind(new InetSocketAddress(8080));
// 关联事件
sscKey.interestOps(SelectionKey.OP_ACCEPT);

2 netty服务端启动代码

package CodeAnalyze;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.logging.LoggingHandler;

public class TestSourceServer {
    public static void main(String[] args) {
        new ServerBootstrap()
                .group(new NioEventLoopGroup())
                .channel(NioServerSocketChannel.class)
                .childHandler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel ch) throws Exception {
                        ch.pipeline().addLast(new LoggingHandler());
                    }
                }).bind(8080);      // 入口1
    }
}

bind源码追踪

io.netty.bootstrap.AbstractBootstrap.bind(int inetPort) =>
io.netty.bootstrap.AbstractBootstrap.doBind(final SocketAddress localAddress
    public ChannelFuture bind(int inetPort) {            // 入口1源码
        return bind(new InetSocketAddress(inetPort));    // 入口2
    }
---------------------------------------------------------------------------------------
    public ChannelFuture bind(SocketAddress localAddress) { //入口2源码
        validate();  // 检测group和channel的工厂类是否为空,为空则抛出异常
        return doBind(ObjectUtil.checkNotNull(localAddress, "localAddress")); // 入口3
    }
---------------------------------------------------------------------------------------
    private ChannelFuture doBind(final SocketAddress localAddress) { // 入口3源码
        final ChannelFuture regFuture = initAndRegister();
        final Channel channel = regFuture.channel();
        if (regFuture.cause() != null) {
            return regFuture;
        }

        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 {
            // Registration future is almost always fulfilled already, but just in case it's not.
            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) {
                        // Registration on the EventLoop failed so fail the ChannelPromise directly to not cause an
                        // IllegalStateException once we try to access the EventLoop of the Channel.
                        promise.setFailure(cause);
                    } else {
                        // Registration was successful, so set the correct executor to use.
                        // See https://github.com/netty/netty/issues/2586
                        promise.registered();

                        doBind0(regFuture, channel, localAddress, promise);
                    }
                }
            });
            return promise;
        }
    }


总结:基于bind的源码追踪可以定位到doBind方法,该方法实现了服务端的3个启动步骤

2-1 dobind()源码分析

2-1-1 概述

    private ChannelFuture doBind(final SocketAddress localAddress) { 
        /* 
           创建并初始化NioServerSocketChannel,并且将该channel注册到一个EventLoop中,需要注意的是
           这里注册即绑定selector的操作是通过EventLoop提交线程任务完成,对于这种类型的异步操作,这里
           initAndRegister()会返回一个Future对象用于感知注册的结果
        */
        final ChannelFuture regFuture = initAndRegister();
        final Channel channel = regFuture.channel();
        if (regFuture.cause() != null) {
            return regFuture;
        }
        if (regFuture.isDone()) {   // channel注册已经完成(绝大部分情况),直接进行绑定
            ChannelPromise promise = channel.newPromise();
            doBind0(regFuture, channel, localAddress, promise);
            return promise;
        } else {                    // channel注册还未完成
            final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel);
            // 当前注册还么完成通过在future中添加回调函数确保
            // 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();
                        doBind0(regFuture, channel, localAddress, promise);
                    }
                }
            });
            return promise;
        }
    }

2-1-2 initAndRegister()

initAndRegister()源码
    final ChannelFuture initAndRegister() {
        Channel channel = null;
        try {
            channel = channelFactory.newChannel();  //实例化NioServerSocketChannel
            init(channel);          // 初始化channel(在pipeline中添加handler)
        } catch (Throwable t) {
            if (channel != null) {
                channel.unsafe().closeForcibly();
                return new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE).setFailure(t);
            }
            return new DefaultChannelPromise(new FailedChannel(), GlobalEventExecutor.INSTANCE).setFailure(t);
        }
        // 将创建的channel实例对象与EventLoopGroup中的一个EventLoop进行绑定
        ChannelFuture regFuture = config().group().register(channel);
        if (regFuture.cause() != null) {
            if (channel.isRegistered()) {
                channel.close();
            } else {
                channel.unsafe().closeForcibly();
            }
        }
        return regFuture;
    }

initAndRegister方法包含以下三个核心功能

// a)创建NioServerSocketChannel实例
channel = channelFactory.newChannel();
// b) 为channel的pipeline添加初始化handler,注意该handler只被调用一次,并且当前为调用
init(channel);
// c) 将NioServerSocketChanenl实例注册NioEventLoopGroup的selector中
ChannelFuture regFuture = config().group().register(channel);

a) 创建channel对象
通过channelFactory创建NioServerSocketChannel的实例


上面截图表明:方法initAndRegister()在执行过程中通过反射的方式利用NioServerSocketChannel的构造器实例化NioServerSocketChannel对象

进一步分析netty的NioServerSocketChannel的构造源码

public NioServerSocketChannel() {
    this(newSocket(DEFAULT_SELECTOR_PROVIDER));
}
---------------------------------------------------------------------------
private static ServerSocketChannel newSocket(SelectorProvider provider) {
    try {
        return provider.openServerSocketChannel();  // 
    } catch (IOException e) {
        throw new ChannelException(
            "Failed to open a server socket.", e);
    }
}

对比JDK中ServerSocketChannel的open方法源码

    public static ServerSocketChannel open() throws IOException {
        return SelectorProvider.provider().openServerSocketChannel();
    }

总结:对比上述两个代码段可以判定在实例化netty的NioServerSocketChannel的过程中也实例化了JDK自带的ServerSocketChannel


b) 初始化channel对象
在NioServerSocketChannel的pipeline添加初始化handler

init()方法源码

 @Override
    void init(Channel channel) throws Exception {
        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());
            }
        }

        ChannelPipeline p = channel.pipeline();

        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(0));
        }
        synchronized (childAttrs) {
            currentChildAttrs = childAttrs.entrySet().toArray(newAttrArray(0));
        }
		// 为NioServerSocketChannel添加handler,该handler后续只会被调用一次
        // 用于初始化NioServerSocketChannel!!!!!!
        p.addLast(new ChannelInitializer<Channel>() {
            @Override
            public void initChannel(final Channel ch) throws Exception {
                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));
                    }
                });
            }
        });
    }

c) 完成channel对象的注册

ChannelFuture regFuture = config().group().register(channel)的调用链如下所示:

io.netty.channel.MultithreadEventLoopGroup.register =>
io.netty.channel.SingleThreadEventLoop.register(Channel channel) =>
io.netty.channel.SingleThreadEventLoop.register(final ChannelPromise promise)=>
io.netty.channel.AbstractChannel.register(EventLoop eventLoop, final ChannelPromise promise)=>
io.netty.channel.AbstractChannel.register0=>
io.netty.channel.nio.doRegister()

==============================================================================

分析调用链可发现:注册的基本逻辑是先从NioEventLoopGroup中选择一个NioEventLoop,然后通过选择的NioEventLoop提交任务完成channel的注册(绑定当前eventLoop的selector)

==============================================================================

    @Override
    public ChannelFuture register(Channel channel) {
        return next().register(channel);
    }
    ------------------------------------------------------------------------
    @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;
    }
-------------------------------------------------------------------------------
     @Override
     public final void register(EventLoop eventLoop, final ChannelPromise promise) {
            if (eventLoop == null) {
                throw new NullPointerException("eventLoop");
            }
            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;
			// 下面的if-else通过检查当前线程是否时NIO线程,确保register0方法在NIO线程中执行!!
            if (eventLoop.inEventLoop()) {
                register0(promise);
            } else {
                try {
                    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);
                }
            }
        }
=====================================register0=================================================
	private void register0(ChannelPromise promise) {
            try {
                // check if the channel is still open as it could be closed in the mean time when the register
                // call was outside of the eventLoop
                if (!promise.setUncancellable() || !ensureOpen(promise)) {
                    return;
                }
                boolean firstRegistration = neverRegistered;
                doRegister();
                neverRegistered = false;
                registered = true;
                // 调用之前添加的初始化handler
                pipeline.invokeHandlerAddedIfNeeded();
				
                // 设置promise容器中为success,之后会调用promise容器关联的回调函数进行bind
                safeSetSuccess(promise);
                // 触发channelRegister方法调用
                pipeline.fireChannelRegistered();
                if (isActive()) {
                    if (firstRegistration) {
                        pipeline.fireChannelActive();
                    } else if (config().isAutoRead()) {
                        beginRead();
                    }
                }
            } catch (Throwable t) {
                closeForcibly();
                closeFuture.setClosed();
                safeSetFailure(promise, t);
            }
        }
----------------------------doregister----------------------------------------
    @Override
    protected void doRegister() throws Exception {
        boolean selected = false;
        for (;;) {
            try {             
                selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
                return;
            } catch (CancelledKeyException e) {
                if (!selected) {
                    eventLoop().selectNow();
                    selected = true;
                } else {
                    throw e;
                }
            }
        }
    }

总结:register过程中首先从EventLoopGroup中选择1个EventLoop,由EventLoop中的线程池提交任务去执行doRegister()方法完成注册,所谓注册就是让EventLoop中的selector关注ServerSocketChannel的事件

// 主线程和NIO线程配合的关键代码
if (eventLoop.inEventLoop())   
// 源码中eventLoop通过判断当前线程是否是evenLoop中的线程对象来确定是否需要提交任务。
// 不是,则执行eventLoop.execute(()->{ register0(promise);}),该代码让选择eventLoop提交任务。

register0的工作:

  • doRegister()方法将ServerSocketChannel注册到当前eventLoop中
selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
// javaChannel()就是JDK原生的ServerSocketChannel,eventLoop()
// unwrappedSelector()是当前EventLoop的selector
// this指的就是已经创建好的NioServerSocketChannel(Netty提供)
// doRegister()方法中的这行代码与(1 服务端启动步骤概述 step2)本质是相同的。
  • 执行handler初始化Channel
pipeline.invokeHandlerAddedIfNeeded();    // 此处代码执行之前添加在pipeline中的初始化handler

问题:为什么在“初始化channel对象阶段b”添加的handler到“channel对象注册阶段c“才进行执行?

原因:在阶段b,channel对象还没有绑定eventLoop,但是所添加的handler中需要EventLoop提交任务(见下面代码片段),因此必须等到阶段c中完成EventLoop的绑定之后再调用handler中的代码

			ch.eventLoop().execute(new Runnable() {
                    @Override
                    public void run() {
                        pipeline.addLast(new ServerBootstrapAcceptor(
                                ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
                    }
/* 
	ServerBootstrapAcceptor是1个handler用于ServerSocketChannel中accept事件
	发生时进行连接建立的处理。
*/
initAndRegister方法总结

​ 该方法总体的执行需要主线程和EventLoop中的线程配合完成

* 主线程创建并初始化channel对象并从EventLoop中选出channel绑定的EventLoop
* 实际注册工作是EventLoop中线程池提交任务完成的

注册的结果通过future容器(regFuture)作为该方法的返回值返回

注册成功设置位置如下:Nio线程执行register0()时设置promise容器中为success

 safeSetSuccess(promise);

在dobind()源码中:regFuture就是上述代码的promise

 /* 
    通过regFuture(promise容器)感知NIO线程中register是否完成,dobind方法中如果发现注册还没有完成,此	 时会为future添加回调函数ChannelFutureListener()来确保执行doBind0()中完成端口绑定。
 */
 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);
                    }
                }
            });

2-1-3 doBind0()

调用链

io.netty.bootstrapAbstractBootstrap.doBind0()
=>
io.netty.channel.AbstractChannel.bind(SocketAddress localAddress, ChannelPromise promise) =>
io.netty.channel.DefaultChannelPipeline.bind(SocketAddress localAddress, ChannelPromise promise)
=>
io.netty.channel.AbstractChannelHandlerContext.bind(SocketAddress localAddress) 
=>
io.netty.channel.AbstractChannelHandlerContext.invokeBind(SocketAddress localAddress, ChannelPromise promise)
=>
io.netty.channel.DefaultChannelPipeline.bind(localAddress, promise);
=>
io.netty.channel.AbstractChannel.bind(final SocketAddress localAddress, final ChannelPromise promise)
=>
    

AbstractChannelHandlerContext中bind相关源码

@Override
    public ChannelFuture bind(final SocketAddress localAddress, final ChannelPromise promise) {
        if (localAddress == null) {
            throw new NullPointerException("localAddress");
        }
        if (isNotValidPromise(promise, false)) {
            // cancelled
            return promise;
        }

        final AbstractChannelHandlerContext next = findContextOutbound(MASK_BIND);
        EventExecutor executor = next.executor();
        /*
           下面的if-else确保后续的invokeBind()操作是由nio线程完成
           if 检测当前线程是否是EventLoop的线程
               invokeBind
           else
           	   线程池提交任务及逆行invokeBind
           返回promise
        */
        if (executor.inEventLoop()) {      
            next.invokeBind(localAddress, promise);
        } else {
            safeExecute(executor, new Runnable() {
                @Override
                public void run() {
                    next.invokeBind(localAddress, promise);
                }
            }, promise, null);
        }
        return promise;
    }

    private void invokeBind(SocketAddress localAddress, ChannelPromise promise) {
        if (invokeHandler()) {
            try {
                ((ChannelOutboundHandler) handler()).bind(this, localAddress, promise);
            } catch (Throwable t) {
                notifyOutboundHandlerException(t, promise);
            }
        } else {
            bind(localAddress, promise);
        }
    }
    }

注意:上述源码中涉及线程的切换,代码中保证invokeBind()方法调用是由EventLoop的线程执行的。

AbstractChannel.bind源码如下

        @Override
        public final void bind(final SocketAddress localAddress, final ChannelPromise promise) {
            assertEventLoop();

            if (!promise.setUncancellable() || !ensureOpen(promise)) {
                return;
            }

            // See: https://github.com/netty/netty/issues/576
            if (Boolean.TRUE.equals(config().getOption(ChannelOption.SO_BROADCAST)) &&
                localAddress instanceof InetSocketAddress &&
                !((InetSocketAddress) localAddress).getAddress().isAnyLocalAddress() &&
                !PlatformDependent.isWindows() && !PlatformDependent.maybeSuperUser()) {
                // Warn a user about the fact that a non-root user can't receive a
                // broadcast packet on *nix if the socket is bound on non-wildcard address.
                logger.warn(
                        "A non-root user can't receive a broadcast packet if the socket " +
                        "is not bound to a wildcard address; binding to a non-wildcard " +
                        "address (" + localAddress + ") anyway as requested.");
            }

            boolean wasActive = isActive();
            try {
                doBind(localAddress);     // 1)完成底层ServerSocketChannel和端口的绑定
            } catch (Throwable t) {
                safeSetFailure(promise, t);
                closeIfClosed();
                return;
            }
			
            // 2)完成端口绑定后触发pipeline中handler的active事件
            if (!wasActive && isActive()) {
                invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        pipeline.fireChannelActive(); 
                        // fire channel active(调用所有handler的channelActive方法
                    }
                });
            }

            safeSetSuccess(promise);
        }

上述源码中的注意点:

1) doBind方法完成底层ServerSocketChannel和端口的绑定

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

2) 完成所有工作后,会触发当前NioServerSocketChannel的pipeline中所有handler的active事件

动机:当前的NioServerSocketChannel的pipeline结构是 head <= accept handler <= tail,

其中head和tail是pipeline默认的handler。三个handler中,head handler的channelActive让key关注accept事件

sscKey.interestOps(SelectionKey.OP_ACCEPT); // 默认的head handler的channelActive会完成该工作

3 总结

3-1 设计思想


ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
Selector selector = Selector.open();
SelectionKey sscKey = serverSocketChannel.register(selector,0,null);
serverSocketChannel.bind(new InetSocketAddress(8080));
sscKey.interestOps(SelectionKey.OP_ACCEPT);

上述原始的服务端启动流程在netty中显得异常复杂度,主要原因在于:

代码结构

  • netty采用EventLoop对原始的JDK的selector进行封装
    • 动机:充分利用多线程优势
  • 采用NioServerSocketChannel封装了ServerSocketChannel
    • 动机:netty基于责任链模式设计的channel让数据的处理更加方便直观

执行逻辑:执行过程中,无论是注册还是绑定端口都是通过channel绑定的EventLoop取提交任务实现,线程间的

协同使得代码不是特别直观。但框架的复杂性保证了使用者的简便性。


3-2 线程间的同步

Netty中线程间的写法

promise = func1(参数,...,promise)

// 情况1:通过promise判断func1的逻辑是否执行完整,如果已经执行完整,直接执行后续逻辑
if(promise.isSuccess){     
	func2()
}else{
// 情况2:通过promise判断func1的逻辑是否执行完整,如果未执行完整,添加回调函数,当执行完成后通过回调函数调用确保后续逻辑执行
	promise.addListener(()->{func1})
}

说明:上述伪代码逻辑在netty中经常出现,首先是异步方法func1执行(涉及其他线程),我们可以通过方法参数中是否有promise类型对象来判断当前方法是否涉及异步调用。然后通过返回的promise内容的判断其他线程是否执行完成(其他线程会将结果放到promise中),确保func2的逻辑一定实在func1后面执行。

  • 上述逻辑在Netty启动源码中体现:端口的绑定必须在channel注册后才能执行,这种严格顺序关系就是通过上述伪代码的思路实现。本质上还是多线程思想中的保护性暂停模式。这里的promise是充当了线程间同步的协调者

  • 上述逻辑func2()由于线程执行的不确定性,可能是线程1执行,也可能是线程2执行,如果我们希望确保某个目标线程执行某些操作,那么可以判断当前线程是否是目标线程。netty源码中多次出现inEventLoop()方法调用就是确保某些操作必须由EventLoop中的线程完成

参考资料

Netty基础课程

posted @   狗星  阅读(358)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
/* 返回顶部代码 */ TOP
点击右上角即可分享
微信分享提示

目录导航