Netty详解之三:Server端启动过程

Netty的源码包里面,io.netty.example下有很多示例代码可供参考,是学习使用Netty的利器;另外要掌握netty,需要对它的关键源码有一定程度的了解。从这一章开始,我们开始跟着源码学习Netty的各核心模块,源码使用当前最新稳定版4.1.45.Final

另外要能读懂Netty源码,有几个前提条件:

  • 掌握java的多线程原理,有一定深度,尤其对java.util.concurrent.Executor框架要熟悉;
  • 对TCP协议有一定了解,会Socket编程,掌握java NIO相关接口;
  • 使用过Netty。

本系列文章剖析源码的策略:

  • Netty是一个比较复杂的框架,尤其是一些核心类,功能非常多,牵涉的概念非常多,完全以类为单位来分析是徒劳的,我们经常是以某个功能为线索进行追踪;
  • 为了节省篇幅,本文对源码做了裁剪处理,只贴关键部分,基本剔除了错误和异常处理(实际这是Netty最有价值的部分之一);
  • 章节安排有严格顺序,在分析每个功能的实现细节过程中,不断洞察到Netty的设计思路,构成后续章节的基础。

看源码是一个非常细致的活,跟着别人的思路往往无法深入,所以读者必须实操一番才会正真掌握;本系列文章期望能够帮助大家理解关键部分,进而提高学习效率。

ServerBootStrap

ServerBootStrap是Netty服务端服务端的引导类,以io.netty.example.echo工程为例,一个典型的Netty服务端启动过程如下:

//创建bossGroup,指定线程数1
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
//创建workerGroup,不指定线程数
EventLoopGroup workerGroup = new NioEventLoopGroup();

try {
    ServerBootstrap b = new ServerBootstrap();
	 
	 //指定bossGroup,和workerGroup
    b.group(bossGroup, workerGroup)
     //指定监听Channel的类型
     .channel(NioServerSocketChannel.class)
     //设定监听Channel的TCP参数
     .option(ChannelOption.SO_BACKLOG, 100)
     //设定监听Channel的handler
     .handler(new LoggingHandler(LogLevel.INFO))
     //设定读写Channel的handler
     .childHandler(new ChannelInitializer<SocketChannel>() {
         @Override
         public void initChannel(SocketChannel ch) throws Exception {
             ch.pipeline().addLast(new EchoServerHandler());
         }
     });

    //绑定端口,开始监听连接请求
    ChannelFuture f = b.bind(PORT).sync();
    //主线程阻塞,等待监听Channel关闭
    f.channel().closeFuture().sync();
} finally {
    //关闭两个EventLoopGroup,和关闭java.concurent.Executor的原理类似
    bossGroup.shutdownGracefully();
    workerGroup.shutdownGracefully();
}

这一节我们围绕ServerBootstrap的启动过程,从源码角度剖析一下每个步骤背后的工作;按照我们的既定策略,这一步只深入一层,点到即止。

除非明确指出,下面的讨论假设Netty工作以Java NIO为底层接口,相关类型是NioEventLoopGroup,NioServerSocketChannel。

NioEventLoopGroup

无论是Netty客户端和服务端都要依赖EventLoopGroup来处理事件,前面在概念模型介绍中说过,EventLoopGroup是一个EventLoop组,每个EventLoop都是一个线程。我们以NioEventLoopGroup这个具体实现类作为切入点来探索一番。

当我们调用new NioEventLoopGroup()时,后者调用了它的基类MultithreadEventLoopGroup构造方法:

class MultithreadEventLoopGroup {

    private static final int DEFAULT_EVENT_LOOP_THREADS;

    static {
        DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt(
                "io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));
     }
    protected MultithreadEventLoopGroup(int nThreads, Executor executor, Object... args) {
        super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, executor, args);
    }
}

参数nThreads代表EventLoopGrouop线程数,NioEventLoopGroup的默认构造函数,nThreads实参是0,将会被改写为DEFAULT_EVENT_LOOP_THREADS;而后者有两个来源,一是java系统属性:io.netty.eventLoopThreads,二是CPU核数x2,前者优先。也就是说,如果我们没有定义这个系统属性,NioEventLoopGroup线程数要么由参数指定,要么等于CPU核数x2。

继续跟进,来到MultithreadEventLoopGroup的基类MultithreadEventExecutorGroup构造函数:

class MultithreadEventExecutorGroup {
	 private final EventExecutor[] children;
    private final Set<EventExecutor> readonlyChildren;
    private final AtomicInteger terminatedChildren = new AtomicInteger();
    private final Promise<?> terminationFuture = new DefaultPromise(GlobalEventExecutor.INSTANCE);
    private final EventExecutorChooserFactory.EventExecutorChooser chooser;

    protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
                                            EventExecutorChooserFactory chooserFactory, Object... args) {
		
		  
		 //这个executor充当了线程工厂的角色
        if (executor == null) {
            executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
        }

		 //创建一个EventExecutor数组,长度就是nThreads参数
		 //这个数组存放的就是EventLoop对象
        children = new EventExecutor[nThreads];

        for (int i = 0; i < nThreads; i ++) {
        	   //创建EventLoop的地方
            children[i] = newChild(executor, args);
        }
        ...
    }
    
    protected abstract EventExecutor newChild(Executor executor, Object... args) throws Exception;
}

上面代码展示了创建EventLoop的过程,具体创建逻辑还要看newChild方法的实现,于是又回到NioEventLoopGroup:

class NioEventLoopGroup {

    ...
    
    protected abstract EventExecutor newChild(Executor executor, Object... args) throws Exception {
  		 EventLoopTaskQueueFactory queueFactory = args.length == 4 ? (EventLoopTaskQueueFactory) args[3] : null;
  		 
  		 //创建NioEventLoop实例
        return new NioEventLoop(this, executor, (SelectorProvider) args[0],
            ((SelectStrategyFactory) args[1]).newSelectStrategy(), (RejectedExecutionHandler) args[2], queueFactory);
    }
}

可见NioEventLoopGroup最终按照指定的线程数,创建了对应数量的NioEventLoop。

关键点总结:

  • NioEventLoopGroup本质上是一个固定线程数的线程池;
  • NioEventLoopGroup的线程数量可以通过参数指定,也可以默认,默认值是CPU核数x2或java系统属性:io.netty.eventLoopThreads;
  • NioEventLoopGroup的内部线程实例类型是NioEventLoop;
  • EventExecutorChooser替Channel在NioEventLoopGroup选择一个EventLoop,它的默认实现就是轮询,这块不再深挖;

监听Channel的具体类型

SeverBootStrap的channel()方法,定义在基类AbstractBootstrap中:

class AbstractBootstrap {

    //以及ChannelClass创建一个工厂类对象:ReflectiveChannelFactory
    public B channel(Class<? extends C> channelClass) {
        return channelFactory(new ReflectiveChannelFactory<C>(
                ObjectUtil.checkNotNull(channelClass, "channelClass")
        ));
    }

	//指定一个channel工厂
    public B channelFactory(ChannelFactory<? extends C> channelFactory) {
        ObjectUtil.checkNotNull(channelFactory, "channelFactory");
        this.channelFactory = channelFactory;
        return self();
    }
}

所以SeverBootStrap使用简单工厂模式来创建监听Socket Channel。

EventLoopGroup和channel的具体类型需要对应上,NioEventLoopGroup需要配合NioServerSocketChannel来使用。

bind

SeverBootStrap的handler(),option(),childHadle()只是将相关参数保存起来备用,它们的具体作用后续再分析。

我们来看看bind方法是如何工作的,SeverBootStrap.bind方法最终调用了AbstractBootstrap.doBind(略去调用转发和参数变换的中间过程):

class AbstractBootstrap {

   //初始化监听channel,绑定网络端口
   //bind是一个异步操作,所以返回ChannelFuture
   //当ChannelFuture返回成功时,netty服务端启动完成
	private ChannelFuture doBind(final SocketAddress localAddress) {
		  //注册监听channel
        final ChannelFuture regFuture = initAndRegister();
        final Channel channel = regFuture.channel();
        
        //如果注册失败,提前结束
        if (regFuture.cause() != null) {
            return regFuture;
        }

		  //注册已经成功
        if (regFuture.isDone()) {
            ChannelPromise promise = channel.newPromise();
            //执行doBind0,并返回bind操作的Future
            doBind0(regFuture, channel, localAddress, promise);
            return promise;
        } 
        else //绝大多数情况下注册会立即成功,否则需要监听regFuture,等注册成功后继续执行doBind0
        {
            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;
        }
    }
    
    //初始化监听Channel,并注册到bossGroup
    final ChannelFuture initAndRegister() {
        Channel channel = null;
        try {
            //创建channel
            channel = channelFactory.newChannel();
            init(channel);
        } catch (Throwable t) {
			  //异常处理代码已省略
        }

		 //注册channel到config().group(),实际就是bossGroup
        ChannelFuture regFuture = config().group().register(channel);
        if (regFuture.cause() != null) {
        	//异常处理代码已省略
        }
        return regFuture;
    }
    
    //Channel的具体初始化工作由子类来实现
    abstract void init(Channel channel) throws Exception;
    
    //Channel注册bossGroup成功后,调用channel.bind
    private static void doBind0(final ChannelFuture regFuture, final Channel channel,
            						  final SocketAddress localAddress, final ChannelPromise promise) {
        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());
                }
            }
        });
    }
}

AbstractBootstrap.bind的逻辑可分成以下步骤:

  • 调用channelFactory创建channel;
  • 初始化channel,由子类来实现,这里是SeverBootStrap;
  • 将channel注册到bossGroup,细节待探索;
  • 调用channel.bind绑定端口,细节待探索。

上面代码中,注册channel到bossGroup是一个异步操作,通过ChannelFuture(ChannelPromise是ChannelFuture子类)来同步状态。

再看看SeverBootStrap.init如何初始化监听Channel:

Class SeverBootStrap {
    @Override
    void init(Channel channel) {
    		
    	 //这两步将之前保存的channel option,以及一些atrribute copy到channel对象内;
        setChannelOptions(channel, options0().entrySet().toArray(EMPTY_OPTION_ARRAY), logger);
        setAttributes(channel, attrs0().entrySet().toArray(EMPTY_ATTRIBUTE_ARRAY));

        ChannelPipeline p = channel.pipeline();

        final EventLoopGroup currentChildGroup = childGroup;
        final ChannelHandler currentChildHandler = childHandler;
        final Entry<ChannelOption<?>, Object>[] currentChildOptions = childOptions.entrySet().toArray(EMPTY_OPTION_ARRAY);
        final Entry<AttributeKey<?>, Object>[] currentChildAttrs = childAttrs.entrySet().toArray(EMPTY_ATTRIBUTE_ARRAY);

		 //添加一个ChannelInitializer
        p.addLast(new ChannelInitializer<Channel>() {
            @Override
            public void initChannel(final Channel ch) {
                final ChannelPipeline pipeline = ch.pipeline();
                ChannelHandler handler = config.handler();
                
                //这是客户代码调用SeverBootStrap.handler配置的handler
                if (handler != null) {
                    pipeline.addLast(handler);
                }

				  //向channel所在的eventLoop提交了一个task,添加一个叫做ServerBootstrapAcceptor的handler到pipeline
                ch.eventLoop().execute(new Runnable() {
                    @Override
                    public void run() {
                        pipeline.addLast(new ServerBootstrapAcceptor(
                                ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
                    }
                });
            }
        });
    }
}

SeverBootStrap.init方法最关键的逻辑就是添加了一个ServerBootstrapAcceptor到pipeline,回忆Reactor模型,相信你已经猜到这个它的角色。

ServerBootStrap要点总结

ServerBootStrap的启动过程大体如下:

  1. 创建两个EventLoopGroup(bossGroup和workerGroup),可指定线程数量(或默认CPU核数x2),EventLoopGroup内部创建对应数量、对应类型的EventLoop;
    • NioEventLoopGroup内部创建NioEventLoop;
    • NioEventLoopGroup继承自MultithreadEventExecutorGroup,是一个多线程Group,每个NioEventLoop相当于一个线程;
  2. 调用ServerBootStrap.channel配置监听Channel类型;
  3. 调用ServerBootStrap.option设置监听channel参数,调用childOption设置通信channel的参数;
  4. 调用ServerBootStrap.handler设置监听channel的handler,.childHandler设置通信channel的handler;
  5. 调用ServerBootStrap.bind初始化Channel,并完成注册、绑定端口的操作;
    • 内部通过反射创建指定类型的Channel实例,并将相关参数Copy过去;
    • 初始化channel的pipline,添加了一个神秘的ServerBootstrapAcceptor;
    • 给channel分配一个EventLoop,并调用EventLoop.register注册channel,细节待探索;
    • 调用channel.bind方法绑定端口,细节待探索。

这一节基本搞清楚了ServerBootStrap的启动过程,有以下几个遗留问题:

  1. 向EventLoop注册channel是如何实现的?
  2. NioServerSocketChannel的bind方法到底做了什么?

ChannelInitializer

我们抽空聊一下这个ChannelInitializer类,我们在配置ServerBootstrap的childHandler用到它,而上面的源码看到,SeverBootStrap在内部初始化监听channel时也用到了它。
首先它是一个ChannelHandler,但它的存在不是为了处理网络事件,而是为了注册其他ChannelHandler。

@Sharable
public abstract class ChannelInitializer<C extends Channel> extends ChannelInboundHandlerAdapter 
{
	private final Set<ChannelHandlerContext> initMap = Collections.newSetFromMap(
            new ConcurrentHashMap<ChannelHandlerContext, Boolean>());
            
    //初始化Channel接口,由具体类来实现
    //个人认为,该方法叫iniChannelPipeline更好
    protected abstract void initChannel(C ch) throws Exception;
    
    //如果channel注册时,ChannelInitializer已经进入pipeline,那么由该方法来执行initChannel
    @Override
    @SuppressWarnings("unchecked")
    public final void channelRegistered(ChannelHandlerContext ctx) throws Exception {
    		
    	 //当channel注册到EventLoopGroup之后的回调
        if (initChannel(ctx)) {
        	  //因为channel的pipeline被重新初始化了,所以从头fire register事件,让刚加入handler也能收到该事件;
            ctx.pipeline().fireChannelRegistered();
            //初始化完成,就从initMap移除ctx
            removeState(ctx);
        } else {
        	  //如果initChannel已经执行过了,转发事件即可
            ctx.fireChannelRegistered();
        }
    }
    
    //如果channel注册时,ChannelInitializer尚未进入pipeline,那么由handlerAdded回调来执行initChannel
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        if (ctx.channel().isRegistered()) {
            if (initChannel(ctx)) {
                removeState(ctx);
            }
        }
    }
    
    //执行initChannel,调用子类的initChannel实现,并将自己移除
    private boolean initChannel(ChannelHandlerContext ctx) throws Exception {
    	  将ctx加入到initMap,防止重入导致多次初始化
        if (initMap.add(ctx)) {
            try {
                initChannel((C) ctx.channel());
            } catch (Throwable cause) {
                exceptionCaught(ctx, cause);
            } finally {
            		//initChannel成功后,ChannelInitializer完成了使命,将自己从pipeline移除
                ChannelPipeline pipeline = ctx.pipeline();
                if (pipeline.context(this) != null) {
                    pipeline.remove(this);
                }
            }
            return true;
        }
        return false;
    }
    
    //将此ChannelHandlerContext从initMap移除
    //按道理,上面initChannel方法已经将channel从pipeline移除了,至于为什么要考虑移除尚未完成的情况,暂不清楚
    private void removeState(final ChannelHandlerContext ctx) {
        if (ctx.isRemoved()) {
            initMap.remove(ctx);
        } else {
            ctx.executor().execute(new Runnable() {
                @Override
                public void run() {
                    initMap.remove(ctx);
                }
            });
        }
    }
}

上面代码有几个值得注意的点:

  • @Sharable:说明该handler实例可以为多个channel pipeline共享,所以它需要用一个集合来暂存正在初始化的ChannelHandlerContext;
  • 一旦初始化完成,ChannelInitializer就从pipeline删除自己;
  • 无论在channel注册到EvenLoop之前还是之后,都可以使用ChannelInitializer。

ChannelInitializer的存在是很有必要的,因为Channel是动态建立的,我们必须预先配置好pipeline的初始化逻辑,让Netty在创建新Channel之后来执行。不过将ChannelInitializer作为ChannelHandler的实现类,而不是单独作为一个抽象,确实造成了一点混淆。

NioServerSocketChannel的启动

现在我们回过头,解决ServerBootStrap启动过程中留下的两个问题,NioServerSocketChannel是如何注册 和 绑定端口的。

NioServerSocketChannel创建

我们要看一个前面忽略了的问题,关于NioServerSocketChannel创建的细节。ServerBootStrap通过反射创建NioServerSocketChannel实例,它的相关构造代码如下:

public class NioServerSocketChannel extends AbstractNioMessageChannel
                             implements io.netty.channel.socket.ServerSocketChannel {

    private static final ChannelMetadata METADATA = new ChannelMetadata(false, 16);
    private static final SelectorProvider DEFAULT_SELECTOR_PROVIDER = SelectorProvider.provider();

	 //静态方法,通过SelectorProvider创建一个java NIO ServerSocketChannel
    private static ServerSocketChannel newSocket(SelectorProvider provider) {
        try {
            return provider.openServerSocketChannel();
        } catch (IOException e) {
            throw new ChannelException("Failed to open a server socket.", e);
        }
    }

    private final ServerSocketChannelConfig config;

	 //默认构造方法,通过DEFAULT_SELECTOR_PROVIDER创建ServerSocketChannel
    public NioServerSocketChannel() {
        this(newSocket(DEFAULT_SELECTOR_PROVIDER));
    }
    
    //将ServerSocketChannel传递了给父类构造方法
    public NioServerSocketChannel(ServerSocketChannel channel) {
        super(null, channel, SelectionKey.OP_ACCEPT);
        config = new NioServerSocketChannelConfig(this, javaChannel().socket());
    }
}
  • NioServerSocketChannel默认构造方法确实创建了一个java nio ServerSocketChannel;
  • SelectorProvider是JDK提供的创建channel&selector等相关对象的抽象工厂,位于java.nio.channels.spi.SelectorProvider;
  • ServerSocketChannelConfig是channel对外发布配置信息的抽象,是一个面向对象设计技巧,应该没有特别的用意。

NioServerSocketChannel注册

在AbstractBootstrap.initAndRegister的方法中,有一句代码:config().group().register(channel),将NioServerSocketChannel注册到bossGroup,所以我们追踪NioEventLoopGroup.register方法。
发现实现在基类MultithreadEventLoopGroup里。

class MultithreadEventLoopGroup {
    @Override
    public ChannelFuture register(Channel channel) {
        return next().register(channel);
    }
    
    //super.next的实现就是用EventExecutorChooser选择一个EventLoop
    @Override
    public EventLoop next() {
        return (EventLoop) super.next();
    }
}

如上所示,register又被转发给了NioEventLoop;继续追踪发现,NioEventLoop.register实现在基类SingleThreadEventLoop里。

class SingleThreadEventLoop {

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

如上所示,最终又绕回去了,回到了NioServerSocketChannel里面,调用了内部一个unsafe对象的register方法。

经过追踪,这个unSafe对象来自AbstractChannel的内部类——AbstractUnsafe:

class AbstractChannel.AbstractUnsafe implements Unsafe {

    @Override
    public final void register(EventLoop eventLoop, final ChannelPromise promise) {
 		  //这里省略了一些检查代码
 		  ...
        
        //要求注册必须在EventLoop线程执行,这也是为什么注册可能是异步的
        //真正的逻辑在register0方法
        if (eventLoop.inEventLoop()) {
            register0(promise);
        } else {
            try {
                eventLoop.execute(new Runnable() {
                    @Override
                    public void run() {
                        register0(promise);
                    }
                });
            } catch (Throwable t) {
				  //这里省略了异常处理代码
            }
        }
    }
    
    private void register0(ChannelPromise promise) {
		  //由于注册是异步的,这里检查channel是否已经关闭了
        if (!promise.setUncancellable() || !ensureOpen(promise)) {
            return;
        }
 
 		 //这个方法又回到Channel里去了
        doRegister();

		 //如果channel第一次注册,Pipeline触发HandlerAdd事件,即Pipeline所有handler收到handlerAdded回调
        pipeline.invokeHandlerAddedIfNeeded();

        safeSetSuccess(promise);
        
        //Pipeline触发ChannelRegister事件
        pipeline.fireChannelRegistered();
 
        //如果channel现在是活跃的
        if (isActive()) {
            //如果是第一次注册,触发ChannelActive事件
            if (firstRegistration) {
                pipeline.fireChannelActive();
            } 
            //开始读数据,与https://github.com/netty/netty/issues/4805有关
            else if (config().isAutoRead()) {
                beginRead();
            }
        }
    }
}

这个Unsafe类族有必要说明一下,它和JDK里的那个unsafe没有关系,它是Netty对底层IO逻辑的抽象;起名为Unsafe是为了警示用户,不要直接使用它。

继续追踪doRegister方法,这里的实现在AbstractNioChannel里面:

class AbstractNioChannel {
    //ava nio channel
	 private final SelectableChannel ch;
	//java nio SelectionKey
    volatile SelectionKey selectionKey;
    
    @Override
    protected void doRegister() throws Exception {
        boolean selected = false;
        for (;;) {
            try {
                 //javaChannel()返回的就是上面的nio channel
                 //eventLoop().unwrappedSelector()返回的就是nio selector对象;
                selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
                return;
            } catch (CancelledKeyException e) {
                if (!selected) {
                    eventLoop().selectNow();
                    selected = true;
                } else {
                    throw e;
                }
            }
        }
    }
}

上面代码复杂的异常处理咱们暂且不谈,doRegister方法执行的就是:将NioServerSocketChannel内部封装的nio channel注册到NioEventLoop封装的nio Selector。

NioServerSocketChannel绑定端口

前面已经看到AbstractBootstrap.doBind调用了NioServerSocketChannel.bind,它的实现在基类AbstractChannel里面:

class AbstractChannel {
    @Override
    public ChannelFuture bind(SocketAddress localAddress) {
        return pipeline.bind(localAddress);
    }
}

令人惊奇的是,居然将bind转发给了pipeline,我们知道pipeline只会将事件转发给handler,不会处理任何操作。
不过问题是,前面的分析可知,NioServerSocketChannel的pipline目前只有一个ServerBootstrapAcceptor,但是阅读源码发现ServerBootstrapAcceptor并没有实现bind。。。。

经过调试发现,pipeline的默认实现DefaultChannelPipeline,将pineline实现为双向链表,该链表有一个头结点、一个尾部节点,这两个节点可不仅仅是哨兵元素,而是包含逻辑的的,其中头结点HeadContext实现了bind方法:

class DefaultChannelPipeline.HeadContext extends AbstractChannelHandlerContext
        implements ChannelOutboundHandler, ChannelInboundHandler {

    private final Unsafe unsafe;

    HeadContext(DefaultChannelPipeline pipeline) {
        super(pipeline, null, HEAD_NAME, HeadContext.class);
        unsafe = pipeline.channel().unsafe();
        setAddComplete();
    }

    //原来是调用了Channel.unsafe.bind
    @Override
    public void bind(
            ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) {
        unsafe.bind(localAddress, promise);
    }
}      

于是我们又回到了AbstractUnsafe类:

class AbstractChannel.AbstractUnsafe {
 	 @Override
    public final void bind(final SocketAddress localAddress, final ChannelPromise promise) {
		 //这里忽略了部分检查逻辑
		 ...

        boolean wasActive = isActive();
        try {
            //doBind由AbstractChannel的子类实现
            doBind(localAddress);
        } catch (Throwable t) {
            safeSetFailure(promise, t);
            closeIfClosed();
            return;
        }
		 //如果channel绑定后,已经处于Active状态,触发pipeline ChannelActive事件
        if (!wasActive && isActive()) {
            invokeLater(new Runnable() {
                @Override
                public void run() {
                    pipeline.fireChannelActive();
                }
            });
        }
        safeSetSuccess(promise);
    }
}

doBind方法又回到NioServerSocketChannel类:

class NioServerSocketChannel {
	 //其实就是调用了nio ServerSocketChannel的bind方法
	 @Override
    protected void doBind(SocketAddress localAddress) throws Exception {
        if (PlatformDependent.javaVersion() >= 7) {
            javaChannel().bind(localAddress, config.getBacklog());
        } else {
            javaChannel().socket().bind(localAddress, config.getBacklog());
        }
    }
}

至此,bind的逻辑终于理顺了,出人意料地绕了很大一个圈子,细细体会有几点发现:

  • 感觉netty的设计理念是,一旦channel注册完成,所有的操作都应该在EventLoop里面来做,避免多线程并发;
  • bind方法实际是声明在接口ChannelOutboundHandler里,它还声明了connect,close等方法,这些操作从传播方向上看,都是outbound方向,作为ChannelOutboundHandler方法倒也合情合理;
  • PipeLine的头节点HeadContext,实现了connect&close,基本都是转发给Channel的Unsafe成员,其他ChannelOutboundHandler实现就不用操心connect&close;
  • 最终还是调用了Java NIO ServerSocketChannel的bind方法。

NioServerSocketChannel要点总结

NioServerSocketChannel启动的过程,涉及以下几个步骤:

  1. 创建NioServerSocketChannel,它内部封装Java NIO ServerSocketChannel;
  2. 将NioServerSocketChannel注册到NioEventLoopGroup;
    • NioEventLoopGroup给channel分配一个NioEventLoop,channel实际注册到NioEventLoop;
    • 在底层,将ServerSocketChannel(封装在NioServerSocketChannel内)注册到内Selector(封装在NioEventLoop内);
    • NioEventLoop做好了将事件转发到Channel Pipeline的准备;
  3. NioServerSocketChannel绑定到端口;
    • 这个操作通过Channel Pipeline来执行的,接受者是PipeLine的头结点HeadContext;
    • HeadContext最终调用了Channel.unsafe.bind,印证了Channel的底层IO操作都封装在Unsafe抽象内;

Netty Server启动过程总结

通过读源码,我们基本印证了之前了解到的Netty工作模型,核心理念就是:bossGroup相当于Reactor模型中的主Reactor,它本质上是一个线程池(一般配置为单线程模式),用来监听客户端的链接。更重要的是,通过源码我们洞察到以下关键信息:

  • MultithreadEventLoopGroup(所有多线程EventLoopGroup的基类)它的默认线程数是CPU核数x2;
  • NioServerSocketChannel内部确实封装了一个java nio ServerSocketChannel,通过SelectorProvider创建的;
  • ServerBootStrap.bind方法实现了NioServerSocketChannel的初始化、注册、绑定端口三个步骤;
  • ServerBootStrap在初始化NioServerSocketChannel时,将一个类型为ServerBootstrapAcceptor的handler加入了它的pipeline中,从类名基本可以确定,该handler就是Reactor模型中的acceptor角色;
  • NioEvengLoop内有一个Selector,在注册NioServerSocketChannel到EvengLoop时,内部将ServerSocketChannel注册到了Selector;
  • Netty的Channel内部有一个抽象叫Unsafe,它封装了所有底层IO接口调用,channel相关的底层操作最终会经过该抽象,每个具体Channel类型都会实现它,所以以后要调试底层功能的话,我们在这守株待兔即可;
  • 从线程角度来看,Channel的IO操作(包括bind,connect,close)是在EventLoop内执行的,换句话说,是通过pipleline来执行;为此Netty在pipeline内放置了头尾两个节点,实现了相关IO操作(其实就是转发给Channel的Unsafe);

我们虽然仅从NioServerSocketChannel入手分析了Netty Server端的启动过程,其他类型的Channel相信也是大同小异的。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值