Netty8:服务器启动源码分析

本文详细解析了Netty服务端启动的过程,包括创建ServerBootstrap实例、配置Reactor线程池、绑定服务端Channel、初始化ChannelPipeline等关键步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、服务端启动时序图

在这里插入图片描述

  1. 创建ServerBootStrap实例。

ServerBootStrap是Netty服务端的启动辅助类,它提供了一系列的方法用于设置服务器启动相关的参数。
ServerBootStrap只有一个无参的构造函数,是因为他的参数太多了。

  1. 设置并绑定Reactor线程池。

Netty的Reactor线程池就是EventLoopGroup,是EventLoop的数组。处理所有注册到本线程Selector上的Channel,Selector的轮询操作由绑定的EventLoop线程run方法驱动,在一个循环体内循环执行;除了处理网络IO事件,EventLoop也负责处理用户自定义的Task和定时Task。
从调度层面看,EventLoop也不会再启动其它类型的线程用于异步执行另外的任务,这样就避免了多线程并发操作和锁竞争,提升了IO线程的处理和调度性能;

  1. 设置并绑定服务端Channel。

  Netty对原生的NIO类库进行了封装,对应实现是NioServerSocketChannel。对于用户而言,不需要关心服务器Channel的底层实现细节和工作原理,只需要指定具体使用哪种服务端Channel即可。
  Netty的ServerBootStrap方法提供了channel方法用于指定服务端Channel的类型。
  Netty通过工厂类,利用反射创建NioServerSocketChannel对象。

  1. 链路创建时并初始化ChannelPipeline。

本质是一个负责处理网络事件的职责链,负责管理执行ChannelHandler。网络事件以事件流的形式在ChannelPipeline中流转,由ChannelPipeline根据ChannelHandler的执行策略调度ChannelHandler执行。

  1. 初始化ChannelPipeline之后,添加并设置ChannelHandler。

用户可以完成大多数的功能定制,例如信息编解码、心跳、安全认证、TSL/SSL认证、流量控制和流量整形。

  1. 绑定并启动监听端口。
  2. Selector轮询。

由Reactor线程NioEventLoop负责调度执行Selector轮询操作,选择就绪的Channel集合。

  1. 当轮询到准备就绪的Channel后,由Reactor线程NioEventLoop执行ChannelPipeline的相应方法,最终调度并执行ChannelHandler。
  2. 执行Netty系统ChannelHandler和用户添加定制的ChannelHandler;

2、Netty服务端启动源码

2.1 创建EventLoopGroup过程:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

protected MultithreadEventExecutorGroup(int nThreads, Executor executor, EventExecutorChooserFactory chooserFactory, Object... args) {
        this.terminatedChildren = new AtomicInteger();
        this.terminationFuture = new DefaultPromise(GlobalEventExecutor.INSTANCE);
        if (nThreads <= 0) {
            throw new IllegalArgumentException(String.format("nThreads: %d (expected: > 0)", nThreads));
        } else {
            if (executor == null) {
                executor = new ThreadPerTaskExecutor(this.newDefaultThreadFactory());
            }

            this.children = new EventExecutor[nThreads];

            int j;
            for(int i = 0; i < nThreads; ++i) {
                boolean success = false;
                boolean var18 = false;

                try {
                    var18 = true;
                    this.children[i] = this.newChild((Executor)executor, args);
                    success = true;
                    var18 = false;
                } catch (Exception var19) {
                    throw new IllegalStateException("failed to create a child event loop", var19);
                } finally {
                    if (var18) {
                        if (!success) {
                            int j;
                            for(j = 0; j < i; ++j) {
                                this.children[j].shutdownGracefully();
                            }

                            for(j = 0; j < i; ++j) {
                                EventExecutor e = this.children[j];

                                try {
                                    while(!e.isTerminated()) {
                                        e.awaitTermination(2147483647L, TimeUnit.SECONDS);
                                    }
                                } catch (InterruptedException var20) {
                                    Thread.currentThread().interrupt();
                                    break;
                                }
                            }
                        }

                    }
                }

                if (!success) {
                    for(j = 0; j < i; ++j) {
                        this.children[j].shutdownGracefully();
                    }

                    for(j = 0; j < i; ++j) {
                        EventExecutor e = this.children[j];

                        try {
                            while(!e.isTerminated()) {
                                e.awaitTermination(2147483647L, TimeUnit.SECONDS);
                            }
                        } catch (InterruptedException var22) {
                            Thread.currentThread().interrupt();
                            break;
                        }
                    }
                }
            }

            this.chooser = chooserFactory.newChooser(this.children);
            FutureListener<Object> terminationListener = new FutureListener<Object>() {
                public void operationComplete(Future<Object> future) throws Exception {
                    if (MultithreadEventExecutorGroup.this.terminatedChildren.incrementAndGet() == MultithreadEventExecutorGroup.this.children.length) {
                        MultithreadEventExecutorGroup.this.terminationFuture.setSuccess((Object)null);
                    }

                }
            };
            EventExecutor[] var24 = this.children;
            j = var24.length;

            for(int var26 = 0; var26 < j; ++var26) {
                EventExecutor e = var24[var26];
                e.terminationFuture().addListener(terminationListener);
            }

            Set<EventExecutor> childrenSet = new LinkedHashSet(this.children.length);
            Collections.addAll(childrenSet, this.children);
            this.readonlyChildren = Collections.unmodifiableSet(childrenSet);
        }
    }

最终会跟踪到上述函数:

  • 如果executor是null,创建一个默认的ThreadPerTaskExecutor,使用Netty默认的线程工厂;
  • 根据传入的线程数创建一个线程池数组;
  • 循环填充数组中的元素。
  • 创建一个线程选择器,默认是对2取余(位运算);
  • 为每一个线程池添加一个关闭监听器;
  • 将所有经过上述处理的线程池添加到一个HashSet;

2.2 ServerBootStrap创建和构造过程

在这里插入图片描述
是个空构造,但是有默认的变量;
group方法将boss和worker传入
bootstrap.group(bossGroup,workerGroup)

在这里插入图片描述

线程组和线程类型设置完成后,需要设置服务端Channel用于监听端口和客户端链路接入;

channel方法channel(NioServerSocketChannel.class)
对于服务端,需要创建NioServerSocketChannel。因此通过指定channel的类型创建channel工厂。
在这里插入图片描述
option方法option(ChannelOption.SO_BACKLOG,128)

指定NioServerChannel后,需要设置TCP的一些参数,服务端主要是设置TCP的backlog参数。

backlog指定了内核为此套接口排队的最大连接个数,对于给定的监听套接字,内核维护两个队列:未连接队列和已连接队列,根据TCP三次握手分隔两个队列。

  • 服务器处于listen状态时,收到客户端syn(connect)时在未完成队列中创建一个新的条目;
  • 服务器syn响应客户端;
  • 客户端对服务器syn的ack。
  • 如果三次握手完成,上述条目将从未完成队列搬到已完成连接队列尾部。

当进程调用accept时将从已完成队列的头部取出一个条目给进程,当已完成队列为空时进程将睡眠,直到已经完队列中有条目才会唤醒;

backlog被规定为两个队列总和的最大值,大多数实现默认值是5。在高并发web服务器需要设置的更大:客户端syn和客户端ack的延时导致未完成队列长度增大;

handler方法和childHandler方法

childHandler()来自于启动辅助类ServerBootstrap,是NioServerSocketChannel对应的ChannelPipeline的Handler;
handler()来自于其父类AbstractBootstrap;是客户端新接入的连接SocketChannel对应的ChannelPipeline的Handler。

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

3、bind方法

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • initAndRegister 初始化 NioServerSocketChannel 通道并注册各个 handler,返回一个 future。
  • 执行 doBind0 方法,完成对端口的绑定。

3.1 initAndRegister 方法

final ChannelFuture initAndRegister() {
        Channel channel = null;

        try {
            channel = this.channelFactory.newChannel();
            this.init(channel);
        } catch (Throwable var3) {
            if (channel != null) {
                channel.unsafe().closeForcibly();
                return (new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE)).setFailure(var3);
            }

            return (new DefaultChannelPromise(new FailedChannel(), GlobalEventExecutor.INSTANCE)).setFailure(var3);
        }

        ChannelFuture regFuture = this.config().group().register(channel);
        if (regFuture.cause() != null) {
            if (channel.isRegistered()) {
                channel.close();
            } else {
                channel.unsafe().closeForcibly();
            }
        }

        return regFuture;
    }
  • 通过通道工厂反射创建一个NioServerSocketChannel;
  • init初始化这个NioServerSocketChannel;
  • config().group().register(channel) 通过 ServerBootstrap 的 bossGroup 注册 NioServerSocketChannel。
  • 最后,返回这个异步执行的占位符。

channelFactory.newChannel() 方法
init方法

void init(Channel channel) throws Exception {
        Map<ChannelOption<?>, Object> options = this.options0();
        synchronized(options) {
            setChannelOptions(channel, options, logger);
        }

        Map<AttributeKey<?>, Object> attrs = this.attrs0();
        synchronized(attrs) {
            Iterator var5 = attrs.entrySet().iterator();

            while(true) {
                if (!var5.hasNext()) {
                    break;
                }

                Entry<AttributeKey<?>, Object> e = (Entry)var5.next();
                AttributeKey<Object> key = (AttributeKey)e.getKey();
                channel.attr(key).set(e.getValue());
            }
        }

        ChannelPipeline p = channel.pipeline();
        final EventLoopGroup currentChildGroup = this.childGroup;
        final ChannelHandler currentChildHandler = this.childHandler;
        final Entry[] currentChildOptions;
        synchronized(this.childOptions) {
            currentChildOptions = (Entry[])this.childOptions.entrySet().toArray(newOptionArray(this.childOptions.size()));
        }

        final Entry[] currentChildAttrs;
        synchronized(this.childAttrs) {
            currentChildAttrs = (Entry[])this.childAttrs.entrySet().toArray(newAttrArray(this.childAttrs.size()));
        }

        p.addLast(new ChannelHandler[]{new ChannelInitializer<Channel>() {
            public void initChannel(final Channel ch) throws Exception {
                final ChannelPipeline pipeline = ch.pipeline();
                ChannelHandler handler = ServerBootstrap.this.config.handler();
                if (handler != null) {
                    pipeline.addLast(new ChannelHandler[]{handler});
                }

                ch.eventLoop().execute(new Runnable() {
                    public void run() {
                        pipeline.addLast(new ChannelHandler[]{new ServerBootstrap.ServerBootstrapAcceptor(ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs)});
                    }
                });
            }
        }});
    }
  • 设置 NioServerSocketChannel 的 TCP 属性。
  • 由于 LinkedHashMap 是非线程安全的,使用同步进行处理。
  • 对 NioServerSocketChannel 的 ChannelPipeline 添加 ChannelInitializer 处理器。
  • 这里调用了他的 addLast 方法,也就是将整个 handler 插入到 tail 的前面,因为 tail 永远会在后面,需要做一些系统的固定工作。

在这里插入图片描述
在这里插入图片描述

public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
        final AbstractChannelHandlerContext newCtx;
        synchronized(this) {
            checkMultiplicity(handler);
            newCtx = this.newContext(group, this.filterName(name, handler), handler);
            this.addLast0(newCtx);
            if (!this.registered) {
                newCtx.setAddPending();
                this.callHandlerCallbackLater(newCtx, true);
                return this;
            }

            EventExecutor executor = newCtx.executor();
            if (!executor.inEventLoop()) {
                newCtx.setAddPending();
                executor.execute(new Runnable() {
                    public void run() {
                        DefaultChannelPipeline.this.callHandlerAdded0(newCtx);
                    }
                });
                return this;
            }
        }

        this.callHandlerAdded0(newCtx);
        return this;
    }
  • 检查该 handler 是否符合标准,如果没有 Sharable 注解且已经被使用过了,就抛出异常;
  • 创建一个 AbstractChannelHandlerContext 对象;
  • 然后将 Context 添加到链表中。也就是追加到 tail 节点的前面。
  • 最后,同步或者异步或者晚点异步的调用 callHandlerAdded0 方法,在该方法中,调用之前的 handler 的 handlerAdded 方法,而该方法内部调用了之前的 ChannelInitializer 匿名类的 initChannel 方法;

pipeline 的 addLast 方法,实际上创建一个 Context 对象包装了 pipeline 和 handler,然后通过同步或者异步的方式,间接执行 handler 的 自定义方法-------initChannel 方法。而这个 context 也加入到了 pipeline 的链表节点中。

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

3.2 doBind0()

private static void doBind0(final ChannelFuture regFuture, final Channel channel, final SocketAddress localAddress, final ChannelPromise promise) {
        channel.eventLoop().execute(new Runnable() {
            public void run() {
                if (regFuture.isSuccess()) {
                    channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
                } else {
                    promise.setFailure(regFuture.cause());
                }

            }
        });
    }

在这里插入图片描述
在这里插入图片描述

来到了 NioServerSocketChannel 的 pipeline 的 tail 节点的 bind 方法,该方法首先找到出站节点,然后执行出站节点的 invokeBind 方法。

在这里插入图片描述

private void invokeBind(SocketAddress localAddress, ChannelPromise promise) {
        if (this.invokeHandler()) {
            try {
                ((ChannelOutboundHandler)this.handler()).bind(this, localAddress, promise);
            } catch (Throwable var4) {
                notifyOutboundHandlerException(var4, promise);
            }
        } else {
            this.bind(localAddress, promise);
        }

    }

4、总结

  • 首先创建2个 EventLoopGroup 线程池数组。数组默认大小CPU*2,方便chooser选择线程池时提高性能。
  • BootStrap 将 boss 设置为 group属性,将 worker 设置为 childer 属性。
  • 通过 bind 方法启动,内部重要方法为 initAndRegister 和 dobind 方法。
  • initAndRegister 方法会反射创建 NioServerSocketChannel 及其相关的 NIO 的对象, pipeline , unsafe,同时也为 pipeline 初始了 head 节点和 tail 节点。同时也含有 NioServerSocketChannelConfig 对象。然后向 pipeline 添加自定义的处理器和 ServerBootstrapAcceptor 处理器。这个处理器用于分配接受的 请求给 worker 线程池。每次添加处理器都会创建一个相对应的 Context 作为 pipeline 的节点并包装 handler 对象。注册过程中会调用 NioServerSocketChannel 的 doRegister 方法注册读事件。
  • 在register0 方法成功以后调用在 dobind 方法中调用 doBind0 方法,该方法会 调用 NioServerSocketChannel 的 doBind 方法对 JDK 的 channel 和端口进行绑定,之后在调用 pipeline 的fireChannelActive 最后会调用 NioServerSocketChannel 的 doBeginRead 方法,将感兴趣的事件设置为Accept,完成 Netty 服务器的所有启动,并开始监听连接事件。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值