Netty服务端启动过程源码解析

Netty服务端创建时序图

  1. 创建ServerBootstrap实例
    ServerBootstrap是一个用于启动Netty服务端的辅助类,提供一系列方法设置启动参数。因为ServerBootstrap需要设置的各项信息很多,所以这里采用builder模式实现。

  2. 设置并绑定Reactor线程池
    通过构造函数创建ServerBootstrap实例之后,通常会创建两个EventLoopGroup(并不是必须要创建两个不同的EventLoopGroup,也可以只创建一个并共享),代码如下所示:
    EventLoopGroup bossGroup = new NioEventLoopGroup();
    EventLoopGroup workerGroup = new NioEventLoopGroup();
    BossEventLooPGroup和WorkerEventLoopGroup。BossEventLoopGroup通常是一个单线程的EventLoop,EventLoop维护着一个注册了ServerSocketChannel的Selector实例,BoosEventLoop不断轮询Selector将连接事件分离出来,通常是OP_ACCEPT事件,然后将accept得到的SocketChannel交给WorkerEventLoopGroup。WorkerEventLoopGroup会选择其中一个EventLoopGroup来将这个SocketChannel注册到其维护的Selector并对其后续的IO事件进行处理。在Reactor模式中BossEventLoopGroup主要是对多线程的扩展,而每个EventLoop的实现涵盖IO事件的分离和分发。

    NioEventLoopGroup实际就是Reactor线程池,负责调度和执行客户端的接入、网络读写事件的处理、用户自定义任务和定时任务的执行。通过ServerBootstrap的group方法将两个EventLoopGroup实例传入,代码如下:
    public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) {
            super.group(parentGroup);
            if (childGroup == null) {
                throw new NullPointerException("childGroup");
            }
            if (this.childGroup != null) {
                throw new IllegalStateException("childGroup set already");
            }
            this.childGroup = childGroup;
            return this;
        }

    Netty为了更好的利用多核CPU的资源对Reactor模式进行了改进,每个Netty服务端程序中有多个EventLoop同时运行,每一个EventLoop维护一个Selector实例,进行Reactor模式的工作。此外,EventLoop的职责除了处理IO事件,还包括处理定时任务和用户自定义任务。Netty还提供了一个ioRatio参数,供使用者调整EventLoop运行不同任务时间的比例。这样的设计避免了多线程并发操作和锁竞争,提升了I/O线程的处理和调度性能。

    Netty基于单线程设计的EventLoop能够同时处理成千上万的客户端连接的IO事件,缺点是单线程不能够处理时间过长的任务,这样会阻塞使得IO事件的处理被阻塞,严重的时候回造成IO事件堆积,服务不能够高效响应客户端请求。,当我们遇到需要处理时间很长的任务的时候,我们可以将它交给子线程来处理,主线程继续去EventLoop,当子线程计算完毕再讲结果交给主线程。
  3. 设置并绑定服务端Channel
    线程组和线程类型设置完成后,需要设置服务端Channel,Netty通过Channel工厂类来创建不同类型的Channel,对于服务端,需要创建NioServerSocketChannel,所以,通过指定Channel类型的方式创建Channel工厂。ServerBootstrapChannelFactory是ServerBootstrap的内部静态类,职责是根据Channel的类型通过反射创建Channel的实例,服务端需要创建的是NioServerSocketChannel实例,channel方法代码如下:
    public B channel(Class channelClass) {
            if (channelClass == null) {
                throw new NullPointerException("channelClass");
            }
            return channelFactory(new BootstrapChannelFactory(channelClass));
        }

    Channel工厂类:
    @SuppressWarnings("unchecked")
        public B channelFactory(ChannelFactory channelFactory) {
            if (channelFactory == null) {
                throw new NullPointerException("channelFactory");
            }
            if (this.channelFactory != null) {
                throw new IllegalStateException("channelFactory set already");
            }
    
            this.channelFactory = channelFactory;
            return (B) this;
        }
4.设置TCP参数
  • 指定NioServerSocketChannel后,需要设置TCP的一些参数,作为服务端,主要是要设置TCP的 backlog参数。backlog指定了内核为此套接口排队的最大连接个数,对于给定的监听套接口,内核要维护两个队列,未链接队列和已连接队列,根据TCP三路握手过程中三个分节来分隔这两个队列。
5.链路建立时创建ChannelPipeline
  • ChannelPipeline的本质就是一个负责处理网络事件的职责链,负责管理和执行ChannelHandler。每个Channel都持有一个ChannelPipeline,网络事件以事件流的形式在ChannelPipeline中流转,由ChannelPipeline根据ChannelHandler的执行策略调度ChannelHandler的执行。值得注意的是ChannelPipeline并不直接持有ChannelHandler,而是用一个ChannelHandlerContext类包装ChannelHandler。
6.设置ServerBootstrap的handler
  • TCP参数设置完成后,用户可以为启动辅助类和其父类分别指定Handler,两类Handler的用途不同,子类中的Hanlder是NioServerSocketChannel对应的ChannelPipeline的Handler,父类中的Hanlder是客户端新接入的连接SocketChannel对应的ChannelPipeline的Handler。两者的区别可以通过下图来展示:


本质区别就是:ServerBootstrap中的Handler是NioServerSocketChannel使用的,所有连接该监听端口的客户端都会执行它,父类AbstractBootstrap中的Handler是个工厂类,它为每个新接入的客户端都创建一个新的Handler。

ChannelHandler是Netty提供给用户定制和扩展的关键接口。利用ChannelHandler,用户可以完成大多数的功能定制,例如消息编解码、心跳、安全认证、TSL/SSL认证、流量控制和流量整形等。Netty同事也提供了一些现成的Channel供使用,如:编解码框架ByteToMessageCodec、基于长度的半包解码器LengthFieldBasedFrameDecoder、日志打印LoggerHandler、SSL安全认证SslHandler、链路空闲检测IdleStateHandler、流量整形ChannelTrafficShapingHandler、Base64编解码Base64Encoder和Base64Decoder等。

添加ChannelHandler的具体方式如下:
.childHandler(new ChannelInitializer() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        ch.pipeline().addLast(new HttpRequestDecoder());
                        ch.pipeline().addLast(new HttpObjectAggregator(Integer.MAX_VALUE));
                        ch.pipeline().addLast(new HttpResponseEncoder());
                        ch.pipeline().addLast(new HttpServerInboundHandler());
                    }
                });


7.绑定并启动监听端口
在绑定监听端口之前系统会做一系列的初始化和检测工作,完成之后,会启动监听端口,并将ServerSocketChannel注册到Selector上监听客户端连接。具体实现如下:
private ChannelFuture doBind(final SocketAddress localAddress) {
    final ChannelFuture regFuture = initAndRegister();
    final Channel channel = regFuture.channel();
    if (regFuture.cause() != null) {
        return regFuture;
    }
 
    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) {
                    promise.setFailure(cause);
                } else {
                    promise.executor = channel.eventLoop();
                }
                doBind0(regFuture, channel, localAddress, promise);
            }
        });
        return promise;
    }
}
init
void init(Channel channel) throws Exception {
    final Map, Object> options = options();
    //设置相关参数
    synchronized (options) {
        channel.config().setOptions(options);
    }
 
    final Map, Object> attrs = attrs();
    synchronized (attrs) {
        for (Entry, Object> e: attrs.entrySet()) {
            @SuppressWarnings("unchecked")
            AttributeKey key = (AttributeKey) e.getKey();
            channel.attr(key).set(e.getValue());
        }
    }
 
    //将ServerBootstrap的handler添加到channel的pipeline中
    ChannelPipeline p = channel.pipeline();
    if (handler() != null) {
        p.addLast(handler());
    }
 
    //将用于服务端注册的handler:ServerBootstrapAcceptor注册到channel的pipeline中
    final EventLoopGroup currentChildGroup = childGroup;
    final ChannelHandler currentChildHandler = childHandler;
    final Entry, Object>[] currentChildOptions;
    final Entry, Object>[] currentChildAttrs;
    synchronized (childOptions) {
        currentChildOptions = childOptions.entrySet().toArray(newOptionArray(childOptions.size()));
    }
    synchronized (childAttrs) {
        currentChildAttrs = childAttrs.entrySet().toArray(newAttrArray(childAttrs.size()));
    }
 
    p.addLast(new ChannelInitializer() {
        @Override
        public void initChannel(Channel ch) throws Exception {
            ch.pipeline().addLast(new ServerBootstrapAcceptor(
                    currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
        }
    });
} 
    

可以看到,初始化工作主要包含三点:(1)设置Socket参数;(2)将ServerBootstrap的handler添加到channel的pipeline中;(3)将ServerBootstrapAcceptor注册到channel的pipeline中。

 Channel初始化完成之后,在doBind0方法中将Channel绑定到指定端口上:

private static void doBind0(
        final ChannelFuture regFuture, final Channel channel,
        final SocketAddress localAddress, final ChannelPromise promise) {
    //此方法在channelRegistered()被触发前执行,使用者可以覆盖channelRegistered方法来定制pipeline
    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());
            }
        }
    });
}

8.Selector轮询
到此,Netty服务端监听的相关资源已经初始化完毕,接下来就是讲NioServerSocketChannel注册到Reactor线程的Selector上,然后轮询客户端连接事件。NioServerSocketChannel注册的代码如下:

public final void register(EventLoop eventLoop, final ChannelPromise promise) {
    //此处代码省略
 
    if (eventLoop.inEventLoop()) {
        register0(promise);
    } else {
        try {
            eventLoop.execute(new OneTimeTask() {
                @Override
                public void run() {
                    register0(promise);
                }
            });
        } catch (Throwable t) {
            //此处代码省略
        }
    }
}

首先判断该操作是否是由eventLoop自身发起的,如果是就直接注册,否则就将注册操作封装成一个Task放异步队列中执行。此处由于是ServerBootstrap所在线程执行的操作,所以会将Task放到NioEventLoop中执行。具体注册逻辑的代码如下:

private void register0(ChannelPromise promise) {
    try {
        if (!promise.setUncancellable() || !ensureOpen(promise)) {
            return;
        }
        doRegister();
        registered = true;
        safeSetSuccess(promise);
        pipeline.fireChannelRegistered();
        if (isActive()) {//判断ServerSocketChannel监听是否成功,如果成功则触发ChannelActive事件
            pipeline.fireChannelActive();
        }
    } catch (Throwable t) {       
        //此处代码省略
    }
}
真正实现注册到逻辑:
protected void doRegister() throws Exception {
    boolean selected = false;
    for (;;) {
        try {
            selectionKey = javaChannel().register(eventLoop().selector, 0, this);
            return;
        } catch (CancelledKeyException e) {
            //此处代码省略
        }
    }
}

此处注册时没有注册OP_ACCEPT(16),而是注册0,表示只注册,不监听任何网络事件。这样实现的原因有两点:(1)注册方法是多态的,它可以被NioServerSocketChannel用来监听客户端连接事件,也可以被SocketChannel用来监听网络读或者写操作;(2)监听位可以通过SelectionKey的interestOpos方法方便的修改,所以此处通过register操作获取SelectionKey并给AbstractNioChannel的成员变量selectionKey赋值。

注册成功之后,触发pipelineChannelRegistered事件,ChannelRegistered事件在pipeline中处理后,判断ServerSocketChannel监听是否成功,如果成功则触发ChannelActive事件。isActive方法也是个多态方法,在服务端就是判断监听是否成功,在客户端就是判断TCP连接是否完成。pipeline处理ChannelActive事件,完成之后根据配置决定是否出发Channel的读操作:

public ChannelPipeline fireChannelActive() {
    head.fireChannelActive();
 
    if (channel.config().isAutoRead()) {
        channel.read();
    }
 
    return this;
}

AbstractChannel的读操作触发pipeline的读操作,最终调用HeadHandler的读操作。HeadHandler的读操作则是调用channel自身的成员变量unsafe的beginRead方法,unsafe是channel类的一个内部接口,基本上所有的网络I/O操作都是由Unsafe类负责实现的。

protected void doBeginRead() throws Exception {
    // Channel.read() or ChannelHandlerContext.read() was called
    if (inputShutdown) {
        return;
    }
 
    final SelectionKey selectionKey = this.selectionKey;
    if (!selectionKey.isValid()) {
        return;
    }
 
    readPending = true;
 
    final int interestOps = selectionKey.interestOps();
    if ((interestOps & readInterestOp) == 0) {
        selectionKey.interestOps(interestOps | readInterestOp);//修改注册的操作位
    }
}

由于不同类型的channel对读操作的准备工作不同,所以doBeginRead也是个多态方法,对于NIO通信,客户端和服务端都需要修改网络监听操作位,对于NioServerSocketChannel来说就是OP_ACCEPT,于是修改注册的操作位为OP_ACCEPT。在doBeginRead方法中是将其修改为readInterestOp,而创建NioServerSocketChannel时会将readInterestOp设置成OP_ACCEPT:

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

9.当轮询到准备就绪的Channel之后,就有Reactor线程执行ChannelPipeline的相应方法,最终调度并执行相应的ChannelHandler,包括Netty系统ChannelHandler和用户自定义的ChannelHandler。

你可能感兴趣的:(netty)