Netty服务端是如何启动的

前言

Netty是一个用于构建高性能、可拓展、支持异步事件驱动的Java网络框架,基于该框架我们可以快速各种网络协议下的高性能Java网络应用程序。本篇文章基于自编写的引导类进行Netty服务端启动的流程进行详细分析和讲解。

注意由于Netty服务端启动逻辑繁多,建议读者进行阅读时,可以基于源码跟随笔者的脚步一起调试,方便理解和掌握全流程。

我们使用Netty编写服务端时需要基于ServerBootstrap 这个引导类完成服务端配置工作,配置ServerBootstrap配置步骤大体如下:

  1. 创建ServerBootstrap 引导类。
  2. 基于ServerBootstrap 类完成IO模型、选项设置、属性设置、处理器设置。
  3. 基于ServerBootstrap绑定端口和异步启动服务端。

我们通过代码完成上述配置并启动Java应用程序之后,Netty服务端的启动整体是进行以下几个步骤:

  1. channel创建。
  2. channel初始化。
  3. channel注册。
  4. channel绑定。

本文就会基于这4个大步骤,对Netty服务端启动源码进行深入分析。

channel简介

在此之前,我们需要先了解一下channel的概念,channel用于连接字节缓冲区ByteBuf以及对端实体。其中对端的实体可以是一个File,也可以和服务端进行网络通信的Socket连接。

Netty网络编程模型中,它对原生JDKServerSocketChannel加以拓展和封装,使之具备特性:

  1. 具有唯一标识身份信息的id
  2. channel可具备父子关系,即当前channel可以作为另一个channel的子channel,使其共享父通道的事件处理逻辑,并且可以通过父通道来管理和控制。
  3. 增加pipeline管理业务处理流程。
  4. 读写可基于底层unsafe类实现更加高效的操作。
  5. 关联一个NioEventLoop处理各类读写请求。

AbstractChannel 最核心的抽象类,我们可以将其理解为Channel类的基本骨架,从它继承出了AbstractNioChannelAbstractOioChannelAbstractEpollChannelLocalChannelEmbeddedChannel等类,每个类代表了不同的协议以及相应的IO模型。

对此,我们列出一些比较常用的 Channel 类型:

  1. NioSocketChannel:代表异步的客户端 TCP Socket 连接。
  2. NioServerSocketChannel:异步的服务器端 TCP Socket 连接。
  3. NioDatagramChannel:异步的 UDP 连接。
  4. NioSctpChannel:异步的客户端 Sctp 连接。
  5. NioSctpServerChannel:异步的 Sctp 服务器端连接。
  6. OioSocketChannel:同步的客户端 TCP Socket 连接。
  7. OioServerSocketChannel:同步的服务器端 TCP Socket 连接。
  8. OioDatagramChannel:同步的 UDP 连接。
  9. OioSctpChannel:同步的 Sctp 服务器端连接。
  10. OioSctpServerChannel:同步的客户端 TCP Socket 连接。

Netty服务端启动源码解析

代码示例

为了更好的源码,我们给出一段可以调试的代码示例,如下所示,这就是我们上文所说的服务端配置和启动的示例代码,每一段配置的含义笔者都已给出详尽注释,读者可自行参阅:

public static void main(String[] args) throws Exception {
        //Netty封装了NIO,Reactor模型,Boss,worker
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            //创建ServerBootstrap,ServerBootstrap可以理解为Nio中的ServerSocketChannel
            ServerBootstrap b = new ServerBootstrap();

            //开始设置各种参数
            b.group(bossGroup, workerGroup)
                    //设置IO模型
                    .channel(NioServerSocketChannel.class)
                    //用于给每个连接都设置一些TCP参数
                    .childOption(ChannelOption.TCP_NODELAY, true)
                    //给每一个连接设置attr
                    .childAttr(AttributeKey.newInstance("childAttr"), "childAttrValue")
                    //设置IO处理器
                    .handler(new ServerHandler())
                    //定义后续每个连接的数据读写,对于业务处理逻辑
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) {
                            ch.pipeline().addLast(new AuthHandler());
                            //..

                        }
                    });

            // option() 方法 用于给服务端Channel设置一些TCP参数 SO_BACKLOG 表示系统用于临时存放已完成三次握手的请求的队列的最大长度
            b.option(ChannelOption.SO_BACKLOG, 1024);

            // attr() 方法 用于给 NioServerSocketChannel 维护一个 Map,通常也用不到这个方法
            b.attr(AttributeKey.newInstance("serverName"), "nettyServer");

            ChannelFuture f = b.bind(8888).sync();

            f.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

可以看到在笔者在配置时用到了一个ServerHandler来处理服务端请求,逻辑也比较简单,继承ChannelInboundHandlerAdapter 重写每一个阶段的方法,输出对应的方法名。

public class ServerHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        System.out.println("channelActive");
    }

    @Override
    public void channelRegistered(ChannelHandlerContext ctx) {
        System.out.println("channelRegistered");
    }

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) {
        System.out.println("handlerAdded");
    }

    @Override
    public void channelRead(final ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("channelRead");
    }
    
}

完成编码工作之后,我们将Netty服务端启动,并通过命令行输入下面这段命令尝试和服务端建立连接:

telnet 127.0.0.1 8888

最终可以在控制台看到这样3条输出,由此可知,在服务端启动后会回调这3个方法,接下来我们就可以通过调试的源码的方式了解一下服务端的启动流程。

handlerAdded
channelRegistered
channelActive

启动流程概述

我们需要在Netty服务端启动流程的入口即下面这个bind方法,我们以这段代码为入口开始调试:

 b.bind(8888).sync();

步入bind方法可以看到其内部整体做了两件事:

  1. 创建一个套接字地址InetSocketAddress对象,该对象包含地址和端口号的信息。
  2. 将套接字地址对象InetSocketAddress作为参数传入并调用bind方法。
	
    public ChannelFuture bind(int inetPort) {
    	//1. 创建一个套接字地址
    	//2. 基于InetSocketAddress完成bind操作
        return bind(new InetSocketAddress(inetPort));
    }

继续步进bind方法,它进行了如下几个工作:

  1. 调用validate方法对groupchannelFactory进行判空校验,如果为空则抛出异常,反之进入步骤2。
  2. 判断传入的SocketAddress 是否为空,若为空则抛出异常,反之进入步骤3。
  3. 调用doBind方法。
public ChannelFuture bind(SocketAddress localAddress) {
		//对group和channelFactory进行判空校验
        validate();
        //SocketAddress 判空
        if (localAddress == null) {
            throw new NullPointerException("localAddress");
        }
        //调用doBind完成服务端启动核心步骤
        return doBind(localAddress);
    }

步入的AbstractBootstrapdoBind方法,即可看到笔者所说的4大步骤:

  1. 调用initAndRegister完成channel创建、初始化、以及selector注册。
  2. 调用doBind0完成端口号的绑定。
private ChannelFuture doBind(final SocketAddress localAddress) {
		//channel创建和初始化以及selector注册
        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 {
           	//略
            return promise;
        }
    }

服务端Channel创建

基于反射完成服务端channel创建

上述doBind方法我们看到Netty服务端大体的初始化流程,接下来我们就对每一步进行深入的了解和分析,先来看看创建服务端Channel这一步,我们步入上文的initAndRegister方法,可以看到其调用channelFactorynewChannel方法,那么这个方法内部是如何完成创建的呢?

final ChannelFuture initAndRegister() {
        Channel channel = null;
        try {
            channel = channelFactory.newChannel();
            init(channel);
        } catch (Throwable t) {
             //略
        }

       //略

        return regFuture;
    }

步入channelFactory后我们来到了ReflectiveChannelFactorynewChannel方法,可以看到它会通过JDK的反射方法完成服务端Channel创建。

@Override
    public T newChannel() {
        try {
        	//基于反射完成channel的创建
            return clazz.newInstance();
        } catch (Throwable t) {
            throw new ChannelException("Unable to create Channel from class " + clazz, t);
        }
    }

我们在调试时基于IDEA查看clazz的类名,可以发现它的类类型是NioServerSocketChannel。所以我们这里大胆才测,它是否就是我们配置阶段基于channel方法所设置的NioServerSocketChannel.class,然后在启动阶段,Netty服务端通过这个类类型完成反射创建。

		// 配置线程组并指定NIO模型
        serverBootstrap.group(bossGroup, workerGroup)
                //设置IO模型,这里为NioServerSocketChannel,建议Linux服务器使用 EpollServerSocketChannel
                .channel(NioServerSocketChannel.class)
              //略

为了印证这一点,我们步入channel方法查看一下逻辑,可以看到它会将我们的channelClass传入ReflectiveChannelFactory中完成反射工厂的声明,然后调用channelFactory方法。

public B channel(Class<? extends C> channelClass) {
        if (channelClass == null) {
            throw new NullPointerException("channelClass");
        }
        //基于channelClass创建一个ReflectiveChannelFactory调用channelFactory
        return channelFactory(new ReflectiveChannelFactory<C>(channelClass));
    }

再步入channelFactory可以看到这个方法也仅仅是将反射工厂作为入参传入channelFactory中,完成channelFactory 这个工厂的初始化工作。

public B channelFactory(io.netty.channel.ChannelFactory<? extends C> channelFactory) {
        return channelFactory((ChannelFactory<C>) channelFactory);
    }

步入channelFactory方法,可以看到channelFactory就是基于我们类类型所创建的工厂,这也印证了我们上文所说的channel反射创建的来源,就是我们配置阶段所设置的NioServerSocketChannel.class

 public B channelFactory(ChannelFactory<? extends C> channelFactory) {
       //略
        this.channelFactory = channelFactory;
        return (B) this;
    }
NioServerSocketChannel创建时做了什么

由此我们可以得出在服务端channel初始化是基于我们的引导类通过反射的方式完成创建的。对此我们看看反射创建NioServerSocketChannel时,它做了些什么。

源码如下,可以看到:

  1. 它基于DEFAULT_SELECTOR_PROVIDER调用了一个newSocket方法,打开服务套接字通道。(补充:SelectorProvidernio下的一个抽象类,可以选择器以及可选择通道的服务提供者。)
  2. 调用构造方法完成channel的基本配置。
private static final SelectorProvider DEFAULT_SELECTOR_PROVIDER = SelectorProvider.provider();


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

对上述两步进行拆解,先来看看newSocket,它通过SelectorProvider 调用openServerSocketChannel完成ServerSocketChannel 的创建。

private static ServerSocketChannel newSocket(SelectorProvider provider) {
        try {
        	//调用JDK nio包下的API完成ServerSocketChannel 实例的创建
            return provider.openServerSocketChannel();
        } catch (IOException e) {
            throw new ChannelException(
                    "Failed to open a server socket.", e);
        }
    }

再来看看得到这个ServerSocketChannel 之后,构造方法做了些什么,可以看到它基于我们创建的ServerSocketChannel 做了如下两件事:

  1. 调用父级构造方法完成channel基本设置并将感兴趣的事件设置为OP_ACCEPT
  2. 调用NioServerSocketChannelConfig完成当前channelRecvByteBufAllocator分配器分配以及基于Java socket基本配置。
public NioServerSocketChannel(ServerSocketChannel channel) {
		//调用父级构造方法完成channel基本设置并将感兴趣的事件设置为OP_ACCEPT
        super(null, channel, SelectionKey.OP_ACCEPT);
        //创建NioServerSocketChannelConfig对象,内部会进行channel
        config = new NioServerSocketChannelConfig(this, javaChannel().socket());
    }

我们先来看看父级构造方法的调用做了什么:

  1. 调用父级构造方法idunsafepipeline 初始化。
  2. 它将我们创建的channel感兴趣的事件设置为SelectionKey.OP_ACCEPT
  3. 将阻塞模式设置为非阻塞。
protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
		//调用父级构造方法id 、unsafe 、pipeline 初始化。
        super(parent);
        this.ch = ch;
        //感兴趣的事件设置为SelectionKey.OP_ACCEPT
        this.readInterestOp = readInterestOp;
        try {
        	//将阻塞模式设置为非阻塞
            ch.configureBlocking(false);
        } catch (IOException e) {
          //略
        }
    }

查看上一步所说的父级构造,印证我们所说的idunsafepipeline 初始化。

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

再来说说第二个步骤,即config类的创建,其构造函数不断步入我们即可看到:

  1. 父级构造方法完成channel的绑定以及对channel进行RecvByteBufAllocator分配。
  2. 设置config类对应的javaSocket
public DefaultServerSocketChannelConfig(ServerSocketChannel channel, ServerSocket javaSocket) {
		//拿着我们反射创建的channel调用父级构造,最终会走到下方的构造方法完成RecvByteBufAllocator分配
        super(channel);
        //设置config类对应的javaSocket
        if (javaSocket == null) {
            throw new NullPointerException("javaSocket");
        }
        this.javaSocket = javaSocket;
    }


 protected DefaultChannelConfig(Channel channel, RecvByteBufAllocator allocator) {
		 //上述方法的父级构造最终会走到这里完成channel的RecvByteBufAllocator的分配
        setRecvByteBufAllocator(allocator, channel.metadata());
        //绑定channel
        this.channel = channel;
    }

初始化服务端channel

上述的流程我们完成了服务端Channel的创建,接下来就是对服务端channel进行初始化工作了。所以我们再次将阅读的重心回到到AbstractBootstrapinitAndRegister方法上,此时我们再次步入init方法一探究竟。

final ChannelFuture initAndRegister() {
        Channel channel = null;
        try {
            channel = channelFactory.newChannel();
            //初始化服端channel 
            init(channel);
        } catch (Throwable t) {
       //略
        }

        //略
        return regFuture;
    }

步入ServerBootstrapinit方法可以看到一段比较长的代码,它会拿着我们创建的channel进行如下工作:

  1. 设置channelOptionsChannelAttrs
  2. childOptionschildAttrs保存起来,后续有新连接来时,就给每一个连接的channel设置这些选项和属性。
  3. 配置服务端pipeline
  4. 添加ServerBootstrapAcceptor连接器,每次有新的连接进来时,就会基于本次设置的childOptionschildAttrs对这些child(就是进来的连接的Channel)进行配置,并注册到ChildGroup(针对child的EventLoopGroup)中。

整体步骤和注释如下代码所示:

	@Override
    void init(Channel channel) throws Exception {
		//设置channelOptions
        final Map<ChannelOption<?>, Object> options = options0();
        synchronized (options) {
            channel.config().setOptions(options);
        }
		
		//设置channelAttrs
        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;
        //保存childOptions中的属性
        synchronized (childOptions) {
            currentChildOptions = childOptions.entrySet().toArray(newOptionArray(childOptions.size()));
        }
		
		//保存childAttrs的配置
        synchronized (childAttrs) {
            currentChildAttrs = childAttrs.entrySet().toArray(newAttrArray(childAttrs.size()));
        }
		//配置服务端pipeline
        p.addLast(new ChannelInitializer<Channel>() {
            @Override
            public void initChannel(Channel ch) throws Exception {
            //ChannelInitializer这个匿名内部类添加到pipeline之后会触发initChannel这个回调,下面这些代码就会将我们配置的ServerHandler添加到pipeline上
                final ChannelPipeline pipeline = ch.pipeline();
                ChannelHandler handler = config.handler();
                if (handler != null) {
                    pipeline.addLast(handler);
                }

              
                ch.eventLoop().execute(new Runnable() {
                    @Override
                    public void run() {
                    //添加ServerBootstrapAcceptor每当有新的连接进来时就会基于currentChildOptions和currentChildAttrs为每一个新连接的channel设置选项和属性,并注册到EventLoopGroup中
                        pipeline.addLast(new ServerBootstrapAcceptor(
                                currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
                    }
                });
            }
        });
    }
服务端以及连接参数设置

我们对init方法的核心流程进行逐个拆解分析,先来说说设置channelOptions这一步,它的工作流程为:

  1. 调用options0获取到对应的channelOptions
  2. 因为setOptions底层存储options键值对时用的是HashMap,在调用setOptions时用到了synchronized 关键字确保选项设置时的线程安全。
	//获取options
 	final Map<ChannelOption<?>, Object> options = options0();
 	//将options键值对存入
        synchronized (options) {
            channel.config().setOptions(options);
        }

那么这个options是哪里来的呢?我们步入options0()方法,可以看到这些属性都来自一个options的变量。

 final Map<ChannelOption<?>, Object> options0() {
        return options;
    }

查看options所有设置的入口,笔者定位到了这个方法,可以看到它在进行必要的校验之后,直接将传入的键值对存入options中。

public <T> B option(ChannelOption<T> option, T value) {
		//option 非空校验
        if (option == null) {
            throw new NullPointerException("option");
        }
        //value 非空校验,若为空则移除option
        if (value == null) {
            synchronized (options) {
                options.remove(option);
            }
        } else {
        	//将options上锁后设置option和value键值对
            synchronized (options) {
                options.put(option, value);
            }
        }
        return (B) this;
    }

查看这个方法调用,我们来到了我们编写的代码,由此可知设置的options都来自我们配置时用到的option方法。

 // option() 方法 用于给服务端Channel设置一些TCP参数 SO_BACKLOG 表示系统用于临时存放已完成三次握手的请求的队列的最大长度
            b.option(ChannelOption.SO_BACKLOG, 1024);

同理,我们紧接着阅读下一段代码,即设置channelAttr,原理和options差不多,而attr的值也是来自于笔者上文给出的配置代码 b.attr(AttributeKey.newInstance("serverName"), "nettyServer");,这里就不多做赘述了。

 //设置channel attr
        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());
            }
        }

紧接着我们就开始设置childOptionschildAttrs,和上述几个选项设置也是同理,需要保证线程安全也上了锁。

//基于引导类上的配置保存childOptions和childAttrs,后续新连接建立时会用得上
 synchronized (childOptions) {
            currentChildOptions = childOptions.entrySet().toArray(newOptionArray(childOptions.size()));
        }
        synchronized (childAttrs) {
            currentChildAttrs = childAttrs.entrySet().toArray(newAttrArray(childAttrs.size()));
        }

依照上述代码的规律我们也可以得出childOptionschildAttrs也是来自于笔者的配置代码:

b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    //childOptions设置处
                    .childOption(ChannelOption.TCP_NODELAY, true)
                    //childAttr设置处
                    .childAttr(AttributeKey.newInstance("childAttr"), "childAttrValue")
                    
                    //略
pipeline配置以及handler添加

完成这些基本选项和配置的设置之后,就需要配置服务端pipeline,配置服务端pipeline大抵是设置这几个过程:

  1. 创建一个匿名内部类ChannelInitializer并注册到pipeline,然后向EventLoop提交一个任务,确保当前ChannelInitializer添加到pipeline之后,回调到下面代码中的initChannel方法。
  2. initChannel方法会获取channelpipeline,注意这个channel就是我们上文初始化好的NioServerSocketChannel,具体后文会分析。
  3. 获取我们配置的handler即我们自行编写的ServerHandler
  4. ServerHandler添加到pipeline中。

对应代码和注释如下:

p.addLast(new ChannelInitializer<Channel>() {
            @Override
            public void initChannel(Channel ch) throws Exception {
            	//获取channel的pipeline
                final ChannelPipeline pipeline = ch.pipeline();
                //获取我们配置的handler即用于为请求服务的ServerHandler
                ChannelHandler handler = config.handler();
                if (handler != null) {
                //将ServerHandler添加到pipeline中
                    pipeline.addLast(handler);
                }

              //略
            }
        });

步入addLast我们来到了DefaultChannelPipelineaddLast即可看到其内部调用了addLast方法,入参为我们的ChannelInitializer匿名内部类。

@Override
    public final ChannelPipeline addLast(ChannelHandler... handlers) {
        return addLast(null, handlers);
    }

继续前进可以看到它会对handlers进行非空校验,完成后则遍历ChannelHandler 并调用addLast将当前ChannelHandler 添加到pipeline的链表中。因为我们的handlers只有一个所以遍历一轮就直接调用addLast

@Override
    public final ChannelPipeline addLast(EventExecutorGroup executor, ChannelHandler... handlers) {
    	//handlers 非空校验
        if (handlers == null) {
            throw new NullPointerException("handlers");
        }
		//将handlers添加到pipeline上
        for (ChannelHandler h: handlers) {
            if (h == null) {
                break;
            }
            addLast(executor, null, h);
        }

        return this;
    }

继续步入addLast方法,我们来说一下大体的逻辑:

  1. 检测这个handler是否是共享的,如果是则判断是否添加过,如果添加过则抛一个异常,反之进入步骤2。
  2. 将这个handler封装成一个AbstractChannelHandlerContext
  3. 如果未注册则基于这个handler的上下文创建一个任务,确保后续添加完成后回调我们ServerHandlerhandlerAdded方法,反之进入步骤4。
  4. 如果已注册且当前任务不在eventLoop中,则基于执行executor 完成handler注册和回调handlerAdded,反之进入步骤5。
  5. 如果在eventLoop中且注册了则直接回调ServerHandlerhandlerAdded方法。
@Override
    public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
        final AbstractChannelHandlerContext newCtx;
        synchronized (this) {
        	//检测这个handler是否是共享的,如果是则判断是否添加过,若添加过抛出异常
            checkMultiplicity(handler);
			//将这个handler封装成一个AbstractChannelHandlerContext 
            newCtx = newContext(group, filterName(name, handler), handler);
			//将AbstractChannelHandlerContext存到pipeline的链表中
            addLast0(newCtx);

            // 如果未注册则基于这个ServerHandler的上下文创建一个任务,确保后续添加完成后回调我们的handlerAdded方法
            if (!registered) {
                newCtx.setAddPending();
                callHandlerCallbackLater(newCtx, true);
                return this;
            }
			//如果已注册且当前任务不在eventLoop中,则基于执行executor 完成handler注册和回调handlerAdded
            EventExecutor executor = newCtx.executor();
            if (!executor.inEventLoop()) {
                newCtx.setAddPending();
                executor.execute(new Runnable() {
                    @Override
                    public void run() {
                        callHandlerAdded0(newCtx);
                    }
                });
                return this;
            }
        }
        //如果在eventLoop中且注册了则直接回调ServerHandler的handlerAdded方法
        callHandlerAdded0(newCtx);
        return this;
    }

先来说说checkMultiplicity方法,逻辑比较简单:

  1. 判断是否是共享且添加过,如果是则抛异常。
  2. 反之将added设置为true,代表当前handler为已添加。
 private static void checkMultiplicity(ChannelHandler handler) {
        if (handler instanceof ChannelHandlerAdapter) {
            ChannelHandlerAdapter h = (ChannelHandlerAdapter) handler;
            //判断是否是共享且添加过,如果是则抛异常。
            if (!h.isSharable() && h.added) {
                throw new ChannelPipelineException(
                        h.getClass().getName() +
                        " is not a @Sharable handler, so can't be added or removed multiple times.");
            }
            //将当前handler的added设置为true,代表状态为已添加
            h.added = true;
        }
    }

完成必要的channel校验之后,我们需要基于这个handler完成:

  1. 创建handler的名称。
  2. 基于handler封装一个上下文AbstractChannelHandlerContext
 newCtx = newContext(group, filterName(name, handler), handler);

步入其构造方法,我们可以看到它对channel进行这些信息的记录:

  1. 记录handler属于哪个pipeline
  2. 执行器executor
  3. 上下文名称name
  4. 是否是入站inBound
  5. 是否是出站outbound
  6. 当前handler

这里面仅设置属性赋值和名称初始化,笔者就不做展开,读者感兴趣可自行调试。

DefaultChannelHandlerContext(
            DefaultChannelPipeline pipeline, EventExecutor executor, String name, ChannelHandler handler) {
        super(pipeline, executor, name, isInbound(handler), isOutbound(handler));
        if (handler == null) {
            throw new NullPointerException("handler");
        }
        this.handler = handler;
    }

完成上下文创建之后,我们需要将当前这个上下文添加到pipeline中。

 addLast0(newCtx);

步入addLast0可以看到该操作会将我们的上下文newCtx追加到tailContext前面,。

 private void addLast0(AbstractChannelHandlerContext newCtx) {
 		//获取tail前驱节点
        AbstractChannelHandlerContext prev = tail.prev;
        //newCtx指向这个前驱节点,并且后继节点指向tail
        newCtx.prev = prev;
        newCtx.next = tail;
        //前驱的后继指针指向newCtx
        prev.next = newCtx;
        //tail的前驱指向newCtx
        tail.prev = newCtx;
    }

经过这一步之后,我们的handler就会成功的添加到pipeline的双向链表上,最终效果如下图所示:

在这里插入图片描述

完成handler的添加之后,进行最后的回调通知工作:

  1. 判断当前handler是否注册过,如果registered返回true,则说明注册过直接调用callHandlerAdded0当前handlerhandlerAdded方法,反之进入步骤2。
  2. 如果未注册调用setAddPending将当前handler的状态由于初始化INIT通过CAS修改为ADD_PENDING,即添加中。
  3. 封装一个异步任务提交到EventLoop中回调handlerAdded方法。

因为我们之前并没有注册过这个handler,所以执行1、2两个步骤就直接返回了。

			//如果未注册,则通过CAS方式修改handler的状态,并封装一个异步任务回调handlerAdded
			if (!registered) {
                newCtx.setAddPending();
                callHandlerCallbackLater(newCtx, true);
                return this;
            }
			//如果已注册则直接回调handlerAdded
            EventExecutor executor = newCtx.executor();
            if (!executor.inEventLoop()) {
                newCtx.setAddPending();
                executor.execute(new Runnable() {
                    @Override
                    public void run() {
                        callHandlerAdded0(newCtx);
                    }
                });
                return this;
            }

所以我们先看看setAddPending方法,它就是一个CAS修改当前newCtx的状态为添加中。

final void setAddPending() {
        boolean updated = HANDLER_STATE_UPDATER.compareAndSet(this, INIT, ADD_PENDING);
        assert updated; // This should always be true as it MUST be called before setAddComplete() or setRemoved().
    }

然后来到了callHandlerCallbackLater,它的步骤比较多了:

  1. 判断added状态,如果为true则将上下文封装成PendingHandlerAddedTask,反之封装成PendingHandlerRemovedTask移除任务,从入参可知我们的addedtrue,所以这里我们会将上下文封装成添加任务。
  2. 判断pendingHandlerCallbackHead是否为空,若为空则将当前添加任务设置为pendingHandlerCallbackHead,反之执行步骤3。
  3. 走到这里说明pendingHandlerCallbackHead不为空,我们需要将添加任务追加至pendingHandlerCallbackHead末尾。
private void callHandlerCallbackLater(AbstractChannelHandlerContext ctx, boolean added) {
        assert !registered;
		//基于AbstractChannelHandlerContext 封装成一个PendingHandlerAddedTask
        PendingHandlerCallback task = added ? new PendingHandlerAddedTask(ctx) : new PendingHandlerRemovedTask(ctx);
        PendingHandlerCallback pending = pendingHandlerCallbackHead;
        //如果pending 为空,则说明是第一个任务,直接将pendingHandlerCallbackHead 设为task
        if (pending == null) {
            pendingHandlerCallbackHead = task;
        } else {
            // 反之追加到pending的后继空位中
            while (pending.next != null) {
                pending = pending.next;
            }
            pending.next = task;
        }
    }

这里我们不妨步入查看一下PendingHandlerAddedTask 这个添加任务做了什么,从重写的方法不难看出它继承了Runnable接口,这也就意味着我们将其挂到pendingHandlerCallbackHead 上之后,EventExecutor就会执行这个任务。

private final class PendingHandlerAddedTask extends PendingHandlerCallback {

        PendingHandlerAddedTask(AbstractChannelHandlerContext ctx) {
            super(ctx);
        }
		//回调handler的handlerAdded方法
        @Override
        public void run() {
            callHandlerAdded0(ctx);
        }

        //略
        }
    }

这里我们将callHandlerAdded0方法内部逻辑贴出,它的逻辑为:

  1. 获取ctxhandler,也就是我们的匿名内部类,并回调handlerAdded方法。
  2. 基于CASadd状态设置为完成。
private void callHandlerAdded0(final AbstractChannelHandlerContext ctx) {
        try {
            ctx.handler().handlerAdded(ctx);
            ctx.setAddComplete();
        } catch (Throwable t) {
           //略
        }
    }

handlerAdded方法,回调用一个initChannel的方法。

 @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        if (ctx.channel().isRegistered()) {
          
            initChannel(ctx);
        }
    }

步入initChannel它的核心逻辑就是基于ctxchannel调用initChannel

@SuppressWarnings("unchecked")
    private boolean initChannel(ChannelHandlerContext ctx) throws Exception {
        if (initMap.putIfAbsent(ctx, Boolean.TRUE) == null) { // Guard against re-entrance.
            try {
                initChannel((C) ctx.channel());
            } catch (Throwable cause) {
               //略
            return true;
        }
        return false;
    }

步入initChannel终于来到了我们匿名内部类的initChannel,由此完成一次闭环。

来小结一下初始化服务端channel的步骤,完成反射创建之后,addLast在将ChannelInitializer添加到pipeline上之后,会向EventExecutort提交一个PendingHandlerAddedTask 任务,该任务会回调传入的handlerhandlerAdded方法,而ChannelInitializerhandlerAdded会调用内部initChannel方法,最终走到我们匿名内部类ChannelInitializer的内部逻辑。

查看initChannel的逻辑:

  1. 它会获取NioSocketChannelpipeline
  2. 获取配置中的handler即我们上文添加的ServerHandler
  3. 再次调用addLast方法,需要注意因为这个handler注册过,且当前执行的就是eventLoop线程,所以它会直接执行addLast内部的callHandlerAdded0方法。
  4. 最后添加ServerBootstrapAcceptor处理连接请求。
p.addLast(new ChannelInitializer<Channel>() {
            @Override
            public void initChannel(Channel ch) throws Exception {
            //获取pipeline
                final ChannelPipeline pipeline = ch.pipeline();
                //拿到配置中的handler即我们的ServerHandler
                ChannelHandler handler = config.handler();
                //添加到pipeline中
                if (handler != null) {
                    pipeline.addLast(handler);
                }

            	//添加连接器ServerBootstrapAcceptor处理连接请求
                ch.eventLoop().execute(new Runnable() {
                    @Override
                    public void run() {
                        pipeline.addLast(new ServerBootstrapAcceptor(
                                currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
                    }
                });
            }
        });

addLast工作职责上文已经说过了,大抵上是添加handler、更新状态、执行回调,所以它最终回调到我们的ServerHandlerhandlerAdded,自此配置服务端的pipeline整体流程讲解完毕。

 @Override
    public void handlerAdded(ChannelHandlerContext ctx) {
        System.out.println("handlerAdded");
    }

然后就是连接器的添加了,代码的入口就在我们提到的ServerBootstrap回调方法的下方,可以看到设计者们对于注册Selector的操作仍然是放到EventLoop中执行,这一点作者也给出了原因:

We add this handler via the EventLoop as the user may have used a ChannelInitializer as handler.
In this case the initChannel(…) method will only be called after this method returns. Because
of this we need to ensure we add our handler in a delayed fashion so all the users handler are
placed in front of the ServerBootstrapAcceptor.

其大意是我们需要通过eventLoop添加此处理程序,因为用户可能使用了ChannelInitializer作为处理程序。在这种情况下,initChannel方法只有在该方法返回后才会被调用。因此,我们需要将连接器添加以eventLoop任务的方式进行提交,实现延迟的方式添加处理程序,以便将所有用户处理程序放在ServerBootstrapAcceptor前面。

对应代码如下可以看到添加ServerBootstrapAcceptor传入的我们上文所配置的各种ChildHandlerChildOptionsChildAttrs等。然后仍然是通过addLast将其封装成上下文添加到pipeline的链表上完成handlerAdded回调。

p.addLast(new ChannelInitializer<Channel>() {
            @Override
            public void initChannel(Channel ch) throws Exception {
               //略

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

注册channel

接下来就是对channel的注册了,我们的代码再次回到AbstractBootstrapinitAndRegister上,我们在之前解读中已经完成了服务端的创建newChannel和初始化init,所以这里我们重点阅读register(channel)的调用。

final ChannelFuture initAndRegister() {
       	//略       
		//注册selector
        ChannelFuture regFuture = config().group().register(channel);
       //略
        return regFuture;
    }

我们步入register可以看到它执行以下两步:

  1. 调用next得到一个EventLoop
  2. 调用register完成注册。
@Override
    public ChannelFuture register(Channel channel) {
        return next().register(channel);
    }

我们直接步入register方法,该方法无论以何种方式都会是对register0的调用,整体逻辑为:

  1. 判断当前线程是否是eventLoop如果是直接调用register0,反之执行步骤2。
  2. register0封装成一个任务并提交到eventLoop中。

因为我们的线程不是eventLoop所以需要走第二个分支。

 @Override
        public final void register(EventLoop eventLoop, final ChannelPromise promise) {
           //略

            AbstractChannel.this.eventLoop = eventLoop;
			//如果在eventLoop中直接调用register0
            if (eventLoop.inEventLoop()) {
                register0(promise);
            } else {
                try {
                	//若不在eventLoop则封装成一个线程任务提交到eventLoop中执行
                    eventLoop.execute(new Runnable() {
                        @Override
                        public void run() {
                            register0(promise);
                        }
                    });
                } catch (Throwable t) {
                   //略
                }
            }
        }

我们继续查看register0,代码很长,整体的步骤为:

  1. 调用doRegister完成注册。
  2. 如果是第一次注册,则通过pipeline传播机制,触发handlerAdded回调。
  3. 通过pipeline传播机制,触发channelRegistered回调。
  4. 如果已连接且是第一次注册则回调channelActive方法。

这里因为我们连接还未建立,所以代码只会执行步骤1、2、3。

private void register0(ChannelPromise promise) {
            try {
               //略
				//调用doRegister完成实际注册逻辑
                doRegister();
                neverRegistered = false;
                registered = true;

               //如果是第一次注册则调用pipeline上处理器的handlerAdded方法
                pipeline.invokeHandlerAddedIfNeeded();

                safeSetSuccess(promise);
                //调用pipeline上处理器的channelRegistered方法
                pipeline.fireChannelRegistered();
              //如果连接激活且是第一次注册则回调channelActive方法
                if (isActive()) {
                    if (firstRegistration) {
                        pipeline.fireChannelActive();
                    } else if (config().isAutoRead()) {
                      //略
                    }
                }
            } catch (Throwable t) {
              //略
            }
        }
doRegister完成注册

我们先来看看doRegister方法内部逻辑,可以看到它调用javaChannel方法获取到JDKServerSocketChannelImpl并调用register完成注册,并返回SelectionKey

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

register方法逻辑比较简单:

  1. regLock锁,确保操作线程安全。
  2. 判断是否当前channel是否open,如果不是则抛异常,反之进入步骤3。
  3. 通过位运算判断ops是否是0,如果不是0则抛出异常,反之进入步骤4。
  4. 判断是否阻塞,如果阻塞则抛出异常,反之进入步骤5。
  5. 通过Selector 得到SelectionKey
  6. 判断key是否为空,若不为空,则将ops绑定到SelectionKey 上,注意这里的ops为0,代表不关心任何事件,仅仅完成注册操作。
  7. 并将att添加到SelectionKey 上确保收到这个事件会及时通知att,需要说明一下这里的att即我们的NioSocketChannel,反之进入步骤7。
  8. 如果为空,则调用nio包下的register方法,创建一个SelectionKey并将SelectionKey感兴趣的值设置为0,并将att绑定到SelectionKey上,后续如果有读写事件则会及时通知我们的NioSocketChannel进行处理。
  9. 将生成的SelectionKey保存到SelectionKey数组上。
public final SelectionKey register(Selector sel, int ops,
                                       Object att)
        throws ClosedChannelException
    {
    	//上锁
        synchronized (regLock) {
        	//如果连接为打开则直接抛异常
            if (!isOpen())
                throw new ClosedChannelException();
             //如果是无效ops则抛异常
            if ((ops & ~validOps()) != 0)
                throw new IllegalArgumentException();
            //如果阻塞则抛阻塞异常    
            if (blocking)
                throw new IllegalBlockingModeException();
             //拿到当前的SelectionKey 
            SelectionKey k = findKey(sel);
            if (k != null) {
            	//设置感兴趣的事件,以及收到事件后要通知的对象
                k.interestOps(ops);
                k.attach(att);
            }
            //如果key为空则上锁创建一个,并基于nio包下的SelectorImpl完成SelectionKey创建和事件设置和通知对象设置,
            if (k == null) {
                // New registration
                synchronized (keyLock) {
                    if (!isOpen())
                        throw new ClosedChannelException();
                    k = ((AbstractSelector)sel).register(this, ops, att);
                    //将key设置到SelectionKey数组上
                    addKey(k);
                }
            }
            return k;
        }
    }
invokeHandlerAddedIfNeeded进行handlerAdded回调

完成register操作之后,invokeHandlerAddedIfNeeded,它会判断是否是第一次注册,如果是则会调用callHandlerAddedForAllHandlers回调pipelinehandlerhandlerAdded方法。

final void invokeHandlerAddedIfNeeded() {
        assert channel.eventLoop().inEventLoop();
        if (firstRegistration) {
            firstRegistration = false;
           //如果是第一次注册则调用callHandlerAddedForAllHandlers
            callHandlerAddedForAllHandlers();
        }
    }

还记得我们上文中提到的pendingHandlerCallbackHead,即我们添加handler时封装的一个任务,它会调用任务对应的handerhandlerAdded方法。我们步入callHandlerAddedForAllHandlers就会看到pendingHandlerCallbackHead

private void callHandlerAddedForAllHandlers() {
        final PendingHandlerCallback pendingHandlerCallbackHead;
        synchronized (this) {
          	 //略
          	 
			//拿到pendingHandlerCallbackHead ,并将this的pendingHandlerCallbackHead 置空辅助gc
            pendingHandlerCallbackHead = this.pendingHandlerCallbackHead;
            this.pendingHandlerCallbackHead = null;
        }

       //从pendingHandlerCallbackHead开始遍历,逐个调用每个task 中的handler的handlerAdded
        PendingHandlerCallback task = pendingHandlerCallbackHead;
        while (task != null) {
            task.execute();
            task = task.next;
        }
    }
基于fireChannelRegistered回调channelRegistered

最后就是fireChannelRegistered,我们步入fireChannelRegistered即可看到invokeChannelRegistered这个方法的调用。
可以看到入参为head,即从HeadContext作为入参开始invokeChannelRegistered这个方法的调用。

@Override
    public final ChannelPipeline fireChannelRegistered() {
        AbstractChannelHandlerContext.invokeChannelRegistered(head);
        return this;
    }

步入invokeChannelRegistered方法,可以看到它会调用一个channelRegistered方法,其核心步骤很简单:

  1. 通过handler方法获取head上下文对应的handler
  2. 调用channelRegistered方法。
  private void invokeChannelRegistered() {
		//判断handler状态是否被调用
        if (invokeHandler()) {
            try {
            	//通过handler得到上下文中的handler调用channelRegistered
                ((ChannelInboundHandler) handler()).channelRegistered(this);
            } catch (Throwable t) {
                notifyHandlerException(t);
            }
        } else {
            fireChannelRegistered();
        }
    }

继续步进channelRegistered,它会进行如下步骤:

  1. 会判断是否是第一次注册,如果是则callHandlerAddedForAllHandlers,反之进入步骤2。
  2. 通过上下文调用fireChannelRegistered
@Override
        public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
            invokeHandlerAddedIfNeeded();
            ctx.fireChannelRegistered();
        }

来到fireChannelRegistered可以看到它的核心即通过findContextInbound遍历找到下一个inbound的handler并调用了invokeChannelRegistered方法。

@Override
    public ChannelHandlerContext fireChannelRegistered() {
        invokeChannelRegistered(findContextInbound());
        return this;
    }

步入invokeChannelRegistered可以看到,因为当前工作线程在eventloop上,所以直接调用invokeChannelRegistered

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

于是代码来到了invokeChannelRegistered,最终调用handler获取到我们绑定的ServerHandler,回调channelRegistered方法。

 private void invokeChannelRegistered() {
        if (invokeHandler()) {
            try {
                ((ChannelInboundHandler) handler()).channelRegistered(this);
            } catch (Throwable t) {
                notifyHandlerException(t);
            }
        } else {
            fireChannelRegistered();
        }
    }

自此来到我们的channelRegistered由此完成一次闭环。

 @Override
    public void channelRegistered(ChannelHandlerContext ctx) {
        System.out.println("channelRegistered");
    }

来小结一下注册selector的整体步骤:

  1. 调用doRegister完成事件注册和以及事件通知对象NioSocketChannel设置。
  2. 如果是第一次注册则回调pipeline上的handlerAdded方法。
  3. 基于pipelineHeadContext开始进行对handlerchannelRegistered方法回调。

完成端口绑定

最后一步就是完成端口绑定,代码再次回到AbstractBootstrap上,我们完成initAndRegister方法之后就来到了doBind0方法

private ChannelFuture doBind(final SocketAddress localAddress) {
        final ChannelFuture regFuture = initAndRegister();
        final Channel channel = regFuture.channel();
        if (regFuture.cause() != null) {
            return regFuture;
        }
		
		//完成channel创建和初始化之后,这个regFuture会返回true
        if (regFuture.isDone()) {
           //调用doBind进行端口绑定
            ChannelPromise promise = channel.newPromise();
            doBind0(regFuture, channel, localAddress, promise);
            return promise;
        } else {
           //略
        }
    }

步入doBind0方法,它会提交一个异步任务,任务即调用NioSocketChannel完成地址和端口绑定。

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

步入bind方法可以看到它仅仅是调用pipelinebind方法。

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

我们再次步进,代码来到了DefaultChannelPipelinebind方法,可以看到它通过tail调用bind方法。

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

最终结果不断步进终于来到了NioServerSocketChanneldoBind内部仅仅获取channel后调用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());
        }
    }

于是来到tailbind方法,它会执行下面这段逻辑:

  1. 基于findContextOutboundtail开始自后向前找到第一个outboundhandler的上下文。
  2. 拿到这个handlerexecutor
  3. 如果这个executoreventLoop中则直接调用invokeBind
  4. 反之提交到eventLoop中进行无锁串行化执行。
@Override
    public ChannelFuture bind(final SocketAddress localAddress, final ChannelPromise promise) {
       
		//从tail开始自后向前拿到第一个handler的AbstractChannelHandlerContext 
        final AbstractChannelHandlerContext next = findContextOutbound();
        //拿到这个AbstractChannelHandlerContext 的executor
        EventExecutor executor = next.executor();
        //如果executor在eventLoop中直接调用invokeBind,反之提交到executor中异步执行
        if (executor.inEventLoop()) {
            next.invokeBind(localAddress, promise);
        } else {
            safeExecute(executor, new Runnable() {
                @Override
                public void run() {
                    next.invokeBind(localAddress, promise);
                }
            }, promise, null);
        }
        return promise;
    }

于是拿着我们找到的AbstractChannelHandlerContext,找到对应的handler,调用bind方法。

private void invokeBind(SocketAddress localAddress, ChannelPromise promise) {
        if (invokeHandler()) {
            try {
            	//通过上下文找到handler并调用bind方法
                ((ChannelOutboundHandler) handler()).bind(this, localAddress, promise);
            } catch (Throwable t) {
                notifyOutboundHandlerException(t, promise);
            }
        } else {
            bind(localAddress, promise);
        }
    }

查看bind方法会发现,它会调用unsafe类的bind方法

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

步入unsafebind,我们可以看到两个核心的逻辑:

  1. 基于JDK原生API获取ServerSocketChannelImpl完成端口绑定。
  2. 如果之前channel未绑定(wasActivefalse)且本次绑定成功(调用isActice方法返回true),则提交一个调用pipeline上所有handlerchannelActive的回调
@Override
        public final void bind(final SocketAddress localAddress, final ChannelPromise promise) {
           //略

            boolean wasActive = isActive();
            try {
            	//基于jdk原生API获取ServerSocketChannelImpl完成端口绑定
                doBind(localAddress);
            } catch (Throwable t) {
                safeSetFailure(promise, t);
                closeIfClosed();
                return;
            }
			//如果之前未绑定且本次绑定完成则提交一个调用pipeline上所有handler的channelActive的回调
            if (!wasActive && isActive()) {
                invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        pipeline.fireChannelActive();
                    }
                });
            }

            safeSetSuccess(promise);
        }

查看 pipeline.fireChannelActive();具体执行可以看到它最终会来到HeadContexthandler中的channelActive方法其内部逻辑为:

  1. 基于pipeline完成channelActive传播。
  2. 调用readIfIsAutoReadSelectionKey的兴趣集上增加OP_ACCEPT,处理后续新的连接接入。
 @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            ctx.fireChannelActive();

            readIfIsAutoRead();
        }

fireChannelActive的传播和上述各种事件传播类似,不多赘述,这里我们查看readIfIsAutoRead内部逻辑,它会判断是否是autoRead如果是则直接调用read方法。

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

read方法内部也是对pipelineread的调用。

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

可以看到pipeline对于read的调用则是直接调用tailread

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

于是通过findContextOutbound还是找到HeadContext,并拿到其executor 直接调用invokeRead

@Override
    public ChannelHandlerContext read() {
    //拿到headContext获取其executor ,完成invokeRead调用
        final AbstractChannelHandlerContext next = findContextOutbound();
        EventExecutor executor = next.executor();
        if (executor.inEventLoop()) {
            next.invokeRead();
        } else {
            //略
        }

        return this;
    }

invokeRead内部则是直接拿着HeadContext中的handler调用read

private void invokeRead() {
        if (invokeHandler()) {
            try {
                ((ChannelOutboundHandler) handler()).read(this);
            } catch (Throwable t) {
                notifyHandlerException(t);
            }
        } else {
            read();
        }
    }

于是走到了unsafe的beginRead方法。

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

其内部也仅仅是调用doBeginRead

@Override
        public final void beginRead() {
            assertEventLoop();

            if (!isActive()) {
                return;
            }

            try {
                doBeginRead();
            } catch (final Exception e) {
                invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        pipeline.fireExceptionCaught(e);
                    }
                });
                close(voidPromise());
            }
        }

不断步入后,最终来到doBeginRead,可以看到该方法内部逻辑为:

  1. 校验SelectionKey 有效性。
  2. 获取selectionKey判断selectionKey感兴趣的事件是否为0,即不关心任何事件,如果是则基于异或运算符添加readInterestOp(readInterestOp即我们上文channel创建的时候,设置值为 SelectionKey.OP_ACCEPT)。
@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);
        }
    }

自此我们将端口会绑定的源码也讲解完成了,小结一下端口绑定的流程:

  1. 基于JDKAPI完成channel端口绑定。
  2. 基于pipeline传播channelActive事件。
  3. selectionKey上设置SelectionKey.OP_ACCEPT,使之可以处理后续各种新连接。

小结

自此我们将Netty服务端启动的源码讲解完成了,下面我们来小结一下Netty服务端启动的整体步骤:

  1. 基于反射创建channel
  2. 基于引导类的配置信息完成NioServerSocketChannel的选项和属性绑定,以及child的选项和属性记录。
  3. 将引导类配置的handler添加到pipeline上。
  4. 添加连接器ServerBootstrapAcceptor,用于处理后续的连接请求。
  5. 注册channel,完成后基于pipeline完成handlerAddchannelRegister方法回调。
  6. 完成端口绑定,绑定完成之后触发channelActive方法回调并在selectionKey上设置SelectionKey.OP_ACCEPT,使之可以处理后续各种新连接。

常见面试题

请解释一下Netty服务端启动的主要步骤是什么?

整体来说是以下4个步骤:

  1. 基于反射创建channel
  2. 完成channel初始化即选项和属性设置以及引导类配置的handler添加。
  3. 注册channel,将selector注册到Java NIO Channel上。
  4. 完成端口绑定,绑定完成之后触发channelActive方法回调并在selectionKey上设置SelectionKey.OP_ACCEPT,使之可以处理后续各种新连接。

Netty服务端启动的过程中,主要有哪些重要的组件和概念?

Netty启动的过程中,会涉及下面这些组件:

  1. ServerBootstrap:用于配置和启动Netty服务端的引导类。
  2. EventLoopGroupEventLoopGroup是由一组EventLoop构成,用于处理客户端连接的I/O操作。
  3. Channel:可以理解为一个网络连接,可以进行数据的读写操作。
  4. ChannelHandler:实际处理请求和响应的业务逻辑。
  5. ChannelPipeline:管理ChannelHandler的容器,负责处理入站和出站的事件和操作。

ChannelPipeline和ChannelHandler它们在Netty服务端启动中的作用是什么?

ChannelPipeline是一个拦截和处理事件的管道,由一些列ChannelHandler组成,可以理解为构建请求和响应处理的流水线,将ChannelHandler添加到ChannelPipeline上,即可实现对特定事件的编码、解码、业务逻辑处理、数据读写等操作。

参考文献

Netty服务端创建源码流程解析:https://juejin.cn/post/7050739087724003358#heading-24

netty源码解析01-netty服务端启动过程:https://www.modb.pro/db/175020

Netty源码------Channel的创建到底干了什么?:https://blog.csdn.net/qqq3117004957/article/details/106440866

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

shark-chili

您的鼓励将是我创作的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值