Netty核心组件之Channel的启动(ServerBootstrap的doBind方法)

每日一诗

春未老,风细柳斜斜。试上超然台上望,半壕春水一城花。烟雨暗千家。
寒食后,酒醒却咨嗟。休对故人思故国,且将新火试新茶。诗酒趁年华。
——苏轼《望江南·超然台作》

前言

本文主要讲的是Netty服务端启动bind()源码。先来参考下之前写过的Netty demo代码:

package com.gupaoedu.vip.netty.tomcat.nio;

import com.gupaoedu.vip.netty.tomcat.nio.http.GPRequest;
import com.gupaoedu.vip.netty.tomcat.nio.http.GPResponse;
import com.gupaoedu.vip.netty.tomcat.nio.http.GPServlet;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.*;

import java.io.FileInputStream;
import java.net.ServerSocket;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

public class GPTomcat {

    private int port = 8080;

    private ServerSocket server;

    private Properties webxml = new Properties();

    private Map<String, GPServlet> servletMapping = new HashMap<String, GPServlet>();

    public static void main(String[] args) {
        new GPTomcat().start();
    }

    //Tomcat的启动入口
    private void start() {
        //1、加载web.properties文件,解析配置
        init();

        //Boss线程
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        //Worker线程
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        //2、创建Netty服务端对象
        ServerBootstrap server = new ServerBootstrap();

        try {
            //3、配置服务端参数
            server.group(bossGroup, workerGroup)
                    //配置主线程的处理逻辑
                    .channel(NioServerSocketChannel.class)
                    //子线程的回调处理,Handler
                    .childHandler(new ChannelInitializer() {
                        @Override
                        protected void initChannel(Channel client) throws Exception {
                            //处理回调的逻辑

                            //链式编程,责任链模式

                            //处理响应结果的封装
                            client.pipeline().addLast(new HttpResponseEncoder());
                            //用户请求过来,要解码
                            client.pipeline().addLast(new HttpRequestDecoder());
                            //用户自己的业务逻辑
                            client.pipeline().addLast(new GPTomcatHandler());

                        }
                    })
                    //配置主线程分配的最大线程数
                    .option(ChannelOption.SO_BACKLOG, 128)
                    //保持长连接
                    .childOption(ChannelOption.SO_KEEPALIVE, true);

            //启动服务
            ChannelFuture f = server.bind(this.port).sync();

            System.out.println("GP Tomcat 已启动,监听端口是: " + this.port);

            f.channel().closeFuture().sync();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }

    }


    private void init() {
        try {
            String WEB_INF = this.getClass().getResource("/").getPath();
            FileInputStream fis = new FileInputStream(WEB_INF + "web-nio.properties");

            webxml.load(fis);

            for (Object k : webxml.keySet()) {

                String key = k.toString();

                if(key.endsWith(".url")){

                    //将 servlet.xxx.url 的 .url 替换,只剩下 servlet.xxx当成  servletName
                    String servletName = key.replaceAll("\\.url$","");
                    String url = webxml.getProperty(key);

                    //拿到Serlvet的全类名
                    String className = webxml.getProperty(servletName + ".className");

                    //反射创建Servlet的实例
                    GPServlet obj = (GPServlet) Class.forName(className).newInstance();
                    //将URL和Servlet建立映射关系
                    servletMapping.put(url,obj);
                }

            }

        }catch (Exception e){
            e.printStackTrace();
        }
    }

    public class GPTomcatHandler extends ChannelInboundHandlerAdapter{
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

            if(msg instanceof HttpRequest){
                HttpRequest req = (HttpRequest) msg;

                GPRequest request = new GPRequest(ctx,req);
                GPResponse response = new GPResponse(ctx,req);

                String url = request.getUrl();

                if(servletMapping.containsKey(url)){
                    servletMapping.get(url).service(request,response);
                }else{
                    response.write("404 - Not Found!!");
                }

            }


        }
    }

}

整个netty步骤:

  1. 创建对象
  2. 配置参数
  3. 启动服务(bind方法)

本章我们讲解3.启动服务 的代码,前面的创建对象啊,配置参数啦,我们这篇先不讲,后面再讲,先宏观,再微观,能够帮助我们了解建立整个netty。这一句的代码其实就一句话,server.bind(int port):

//启动服务
ChannelFuture f = server.bind(this.port).sync();

本篇文章是源码的追踪与解析,会比较枯燥。

一、服务端启动核心路径总结

1.1 从网上p来的图

1.1.1 服务端时序图

1.1.2 Netty服务端创建时序图以及步骤

下面我们对Netty服务端创建的关键步骤和原理进行讲解。

步骤1:创建ServerBootstrap实例。ServerBootstrap是Netty服务端的启动辅助类,它提供了一系列的方法用于设置服务端启动相关的参数。底层通过门面模式对各种能力进行抽象和封装,尽量不需要用户跟过多的底层API打交道,降低用户的开发难度。

我们在创建ServerBootstrap实例时,会惊讶的发现ServerBootstrap只有一个无参的构造函数,作为启动辅助类这让人不可思议,因为它需要与多个其它组件或者类交互。ServerBootstrap构造函数没有参数的根本原因是因为它的参数太多了,而且未来也可能会发生变化,为了解决这个问题,就需要引入Builder模式。《Effective Java》第二版第2条建议遇到多个构造器参数时要考虑用构建器,关于多个参数构造函数的缺点和使用构建器的优点大家可以查阅《Effective Java》,在此不再详述。

步骤2:设置并绑定Reactor线程池。Netty的Reactor线程池是EventLoopGroup,它实际就是EventLoop的数组。EventLoop的职责是处理所有注册到本线程多路复用器Selector上的Channel,Selector的轮询操作由绑定的EventLoop线程run方法驱动,在一个循环体内循环执行。值得说明的是,EventLoop的职责不仅仅是处理网络I/O事件,用户自定义的Task和定时任务Task也统一由EventLoop负责处理,这样线程模型就实现了统一。从调度层面看,也不存在在EventLoop线程中再启动其它类型的线程用于异步执行其它的任务,这样就避免了多线程并发操作和锁竞争,提升了I/O线程的处理和调度性能。

步骤3:设置并绑定服务端Channel。作为NIO服务端,需要创建ServerSocketChannel,Netty对原生的NIO类库进行了封装,对应实现是NioServerSocketChannel。对于用户而言,不需要关心服务端Channel的底层实现细节和工作原理,只需要指定具体使用哪种服务端Channel即可。因此,Netty的ServerBootstrap方法提供了channel方法用于指定服务端Channel的类型。Netty通过工厂类,利用反射创建NioServerSocketChannel对象。由于服务端监听端口往往只需要在系统启动时才会调用,因此反射对性能的影响并不大。相关代码如下所示:

 

步骤4:链路建立的时候创建并初始化ChannelPipeline。ChannelPipeline并不是NIO服务端必需的,它本质就是一个负责处理网络事件的职责链,负责管理和执行ChannelHandler。网络事件以事件流的形式在ChannelPipeline中流转,由ChannelPipeline根据ChannelHandler的执行策略调度ChannelHandler的执行。典型的网络事件如下:

  1. 链路注册;
  2. 链路激活;
  3. 链路断开;
  4. 接收到请求消息;
  5. 请求消息接收并处理完毕;
  6. 发送应答消息;
  7. 链路发生异常;
  8. 发生用户自定义事件。

步骤5:初始化ChannelPipeline完成之后,添加并设置ChannelHandler。ChannelHandler是Netty提供给用户定制和扩展的关键接口。利用ChannelHandler用户可以完成大多数的功能定制,例如消息编解码、心跳、安全认证、TSL/SSL认证、流量控制和流量整形等。Netty同时也提供了大量的系统ChannelHandler供用户使用,比较实用的系统ChannelHandler总结如下:

  • 系统编解码框架-ByteToMessageCodec;
  • 通用基于长度的半包解码器-LengthFieldBasedFrameDecoder;
  • 码流日志打印Handler-LoggingHandler;
  • SSL安全认证Handler-SslHandler;
  • 链路空闲检测Handler-IdleStateHandler;
  • 流量整形Handler-ChannelTrafficShapingHandler;
  • Base64编解码-Base64Decoder和Base64Encoder。

创建和添加ChannelHandler的代码示例如下:

 

步骤6:绑定并启动监听端口。在绑定监听端口之前系统会做一系列的初始化和检测工作,完成之后,会启动监听端口,并将ServerSocketChannel注册到Selector上监听客户端连接,相关代码如下:

 

步骤7:Selector轮询。由Reactor线程NioEventLoop负责调度和执行Selector轮询操作,选择准备就绪的Channel集合,相关代码如下:

 

步骤8:当轮询到准备就绪的Channel之后,就由Reactor线程NioEventLoop执行ChannelPipeline的相应方法,最终调度并执行ChannelHandler,代码如下:

 

步骤9:执行Netty系统ChannelHandler和用户添加定制的ChannelHandler。ChannelPipeline根据网络事件的类型,调度并执行ChannelHandler。

1.1.3 客户端时序图分析

步骤:

1.用户线程创建Bootstrap实例

2.创建处理客户端连接,I/O读写Reactor线程组NioEventLoopGroup

3.创建NioSocketChannel

4.创建默认的ChannelHandlerPipeline,用户调度和执行网络事件

5.异步发起TCP连接,如果成功将NioSocketChannel注册到多路复用选择器上,监听读操作位,用于数据读取和消息发送,如果失败,注册连接操作位到多路复用选择 器,等待连接结果

6.注册对应的网络监听状态位到多路复用选择器

7.由多路复用选择器轮询Channel,处理连接结果

8.如果连接成功,设置Future结果,发送连接成功事件,触发ChannelHandlerPipeline执行

9.由ChannelHandlerPipeline调度和执行系统和用户ChannelHandler

 

二、服务端channel的创建

2.1从bind()方法开始

我们点进去上面的bind()方法会进去到AbstractBootstrap#bind方法(为了让读者更容易找到源码,本篇文章不光会贴源码,也会附上相应的的源码图片):

public ChannelFuture bind(int inetPort) {
        return this.bind(new InetSocketAddress(inetPort));
    }

该方法有多个重载方法,但核心作用只有一个,就是将参数转为InetSocketAddress对象传给 --->bind方法.

通过端口号创建一个 InetSocketAddress,然后继续bind:

public ChannelFuture bind(SocketAddress localAddress) {
        this.validate();
        if (localAddress == null) {
            throw new NullPointerException("localAddress");
        } else {
            return this.doBind(localAddress);
        }
    }

validate() 验证服务启动需要的必要参数,然后调用doBind()

doBind()方法,他的入参是localAddress,就是我们前面new InetSocketAddress(inetPort)出来的。

private ChannelFuture doBind(final SocketAddress localAddress) {
        // 1)、初始化和注册,重要***
        final ChannelFuture regFuture = this.initAndRegister();
        final Channel channel = regFuture.channel();
        if (regFuture.cause() != null) {
            return regFuture;
        } else if (regFuture.isDone()) {
            ChannelPromise promise = channel.newPromise();
            // 2)、将SocketAddress和channel绑定起来,最终执行的是nio中的功能(重要**)
            doBind0(regFuture, channel, localAddress, promise);
            return promise;
        } else {
             // 省略异常判断、添加监听器和异步调用doBind0方法(暂且不管)
            
    }

为方便关联对照,下面再粘贴一个简单的原生NIO编程的服务端初始化方法,其实doBind方法的逻辑基本就是对下面这个方法的封装,只是增加了很多附加功能。

这里,我去掉了细枝末节,让我们专注于核心方法,其实就两大核心一个是 initAndRegister(),以及doBind0()。

其实,从方法名上面我们已经可以略窥一二,init->初始化,register->注册,那么到底要注册到什么呢?联系到nio里面轮询器的注册,可能是把某个东西初始化好了之后注册到selector上面去,最后bind,像是在本地绑定端口号,带着这些猜测,我们深入下去。

2.2 initAndRegister方法

final ChannelFuture initAndRegister() {
        Channel channel = null;

        try {
            // 1)、实例化channel,作为服务端初始化的是NioServerSocketChannel
            channel = this.channelFactory.newChannel();
            // 2)、初始化channel,即给channel中的属性赋值
            this.init(channel);
        } catch (Throwable var3) {
            if (channel != null) {
                channel.unsafe().closeForcibly();
            }

            return (new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE)).setFailure(var3);
        }
        // 3)、注册,即最终是将channel 注册到selector上
        ChannelFuture regFuture = this.config().group().register(channel);
        if (regFuture.cause() != null) {
            if (channel.isRegistered()) {
                channel.close();
            } else {
                channel.unsafe().closeForcibly();
            }
        }

        return regFuture;
    }

我们还是专注于核心代码,抛开边角料,我们看到 initAndRegister() 做了几件事情

  1. new一个channel
  2. init这个channel
  3. 将这个channel register到某个对象

2.2.1 new一个channel

从上面的代码,我们发现这条channel是通过一个 channelFactory new出来的,channelFactory 的接口很简单:

public interface ChannelFactory<T extends Channel> extends io.netty.bootstrap.ChannelFactory<T> {
    T newChannel();
}

就一个方法,我们查看channelFactory被赋值的地方:

在这里被赋值,我们层层回溯,查看该函数被调用的地方,发现最终是在这个函数中,ChannelFactory被new出:

这里,我们的demo程序调用channel(channelClass)方法的时候,将channelClass作为ReflectiveChannelFactory的构造函数创建出一个ReflectiveChannelFactory:

demo端的代码如下:

.channel(NioServerSocketChannel.class);

然后回到本节最开始:

channelFactory.newChannel();

我们就可以推断出,最终是调用到 ReflectiveChannelFactory.newChannel() 方法,跟进 :

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

看出来没?是通过反射创建的。

看到clazz.newInstance();,我们明白了,原来是通过反射的方式来创建一个对象,而这个class就是我们在ServerBootstrap中传入的NioServerSocketChannel.class
结果,绕了一圈,最终创建channel相当于调用默认构造函数new出一个 NioServerSocketChannel对象。

1)、new NioServerSocketChannel()

接下来我们就可以将重心放到 NioServerSocketChannel的默认构造函数:

private static final SelectorProvider DEFAULT_SELECTOR_PROVIDER = SelectorProvider.provider();

public NioServerSocketChannel() {
        this(newSocket(DEFAULT_SELECTOR_PROVIDER));
    }
private static java.nio.channels.ServerSocketChannel newSocket(SelectorProvider provider) {
        try {
            return provider.openServerSocketChannel();
        } catch (IOException var2) {
            throw new ChannelException("Failed to open a server socket.", var2);
        }
    }
public NioServerSocketChannel(java.nio.channels.ServerSocketChannel channel) {
        super((Channel)null, channel, 16);
        this.config = new NioServerSocketChannel.NioServerSocketChannelConfig(this, this.javaChannel().socket());
    }

可见,它先通过newSocket方法获取nio原生的ServerSocketChannel,然后传给了重载构造器,如下,其中第三行是对NioServerSocketChannelConfig  config进行了赋值,逻辑比较简单,下面主要看对父类构造方法的调用。 

2)、对NioServerSocketChannel父类构造方法的调用

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

它又调了父类,它的父类是AbstactNioChannel.class:

这里,简单地将前面 provider.openServerSocketChannel(); 创建出来的 ServerSocketChannel 保存到成员变量,然后调用ch.configureBlocking(false);设置该channel为非阻塞模式,标准的jdk nio编程的玩法。这里的 readInterestOp 即前面层层传入的 SelectionKey.OP_ACCEPT。然后重点看对父类构造器的调用——AbstactChannel.class。

3)、AbstractChannel构造器

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

可以看到,此构造方法只是给四个属性进行了赋值,我们挨个看下这四个属性。

第一个属性是this.parent,类型为io.netty.channel.Channel,但此时值为null;
第二个属性id——每个channel都会生成。类型为io.netty.channel.ChannelId,就是一个id生成器,值为new DefaultChannelId();
第三个属性unsafe——在每个channel之上,要去完成数据读写的对象。类型为io.netty.channel.Channel.Unsafe,该属性很重要,封装了对事件的处理逻辑,最终调用的是AbstractNioMessageChannel中的newUnsafe方法,赋的值为new NioMessageUnsafe();
第四个属性pipeline——数据处理的流程全在这里。类型为io.netty.channel.DefaultChannelPipeline,该属性很重要,封装了handler处理器的逻辑,赋的值为 new DefaultChannelPipeline(this)  this即当前的NioServerSocketChannel对象。

其中DefaultChannelPipeline的构造器需要额外看一下,如下,将NioServerSocketChannel对象存入channel属性,然后初始化了tail、head两个成员变量,且对应的前后指针指向对方。TailContext和HeadContext都继承了AbstractChannelHandlerContext,在这个父类里面维护了next和prev两个双向指针,看到这里有经验的园友应该一下子就能看出来,DefaultChannelPipeline内部维护了一个双向链表。

这几个个对象不是jdk nio包提供的,是netty封装的

protected DefaultChannelPipeline(Channel channel) {
        this.channel = (Channel)ObjectUtil.checkNotNull(channel, "channel");
        this.succeededFuture = new SucceededChannelFuture(channel, (EventExecutor)null);
        this.voidPromise = new VoidChannelPromise(channel, true);
        this.tail = new DefaultChannelPipeline.TailContext(this);
        this.head = new DefaultChannelPipeline.HeadContext(this);
        this.head.next = this.tail;
        this.tail.prev = this.head;
    }

 

至此,完成了上面initAndRegister方法中的第一个功能:channel的实例化。此时NioServerSocketChannel的几个父类属性快照图如下所示:

 2.2.2 init(channel)方法

init(channel)方法位于ServerBootstrap中(因为这里是通过ServerBootstrap过来的,如果是通过Bootstrap进入的这里则调用的就是Bootstrap中的init方法),主要功能如下注释所示。本质都是针对channel进行初始化,初始化channel中的option、attr和pipeline。

void init(Channel channel) throws Exception {
        // 1、获取AbstractBootstrap中的options属性,与channel进行关联
        final Map<ChannelOption<?>, Object> options = options0();
        synchronized (options) {
            channel.config().setOptions(options);
        }
         // 2、获取AbstractBootstrap中的attr属性,与channel关联起来
        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());
            }
        }
         // 3、获取pipeline,并将一个匿名handler对象添加进去,重要***
        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(childOptions.size()));
        }
        synchronized (childAttrs) {
            currentChildAttrs = childAttrs.entrySet().toArray(newAttrArray(childAttrs.size()));
        }

        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) {
                    pipeline.addLast(handler);
                }

                ch.eventLoop().execute(new Runnable() {
                    @Override
                    public void run() {
                        pipeline.addLast(new ServerBootstrapAcceptor(
                                currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
                    }
                });
            }
        });
    }

初次看到这个方法,可能会觉得,哇塞,老长了,这可这么看?,庖丁解牛,逐步拆解,最后归一,下面是我的拆解步骤:

1)设置option和attr

 // 1、获取AbstractBootstrap中的options属性,与channel进行关联
        final Map<ChannelOption<?>, Object> options = options0();
        synchronized (options) {
            channel.config().setOptions(options);
        }
         // 2、获取AbstractBootstrap中的attr属性,与channel关联起来
        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());
            }
        }

通过这里我们可以看到,这里先调用options0()以及attrs0(),然后将得到的options和attrs注入到channelConfig或者channel中,关于option和attr是干嘛用的,后面再作了解。

2)设置新接入channel的option和attr

 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(childOptions.size()));
        }
        synchronized (childAttrs) {
            currentChildAttrs = childAttrs.entrySet().toArray(newAttrArray(childAttrs.size()));
        }

这里,和上面类似,只不过不是设置当前channel的这两个属性,而是对应到新进来连接对应的channel,由于我们这篇文章只关心到server如何启动,接入连接放到以后再做详细剖析。

3)加入新连接处理器

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) {
                    pipeline.addLast(handler);
                }

                ch.eventLoop().execute(new Runnable() {
                    @Override
                    public void run() {
                        pipeline.addLast(new ServerBootstrapAcceptor(
                                currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
                    }
                });
            }
        });

到了最后一步,p.addLast()向serverChannel的流水线处理器中加入了一个 ServerBootstrapAcceptor,从名字上就可以看出来,这是一个接入器,专门接受新请求,把新的请求扔给某个事件循环器,我们先不做过多分析

我们总结一下,我们发现其实init也没有启动服务,只是初始化了一些基本的配置和属性,以及在pipeline上加入了一个接入器,用来专门接受新连接,我们还得继续往下跟。

2.2.3 config().group().register(channel)方法

1)config().group()方法

由前面可以知道,config().group().register(channel)这行代码位于AbstractBootstrap类中的initAndRegister方法中,但由于当前对象是ServerBootstrap,故此处config()方法实际调用的都是ServerBootstrap中重写的方法,得到了ServerBootstrapConfig。

    ServerBootstrapConfig的group方法如下,调用的是它的父类AbstractBootstrapConfig中的方法。通过类名就能知道,ServerBootstrapConfig中的方法是获取ServerBootstrap中的属性,而AbstractBootstrapConfig中的方法是获取AbstractBootstrap中的属性,两两对应。故此处获取的EventLoopGroup就是AbstractBootstrap中存放的group,即文章开头demo中的boss对象。

public final EventLoopGroup group() {
        return bootstrap.group();
    }

获取到了名叫boss的这个NioEventLoopGroup对象,下面追踪NioEventLoopGroup.register(channel)方法。

2) NioEventLoopGroup.register(channel)方法

NioEventLoopGroup继承类图:

2.1)next()方法 

下面的register方法位于MultithreadEventLoopGroup类中,是NioEventLoopGroup的直接父类,如下:

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

next方法如下,调用了父类的next方法,下面的就是父类MultithreadEventExecutorGroup中的next实现,可以看到调用的是chooser的next方法。通过初始化流程可知,此处boss的线程数是1,是2的n次方,所以chooser就是PowerOfTwoEventExecutorChooser,通过next方法从EventExecutor[]中选择一个对象。需要注意的是chooser.next()通过轮询的方式选择的对象。

@Override
    public EventLoop next() {
        return (EventLoop) super.next();
    }
@Override
    public EventExecutor next() {
        return chooser.next();
    }

2.2)NioEventLoop.register方法

 next之后是register方法,中间将NioServerSocketChannel和当前的NioEventLoop封装成一个DefaultChannelPromise对象往下传递,在下面第二个register方法中可以看到,实际上调用的是NioServerSocketChannel中的unsafe属性的register方法。

@Override
    public ChannelFuture register(Channel channel) {
        return register(new DefaultChannelPromise(channel, this));
    }
@Override
    public ChannelFuture register(ChannelPromise promise) {
        ObjectUtil.checkNotNull(promise, "promise");
        promise.channel().unsafe().register(this, promise);
        return promise;
    }

2.3)NioMessageUnsafe的register方法

@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;
            }
            // 判断eventLoop类型是否匹配
            if (!isCompatible(eventLoop)) {
                promise.setFailure(
                        new IllegalStateException("incompatible event loop type: " + eventLoop.getClass().getName()));
                return;
            }
            //完成eventLoop属性的赋值
            AbstractChannel.this.eventLoop = eventLoop;
            // 判断eventLoop中的Reactor线程是不是当前线程 ***重要1
            if (eventLoop.inEventLoop()) {
                // 进行注册
                register0(promise);
            } else {
                try {
                    // 不是当前线程则将register0任务放入eventLoop队列中让Reactor线程执行(如果Reactor线程未初始化还要将其初始化) ***重要2
                    eventLoop.execute(new Runnable() {
                        @Override
                        public void run() {
                            // 注册逻辑 ***重要3
                            register0(promise);
                        }
                    });
                } catch (Throwable t) {
                   //省略异常处理。。。
                }
            }
        }

该方法位于io.netty.channel.AbstractChannel.AbstractUnsafe中(它是NioMessageUnsafe的父类),根据注释能了解每一步做了什么,但如果要理解代码逻辑意图则需要结合netty的串行无锁化(串行无锁化参见netty系列一篇文章https://www.cnblogs.com/zzq6032010/p/12872993.html)。它实际就是让每一个NioEventLoop对象的thread属性记录一条线程,用来循环执行NioEventLoop的run方法,后续这个channel上的所有事件都由这一条线程来执行,如果当前线程不是Reactor线程,则会将任务放入队列中,Reactor线程会不断从队列中获取任务执行。这样以来,所有事件都由一条线程顺序处理,线程安全,也就不需要加锁了。

    说完整体思路,再来结合代码看看。上述代码中标识【***重要1】的地方就是通过inEventLoop方法判断eventLoop中的thread属性记录的线程是不是当前线程:

    先调到父类AbstractEventExecutor中,获取了当前线程:

public boolean inEventLoop() {
        return this.inEventLoop(Thread.currentThread());
    }

然后调到SingleThreadEventExecutor类中的方法,如下,比对thread与当前线程是否是同一个:

public boolean inEventLoop(Thread thread) {
        return thread == this.thread;
    }

此时thread未初始化,所以肯定返回false,则进入【***重点2】的逻辑,将register放入run方法中封装成一个Runnable任务,然后执行execute方法,如下,该方法位于SingleThreadEventExecutor中:

public void execute(Runnable task) {
        if (task == null) {
            throw new NullPointerException("task");
        } else {
            boolean inEventLoop = this.inEventLoop();
            if (inEventLoop) {
                //将任务放入队列中 ***重要a
                this.addTask(task);
            } else {
                //判断当前线程不是thread线程,则调用该方法 ***重要b
                this.startThread();
                this.addTask(task);
                if (this.isShutdown() && this.removeTask(task)) {
                    reject();
                }
            }

            if (!this.addTaskWakesUp && this.wakesUpForTask(task)) {
                this.wakeup(inEventLoop);
            }

        }
    }

有两个重要的逻辑,已经在上面代码中标出,先看看【***重要a】,如下,可见最终就是往SingleThreadEventExecutor的taskQueue队列中添加了一个任务,如果添加失败则调reject方法执行拒绝策略,通过前文分析可以知道,此处的拒绝策略就是直接抛错。

protected void addTask(Runnable task) {
        if (task == null) {
            throw new NullPointerException("task");
        } else {
            if (!this.offerTask(task)) {
                this.reject(task);
            }

        }
    }
final boolean offerTask(Runnable task) {
        if (this.isShutdown()) {
            reject();
        }

        return this.taskQueue.offer(task);
    }

然后在看【***重要b】,如下,该方法虽然叫startThread,但内部有控制,不能无脑开启线程,因为调这个方法的时候会有两种情况:1).thread变量为空;2).thread不为空且不是当前线程。第一种情况需要开启新的线程,但第二种情况就不能直接创建线程了。所以看下面代码可以发现,它内部通过CAS+volatile(state属性加了volatile修饰)实现的开启线程的原子控制,保证多线程情况下也只会有一个线程进入doStartThread()方法。

private void startThread() {
        if (STATE_UPDATER.get(this) == 1 && STATE_UPDATER.compareAndSet(this, 1, 2)) {
            this.doStartThread();
        }

    }

 继续往下看一下doStartThread()的方法逻辑:

private void doStartThread() {
        assert thread == null;
        executor.execute(new Runnable() { //此处的executor内部执行的就是ThreadPerTaskExecutor的execute逻辑,创建一个新线程运行下面的run方法
            @Override
            public void run() {
                thread = Thread.currentThread(); //将Reactor线程记录到thread变量中,保证一个NioEventLoop只有一个主线程在运行
                if (interrupted) {
                    thread.interrupt();
                }

                boolean success = false;
                updateLastExecutionTime();
                try {
                    SingleThreadEventExecutor.this.run(); //调用当前对象的run方法,该run方法就是Reactor线程的核心逻辑方法,后面会重点研究
                    success = true;
                } catch (Throwable t) {
                    logger.warn("Unexpected exception from an event executor: ", t);
                } finally {
                   // 省略无关逻辑
                }
            }
        });
    }

可以看到,在上面的方法中完成了Reactor线程thread的赋值和核心逻辑NioEventLoop中run方法的启动。这个run方法启动后,第一步做的事情是什么?让我们往前回溯,回到3.2.3),当然是执行当初封装了 register0方法的那个run方法的任务,即执行register0方法,下面填之前埋得坑,对【***重要3】进行追踪:

private void register0(ChannelPromise promise) {
            try {
                // 省略判断逻辑
                boolean firstRegistration = neverRegistered;
                doRegister();// 执行注册逻辑
                neverRegistered = false;
                registered = true;
                pipeline.invokeHandlerAddedIfNeeded();// 调用pipeline的逻辑

                safeSetSuccess(promise);
                pipeline.fireChannelRegistered();
                // 省略无关逻辑
            } catch (Throwable t) {
                // 省略异常处理
            }
        }

doRegister()方法的实现在AbstractNioChannel中,如下,就是完成了nio中的注册,将nio的ServerSocketChannel注册到selector上:

protected void doRegister() throws Exception {
        boolean selected = false;
        for (;;) {
            try {
                selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
                return;
            } catch (CancelledKeyException e) {
               // 省略异常处理
            }
        }
    }

 再看pipeline.invokeHandlerAddedIfNeeded()方法,该方法调用链路比较长,此处就不详细粘贴了,只是说一下流程。回顾下上面第二部分的第2步,在里面最后addLast了一个匿名的内部对象,重写了initChannel方法,此处通过pipeline.invokeHandlerAddedIfNeeded()方法就会调用到这个匿名对象的initChannel方法(只有第一次注册时才会调),该方法往pipeline中又添加了一个ServerBootstrapAcceptor对象。执行完方法后,netty会在finally中将之前那个匿名内部对象给remove掉,这时pipeline中的handler如下所示:

 

     至此,算是基本完成了initAndRegister方法的逻辑,当然限于篇幅(本篇已经够长了),其中还有很多细节性的处理未提及。

三、AbstractBootstrap的doBind0方法

我们发现,在调用doBind0(...)方法的时候,是通过包装一个Runnable进行异步化的,关于异步化task,可以看下这篇文章,netty源码分析之揭开reactor线程的面纱(三)

 private static void doBind0(
            final ChannelFuture regFuture, final Channel channel,
            final SocketAddress localAddress, final ChannelPromise promise) {

        // This method is invoked before channelRegistered() is triggered.  Give user handlers a chance to set up
        // the pipeline in its channelRegistered() implementation.
        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()方法:AbstractChannel.java

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

发现是调用pipeline的bind方法:

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

相信你对tail是什么不是很了解,我也不了解,可以翻到最开始,tail在创建pipeline的时候出现过,关于pipeline和tail对应的类,我后面源码系列会详细解说,这里,你要想知道接下来代码的走向,唯一一个比较好的方式就是debug 单步进入,篇幅原因,我就不详细展开。

tail.bind方法:

@Override
    public ChannelFuture bind(final SocketAddress localAddress, final ChannelPromise promise) {
        if (localAddress == null) {
            throw new NullPointerException("localAddress");
        }
        if (!validatePromise(promise, false)) {
            // cancelled
            return promise;
        }
         // 从tail开始往前,找到第一个outbond的handler,这时只有head满足要求,故此处next是head
        final AbstractChannelHandlerContext next = findContextOutbound();
        EventExecutor executor = next.executor();
        // 因为当前线程就是executor中的Reactor线程,所以直接进入invokeBind方法
        if (executor.inEventLoop()) {
            next.invokeBind(localAddress, promise);
        } else {
            safeExecute(executor, new Runnable() {
                @Override
                public void run() {
                    next.invokeBind(localAddress, promise);
                }
            }, promise, null);
        }
        return promise;
    }

 下面进入head.invokeBind方法:

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

  核心逻辑就是handler.bind方法,继续追踪:

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

这里的unsafe就是前面提到的 AbstractUnsafe, 准确点,应该是 NioMessageUnsafe;我们进入到它的bind方法:

@Override
public final void bind(final SocketAddress localAddress, final ChannelPromise promise) {
    // ...
    boolean wasActive = isActive();
    // ...
    doBind(localAddress);

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

继续追踪会看到在bind方法中又调用了NioServerSocketChannel中的doBind方法,最终在这里完成了nio原生ServerSocketChannel和address的绑定:

NioServerSocketChannel.class:

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

 至此,ServerBootstrap的bind方法完成。

四、总结

最后,我们来做下总结,netty启动一个服务所经过的流程

  1. 设置启动类参数,最重要的就是设置channel
  2. 创建server对应的channel,创建各大组件,包括ChannelConfig,ChannelId,ChannelPipeline,ChannelHandler,Unsafe等
  3. 初始化server对应的channel,设置一些attr,option,以及设置子channel的attr,option,给server的channel添加新channel接入器,并出发addHandler,register等事件
  4. 调用到jdk底层做端口绑定,并触发active事件,active触发的时候,真正做服务端口绑定

参考文章:https://zhuanlan.zhihu.com/p/37512332
https://www.cnblogs.com/zzq6032010/p/13034608.html
https://blog.csdn.net/weixin_33904756/article/details/85976683 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值