netty源码解析-启动引导(1) 服务端启动流程

导读

原创文章,转载请注明出处。

本文源码地址:netty-source-code-analysis

本文所使用的netty版本4.1.6.Final:带注释的netty源码

我们在“BIO vs NIO”这篇文件中我们给出了使用jdk原生nio编写的服务端Hello World。还记得其中的关键步骤吗,咱们再来温习一下。

  1. 创建一个ServerSocketChannel

  2. 将ServerSocketChannel设置为非阻塞的

  3. 将ServerSocketChannel绑定到8000端口

  4. 将ServerSocketChannel注册到selector上

今天我们就以这几个关键步骤为目标来看一下在netty中是怎么做的,以及在这几个步骤的中间netty又多做了哪些工作。

1 服务端引导代码

以下代码引导启动一个服务端,在以下文章中我们以“引导代码”指代这段程序。这段代码很简单,创建两个EventLoopGroup分别为bossGroupworkerGroup。创建一个ServerBoostrap并将bossGroupworkerGroup传入,配置一个handler,该handler为监听端口这条连接所使用的handler。接着又设置了一个childHandler即新连接所使用的handler,本篇文章我们不讲新连接的接入,所以这里的childHandler里什么也没做。

运行这段这段代码将在控制台打出如下结果。

HandlerAdded
ChannelRegistered
ChannelActive

/**
 * 欢迎关注公众号“种代码“,获取博主微信深入交流
 *
 * @author wangjianxin
 */
public class com.zhongdaima.netty.analysis.bootstrap.ServerBoot {
    public static void main(String[] args) throws InterruptedException {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup(1);
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.TCP_NODELAY, true)
                    .attr(AttributeKey.valueOf("ChannelName"), "ServerChannel")
                    .handler(new ChannelInboundHandlerAdapter() {
                        @Override
                        public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
                            System.out.println("ChannelRegistered");
                        }

                        @Override
                        public void channelActive(ChannelHandlerContext ctx) throws Exception {
                            System.out.println("ChannelActive");
                        }

                        @Override
                        public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
                            System.out.println("HandlerAdded");
                        }
                    }).childHandler(new ChannelInboundHandlerAdapter(){

            });
            ChannelFuture f = b.bind(8000).sync();
            f.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

2 启动过程

我们从ChannelFuture f = b.bind(8000).sync()bind方法往下跟到AbstractBootStrapdoBind方法,这中间的过程很简单,就是将端口号封装为SocketAddress

doBind内的关键代码有第一行的initAddRegister方法,还有后面的doBind0方法。

private ChannelFuture doBind(final SocketAddress localAddress) {
        final ChannelFuture regFuture = initAndRegister();
        if (regFuture.isDone()) {
             doBind0(regFuture, channel, localAddress, promise);
        } else {
            
            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) {
                    } else {
                        doBind0(regFuture, channel, localAddress, promise);
                    }
                }
            });
            return promise;
        }
}

进入到initAndRegister方法,initAddResgiter方法中有3个关键步骤,1是channelFactory.newChannel(),2是init(channel),3是config().group().register(channel)

final ChannelFuture initAndRegister() {
        Channel channel = null;
        try {
            //创建一个Channel
            channel = channelFactory.newChannel();
            //初始化Channel
            init(channel);
        } catch (Throwable t) {
           
        }
        //注册Channel
        ChannelFuture regFuture = config().group().register(channel);
        ...
}

整个doBind方法被分成4个关键步骤,分别是:

  1. channelFacotry.newChannel()
  2. init(channel)
  3. config().group().register(channel)
  4. doBind0

接下来咱们分别来看这4个关键步骤。

2.1 channelFacotry.newChannel() 新创建一个Channel

channelFacotryAbstractBootStrap的一个属性,这个属性在哪里被赋值呢,其实是在我们在启动时调用b.channel(NioServerSocketChannel)时赋的值,这个方法在AbstractBootStrap里,非常简单,我们不再分析。最后的结果是channelFactory被赋值为ReflectiveChannelFactory,顾名思义就是用反射的方法创建Channel,我看们一下其中的newChannel()方法,很简单,clazz.newInstance调用无参构造方法创建实例。

public class ReflectiveChannelFactory<T extends Channel> implements ChannelFactory<T> {
    private final Class<? extends T> clazz;

    @Override
    public T newChannel() {
        try {
            return clazz.newInstance();
        } catch (Throwable t) {
            throw new ChannelException("Unable to create Channel from class " + clazz, t);
        }
    }
}

接下来咱们就看一下NioServerSocketChannel的无参构造方法,其中调用newSocket方法创建了一个jdk的ServerSocketChannel。好了,咱们已经看到了导读中提到的第1步“创建一个ServerSocketChannel”,紧着把这个channel传递给了父类的构造方法,还传递一个参数SelectionKey.OP_ACCEPT,记住这个参数后面会提到

public NioServerSocketChannel() {
        this(newSocket(DEFAULT_SELECTOR_PROVIDER));
}

private static ServerSocketChannel newSocket(SelectorProvider provider) {
    try {
        return provider.openServerSocketChannel();
    } catch (IOException e) {

    }
}

public NioServerSocketChannel(ServerSocketChannel channel) {
    super(null, channel, SelectionKey.OP_ACCEPT);
    config = new NioServerSocketChannelConfig(this, javaChannel().socket());
}

咱们接着跟到父类AbstractNioMessageChannel的构造方法,没什么其他操作,继续调用父类的构造方法。

protected AbstractNioMessageChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
    super(parent, ch, readInterestOp);
}

接着跟下去,到了AbstractNioChannel的构造方法,在这里我们看到了ch.configureBlocking(false),至此我们看到了导读中提到的第2步“将Channel设置为非阻塞的”AbstractNioChannel里又调用了父类的构造方法,接着看下去。

    protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
        super(parent);
        this.ch = ch;
        this.readInterestOp = readInterestOp;
        try {
            //将Channel设置为非阻塞的
            ch.configureBlocking(false);
        } catch (IOException e) {
            throw new ChannelException("Failed to enter non-blocking mode.", e);
        }
    }

到了AbstractChannel的构造方法,这里为Channel创建了一个id,一个Unsafe还有一个PipeLineUnsafePipeLine咱们后面再讲。

protected AbstractChannel(Channel parent) {
    this.parent = parent;
    id = newId();
    unsafe = newUnsafe();
    pipeline = newChannelPipeline();
}

2.2 init(channel) 初始化Channel

我们回到AbstractBootstrapinitAndRegister方法,接着往下看init(channel),这是个抽象方法,实现在ServerBootstrap里。

init方法的主要逻辑是设置Channel参数、属性,并将我们在引导代码中所配置的Handler添加进去,最后又添加了一个ServerBootStrapAccptor,顾名思义这是一个处理新连接接入的Handler

这个ServerBootStrapAccptor在随后的章节中我们会讲,这里先略过。至于为什么调用ch.eventLoop().execute而不是直接添加,这个我在代码里有简要提示,其实目前的版本,直接添加也是没有问题的。这个我会在出视频教程的时候给大家演示一下,欢迎关注。

void init(Channel channel) throws Exception {
        //设置Channel参数,我们在引导代码中通过.option(ChannelOption.TCP_NODELAY, true)所设置的参数
        final Map<ChannelOption<?>, Object> options = options0();
        synchronized (options) {
            channel.config().setOptions(options);
        }
        //设置Channel属性,我们在引导代码中通过.attr(AttributeKey.valueOf("ChannelName"), "ServerChannel")所设置的属性
        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();

        //p.addLast是同步调用,不管是不是EventLoop线程在执行,这个匿名的ChannelInitializer被立即添加进PipeLine中
        //但是这个匿名的ChannelInitializer的initChannel方法是被channelAdded方法调用的,而channelAdded方法只能被EventLoop线程调用
        //此时这个Channel还没绑定EventLoop线程,所以这个匿名的ChannelInitializer的channelAdded方法的调用会被封装成异步任务添加到PipeLine的pendingHandlerCallback链表中
        //当Channel绑定EventLoop以后会从pendingHandlerCallback链表中取出任务执行。
        p.addLast(new ChannelInitializer<Channel>() {
            @Override
            public void initChannel(Channel ch) throws Exception {
                final ChannelPipeline pipeline = ch.pipeline();
                ChannelHandler handler = config.handler();
                if (handler != null) {
                    //添加我们在引导代码中所配置的handler
                    pipeline.addLast(handler);
                }

                //有些同学对这个有疑问,为什么不直接pipeline.addLast,可以参考下面的issue,其实现在的版本已经可以直接改成pipeline.addLast
                //issue链接https://github.com/netty/netty/issues/5566
                //为什么现在的版本可以直接改成pipeline.adLast呢,关键在于ChannelInitializer的handlerAdded方法
                //大家可以对比4.0.39.Final版本和4.1.6.Final版本的区别
                //添加ServerBootStrapAcceptor
                ch.eventLoop().execute(new Runnable() {
                    @Override
                    public void run() {
                        pipeline.addLast(new ServerBootstrapAcceptor(
                                currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
                    }
                });
            }
        });
    }

2.3 config().group().register(channel)绑定EventLoop并向Selector注册Channel

我们回到AbstractBootstrapinitAndRegister方法,接着往下看到ChannelFuture regFuture = config().group().register(channel);,这里就是注册Channel的地方了,咱们跟进去看看。

config.group()的返回是我们在引导代码中所设置的bossGroup,由于这里只有一个Channel,所以bossEventLoopGroup里面只需要1个EventLoop就够了。

跟到register(channel)方法里看看,这个register方法是抽象的,具体实现在MultithreadEventLoopGroup中,跟进去。

@Override
public ChannelFuture register(Channel channel) {
    return next().register(channel);
}

next()方法调用EventExecutorChoosernext()方法选择一个EventLoopEventExecutorChooser有两个实现,分别是PowerOfTowEventExecutorChooserGenericEventExecutorChooser,这两个Chooser用的都是轮询策略,只是轮询算法不一样。如果EventLoopGroup内的EventLoop个数是2的幂,则用PowerOfTowEventExecutorChooser,否则用GenericEventExecutorChooser

PowerOfTowEventExecutorChooser使用位操作。

@Override
public EventExecutor next() {
    return executors[idx.getAndIncrement() & executors.length - 1];
}

GenericEventExecutorChooser使用取余操作。

@Override
public EventExecutor next() {
    return executors[Math.abs(idx.getAndIncrement() % executors.length)];
}

EventLoop的选择算法上我们可以看出,netty为了性能,无所不用其极

chooser属性的赋值在MultithreadEventExecutorGroup的构造方法内通过chooserFactory创建的。

protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
                                        EventExecutorChooserFactory chooserFactory, Object... args) {
    chooser = chooserFactory.newChooser(children);
}

chooserFactory的赋值在MultithreadEventExecutorGroup的另一个构造方法内。当我们在引导代码中通过new NioEventLoopGroup(1)创建EventLoopGroup时最终会调用到这个构造方法内,默认值为DefaultEventExecutorChooserFactory.INSTANCE

protected MultithreadEventExecutorGroup(int nThreads, Executor executor, Object... args) {
    this(nThreads, executor, DefaultEventExecutorChooserFactory.INSTANCE, args);
}

next()方法选出的EventLoop就是个SingleThreadEventLoop了,我们跟到SingleThreadEventLoopregister方法,最终调用的是unsaferegister方法。

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

unsafe.register方法在io.netty.channel.AbstractChannel.AbstractUnsafe内,我们跟下去看看。在register方法中最主要的有两件事,一是绑定eventloop,二是调用register0方法。此时的调用线程不是EventLoop线程,会发起一个异步任务。

 @Override
public final void register(EventLoop eventLoop, final ChannelPromise promise) {
    //绑定eventloop
    AbstractChannel.this.eventLoop = eventLoop;
    
    if (eventLoop.inEventLoop()) {
        register0(promise);
        //此时我们不在EventLoop内,也就是当前线程非EventLoop线程,会走到这个分支
    } else {
        try {
            eventLoop.execute(new Runnable() {
                @Override
                public void run() {
                    //调用子类的register0方法
                    register0(promise);
                }
            });
        } catch (Throwable t) {
           
        }
    }
}
        

register0方法内主要有3步操作。

第1步是doRegister(),这个咱们稍后说。

第2步是pipeline.invokeHandlerAddedIfNeeded()这一步是去完成那些在绑定EventLoop之前触发的添加handler操作,比如我们添加了一个ChannelInitializer,在ChannelInitalizerinitChannel方法中添加的Handler,而initChannelchannelAdded方法调用,channelAdded方法的调用必须在EventLoop内,未绑定EventLoop之前这个调用会被封装成异步任务。

这些操作被放在pipeline中的pendingHandlerCallbackHead中,是个双向链表,具体请参考DefaultChannelPipeLineaddLast(EventExecutorGroup group, String name, ChannelHandler handler)方法。

这一步调用了咱们的引导程序中的System.out.println("HandlerAdded"),在控制台打出"HandlerAdded"

第3步触发ChannelRegistered事件。这一步调用了咱们的引导程序中的System.out.println("ChannelRegistered"),在控制台打出"ChannelRegistered"

好了,到这里我们已经知道了,为什么我们的引导程会先打出"HandlerAdded""ChannelRegistered"

接着往下isActive()最终调用是的jdk ServerSocket类的isBound方法,咱们不再贴出代码,读者自行查看,很简单,显然这里我们还没有完成端口绑定,所以这个if分支的代码并不会执行。

    private void register0(ChannelPromise promise) {
        try {
            //向Selector注册Channel
            doRegister();
           
            //去完成那些在绑定EventLoop之前触发的添加handler操作,这些操作被放在pipeline中的pendingHandlerCallbackHead中,是个链表,具体请参考`DefaultChannelPipeLine`的`addLast(EventExecutorGroup group, String name, ChannelHandler handler)`方法。
            pipeline.invokeHandlerAddedIfNeeded();
            
            //将promise设置为成功的
            safeSetSuccess(promise);

            //触发ChannelRegistered事件
            pipeline.fireChannelRegistered();
            
            //这里并没有Active,因为此时还没完成端口绑定,所以这个if分支的代码都不会执行
            if (isActive()) {
                if (firstRegistration) {
                    pipeline.fireChannelActive();
                } else if (config().isAutoRead()) {
                    beginRead();
                }
            }
        } catch (Throwable t) {
        }
    }

接下来咱们跟进去doRegister方法,这是个抽象方法,本例中方法实现在AbstractNioChannel中。好了,到这里我们终于看到了导读中提到的第4步“向Selector注册Channel”的操作

    @Override
    protected void doRegister() throws Exception {
        boolean selected = false;
        for (;;) {
            try {
                selectionKey = javaChannel().register(eventLoop().selector, 0, this);
                return;
            } catch (CancelledKeyException e) {
                
            }
        }
    }

到了这里,我们在导读中说的总共4步操作中,还有第3步没有看到,在哪里呢,接着往下看。

2.4 绑定端口号 doBind0

前文中我们说过doBind方法内有两个重要调用initAndRegisterdoBind0initAndRegister我们已经分析完了,接下来看doBind0。由于initAndRegisterregister是异步操作,当initAndRegister返回时,register操作有可能完成了,也有可能没完成,这里做了判断,如果已经完成则直接调用doBind0,如果未完成,则将doBind0放到regFutureListener中,等register操作完成后,由EventLoop线程来回调。

那么什么时候会回调Listener呢,当调用promisesetSuccess或者setFailure时回调。还记得上文中的AbstractUnsafe.register0方法吗,其中有一个调用safeSetSuccess(promise),对,就是这里了,很简单,我们不再赘述。

private ChannelFuture doBind(final SocketAddress localAddress) {
    final ChannelFuture regFuture = initAndRegister();
    if (regFuture.isDone()) {
        ChannelPromise promise = channel.newPromise();
        doBind0(regFuture, channel, localAddress, promise);
        return promise;
    } else {
        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) {
                  
                } else {
                    doBind0(regFuture, channel, localAddress, promise);
                }
            }
        });
        return promise;
    }
}

那么又有读者疑问了,在这个if判断完成之后到添加Listener之间的这个时间,promise有可能已经完成了,Listener可能不会回调了, 奥秘在DefaultPromiseaddListener(GenericFutureListener<? extends Future<? super V>> listener)方法里,这里注册完Listener之后,如果发现promise已经完成了,那么将直接调用nofityListeners方法向EventLoop提交异步任务(此时已经完成绑定EventLoop),该异步任务即是回调刚刚注册的Listener

@Override
public Promise<V> addListener(GenericFutureListener<? extends Future<? super V>> listener) {
    synchronized (this) {
        addListener0(listener);
    }
    if (isDone()) {
        notifyListeners();
    }
    return this;
}

咱们回归正题,去看doBind0方法,这里调用了channel.bind方法,具体实现在AbstractChannel里。

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

AbstractChannel里的bind方法调用了pipeline.bind,还记得一篇“Netty整体架构”文章中的那张图吗,咱们再次放出来。

netty整体架构图

bind方法会首先调用Tail的bind方法,最终传播到Headbind方法,具体怎么传播的,咱们讲PipeLine的时候再说。

@Override
public ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) {
    return pipeline.bind(localAddress, promise);
}

这里咱们直接跟到HeadContextbind方法, 我们看到又调用了unsafebind方法,前面我们看到Channel在向Selector注册时最终也调用到了unsafe。这里先跟大家说一下unsafe是netty中最直接跟Channel接触的类,对Channel的所有操作最终都会落到unsafe上,具体详情咱们后面讲unsafe的时候再说。

@Override
public void bind(
        ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise)
        throws Exception {
    unsafe.bind(localAddress, promise);
}

具体实现在AbstractUnsafe中,bind方法中两个重要操作,一是调用doBind方法绑定端口,这个稍后说。二是触发ChannelActive事件,这一步有一个isActive判断,到这里我们已经完成了端口绑定,所以是true。这一步调用了咱们引导程序中的System.out.println("ChannelActive")在控制台打印出"ChannelActive"

@Override
public final void bind(final SocketAddress localAddress, final ChannelPromise promise) {
   
    boolean wasActive = isActive();
    try {
        doBind(localAddress);
    } catch (Throwable t) {
    }

    if (!wasActive && isActive()) {
        invokeLater(new Runnable() {
            @Override
            public void run() {
                pipeline.fireChannelActive();
            }
        });
    }
}

doBind方法的实现在NioServerSocketChannel中,我们一起来看一下,至此导读中提到的第3步操作“绑定端口”,我们已经看到了,服务端启动完成。

protected void doBind(SocketAddress localAddress) throws Exception {
    if (PlatformDependent.javaVersion() >= 7) {
        javaChannel().bind(localAddress, config.getBacklog());
    } else {
        javaChannel().socket().bind(localAddress, config.getBacklog());
    }
}
2.4.1 注册兴趣事件在哪里

但是似乎是不是少了点什么,我们在使用jdk api编写的时候,向selector注册的时候,传递了兴趣事件的,为什么我们没有看到这里有兴趣事件的注册呢。我们继续回到AbstractUnsafebind方法中,最后调用了pipeline.fireChannelActive(),下面是PipeLinefireChannleActive方法,调用了AbstractChannelHandlerContext.invokeChannelActive(head),而这个head就是我们的“netty整体架构图”中的HeadContext

@Override
public final ChannelPipeline fireChannelActive() {
    AbstractChannelHandlerContext.invokeChannelActive(head);
    return this;
}

static void invokeChannelActive(final AbstractChannelHandlerContext next) {
EventExecutor executor = next.executor();
if (executor.inEventLoop()) {
    next.invokeChannelActive();
} else {
    executor.execute(new Runnable() {
        @Override
        public void run() {
            next.invokeChannelActive();
        }
    });
}
}

HeadContext中的channelActive方法如下,奥秘在readIfIsAutoRead里,readIfIsAutoRead,最终调用了channel.read

@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
    ctx.fireChannelActive();

    readIfIsAutoRead();
}

private void readIfIsAutoRead() {
    if (channel.config().isAutoRead()) {
        channel.read();
    }
}

channel.read方法的实现在AbstractChannel中,调用到了pipeline.read

@Override
public Channel read() {
    pipeline.read();
    return this;
}

PipeLine中的read方法如下,调用了tailread方法,最终这个调用会传播到headread方法,具体的传播过程,等咱们讲PipeLine的时候再说。咱们直接去看HeadContextread方法。

@Override
public final ChannelPipeline read() {
    tail.read();
    return this;
}

HeadContextread方法又调用到unsafe.beginRead()

@Override
public void read(ChannelHandlerContext ctx) {
    unsafe.beginRead();
}

beginRead方法的实现在AbstractUnsafe中,这里调用了doBeginReaddoBeginRead方法的实现在AbstractNioChannel中。

@Override
public final void beginRead() {
    try {
        doBeginRead();
    } catch (final Exception e) {
    
    }
}

doBeginRead方法的实现在AbstractNioChannel中,这里修改了selectionKey的兴趣事件,把已有的兴趣事件interestOpsreadInterestOp合并在一起重新设置。

interestOps是现有的兴趣事件,在上文中向Selector注册时的代码里javaChannel().register(eventLoop().selector, 0, this),所以interestOps就是0。

readInterestOp在哪里设置的呢,还记得本篇文章中新创建一个Channel那一小节中吗?

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

NioServerSocketChannel调用父类的构造方法时传递了一个兴趣事件参数,值为SelectionKey.OP_ACCEPT,至此,真相大白。

public NioServerSocketChannel(ServerSocketChannel channel) {
    super(null, channel, SelectionKey.OP_ACCEPT);
    config = new NioServerSocketChannelConfig(this, javaChannel().socket());
}

九曲连环,我们终于找到了这么小小的一个点,为什么流程这么长呢,似乎很难理解,不要紧,继续关注我的文章,咱们讲PipeLine的时候会把这里讲明白。

3 总结

netty服务端启动流程:

  1. 创建一个Channel实例,这个过程中将Channel设置为非阻塞的,为Channel创建了PipeLineUnsafe

  2. 初始化Channel,为Channel设置参数和属性,并添加ServerBootstrapAcceptor这个特殊的Handler

  3. 注册Channel,为Channel绑定一个EventLoop并向Selector注册Channel

  4. 绑定端口。


关于作者

王建新,转转架构部资深Java工程师,主要负责服务治理、RPC框架、分布式调用跟踪、监控系统等。爱技术、爱学习,欢迎联系交流。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,265评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,078评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,852评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,408评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,445评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,772评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,921评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,688评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,130评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,467评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,617评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,276评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,882评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,740评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,967评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,315评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,486评论 2 348