【图解IO与Netty系列】Netty源码解析——服务端启动

Netty源码解析——服务端启动

  • Netty案例复习
  • Netty原理复习
  • Netty服务端启动源码解析
    • bind(int)
    • initAndRegister()
    • channelFactory.newChannel()
    • init(channel)
    • config().group().register(channel)
    • startThread()
    • run()
    • register0(ChannelPromise promise)
    • doBind0(...)

今天我们一起来学习Netty源码,对Netty有一个深入的认知,既能掌握其使用和原理,又能对它底层的设计有一个大概的认知

Netty案例复习

在阅读源码前,我们再看一下Netty服务端的启动代码

    public static void main(String[] args) throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG, 100)
                    .handler(new LoggingHandler(LogLevel.INFO))
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline p = ch.pipeline();
                            p.addLast(new EchoServerHandler());
                        }
                    });
            ChannelFuture f = b.bind(PORT).sync();
            f.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
  1. 我们创建了两个EventLoopGroup已经,然后设置到ServerBootstrap上,bossGroup中的EventLoop处理accept事件,workerGroup中的EventLoop处理read事件时。
  2. 再通过ServerBootstrap设置好一些其他的参数,其中包括Netty服务端接收连接请求用的NioServerSocketChannel,NioServerSocketChannel会注册到bossGroup上,监听accept事件
  3. 然后设置好ChannelInitializer,服务端接收到客户端连接之后,会创建一个NioSocketChannel,ChannelInitializer就是用于初始化连接建立成功后的NioSocketChannel
  4. 最后就是通过ServerBootstrap的bind(int inetPort)绑定一个端口

在这里插入图片描述

Netty原理复习

我们再来复习一下Netty的原理,带着对Netty原理的认知去看源码,效率才会高,不至于走偏。

在这里插入图片描述

  1. 当调用了ServerBootstrap的bind(int inetPort)绑定端口后,我们注册的NioServerSocketChannel就会被注册到bossGroup上的唯一一个NioEventLoop上,然后NioEventLoop会把ServerSocketChannel注册到Selector上监听accept事件,并启动事件循环
  2. 当有客户端连接后,boosGroup中的EventLoop会获得一个SocketChannel然后将其包装成NioSocketChannel,然后NioServerSocketChannel对应的ChannelHandler会把它注册到workerGroup中
  3. workerGroup会将NioSocketChannel注册到其中一个NioEventLoop上,并使用我们配置的ChannelInitializer初始化NioSocketChannel
  4. workerGroup中的NioEventLoop的事件循环中监听到Channel有read事件时发生,就会调用Channel对应的ChannelPipeline进行处理
  5. ChannelPipeline通过责任链模式,逐一调用pipeline上的ChannelHandler处理事件

Netty服务端启动源码解析

接下来我们就开始读源码了,我们从ServerBootstrap的bind方法走起。

bind(int)

    /**
     * Create a new {@link Channel} and bind it.
     */
    public ChannelFuture bind(int inetPort) {
        return bind(new InetSocketAddress(inetPort));
    }

bind方法的作用就是创建一个Channel并绑定端口,创建的Channel类型就是我们指定的NioServerSocketChannel。

bind方法会调用doBind方法。

    private ChannelFuture doBind(final SocketAddress localAddress) {
        final ChannelFuture regFuture = initAndRegister();
        ...
        doBind0(regFuture, channel, localAddress, promise);
        ...
    }

省略了非关键代码后,剩下的就是关键的两行:

  1. initAndRegister()方法做的事情就是创建并初始化Channel然后注册到NioEventLoop上。
  2. doBind0(…)方法给NioServerSocockChannel中的ServerSocketChannel绑定端口。

在这里插入图片描述

initAndRegister()

我们进入initAndRegister()方法,同样是只看核心代码,其他的代码不关心。

    final ChannelFuture initAndRegister() {
		...
        channel = channelFactory.newChannel();
        init(channel);
		...
        ChannelFuture regFuture = config().group().register(channel);
		...
    }
  1. channelFactory.newChannel():反射调用NioServerSocketChannel的构造方法进行实例化。
  2. init(channel):为NioServerSocketChannel的ChannelPipeline添加ChannelInitializer。
  3. config().group().register(channel):把NioServerSocketChannel中的ServerSocketChannel注册到workerGroup的NioEventLoop的Selector上。

在这里插入图片描述

channelFactory.newChannel()

channelFactory.newChannel()会进入到ReflectiveChannelFactory的newChannel()方法,里面就是调用Constructor构造器的newInstance()方法反射实例化一个Channel,这个构造器就是NioServerSocketChannel的构造器。是在我们调用ServerBootstrap的channel(NioServerSocketChannel.class)方法时设置进去的。

我们进入NioServerSocketChannel的构造方法看看。

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

newSocket(…)方法就是创建了一个NIO原生的ServerSocketChannel,然后进入重载的构造方法。

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

继续调用父类的构造方法,可以看到指定了事件类型是accept事件。然后进入到父类AbstractNioChannel的构造方法。

    protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
        super(parent);
        this.ch = ch;
        this.readInterestOp = readInterestOp;
		...
        ch.configureBlocking(false);
		...
    }

AbstractNioChannel的构造方法除了继续调用父类的构造方法以外,还保存了ServerSocketChannel和关注的事件类型OP_ACCEPT,然后设置ServerSocketChannel为非阻塞。

继续进入父类的构造方法。

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

创建了一个unsafe对象,和一个ChannelPipeline。unsafe其实是Netty内部自己使用的一个工具类,先不用管,后面会看到怎么用,这里看看如何创建ChannelPipeline。

    protected DefaultChannelPipeline(Channel channel) {
		...
        tail = new TailContext(this);
        head = new HeadContext(this);
        head.next = tail;
        tail.prev = head;
    }

我们知道ChannelPipeline中的ChannelHandler都是包在ChannelHandlerContext里面的,而这里先创建了一个头部Context和尾部Context。

也就是这样:

在这里插入图片描述

channelFactory.newChannel()的大体逻辑如下:
在这里插入图片描述

init(channel)

我们回到initAndRegister()方法,再来看init方法。

    @Override
    void init(Channel channel) {
        ...
        ChannelPipeline p = channel.pipeline();
		...
        p.addLast(new ChannelInitializer<Channel>() {
            @Override
            public void initChannel(final Channel ch) {
				...
                ch.eventLoop().execute(new Runnable() {
                    @Override
                    public void run() {
                        pipeline.addLast(new ServerBootstrapAcceptor(
                                ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
                    }
                });
            }
        });
    }

init方法就是拿到NioServerSocketChannel的ChannelPipeline,往里面添加了一个ChannelInitializer,ChannelInitializer会在ServerSocketChannel被注册到Selector后,异步的给ChannelPipeline添加一个ServerBootstrapAcceptor,这个ServerBootstrapAcceptor是一个专门处理连接事件的Handler。当然ChannelInitializer还会从ChannelPipeline中把自己删除。

在这里插入图片描述

config().group().register(channel)

我们回到initAndRegister()方法,再往后看一下config().group().register(channel)。

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

进入NioEventLoopGroup的父类MultithreadEventLoopGroup的register方法中。next()方法返回一个NioEventLoop,然后调用NioEventLoop的register(channel)方法。进入到NioEventLoop的父类SingleThreadEventLoop的register方法中。

    @Override
    public ChannelFuture register(final ChannelPromise promise) {
        ...
        promise.channel().unsafe().register(this, promise);
        ...
    }

promise.channel().unsafe()获取到NioServerSocketChannel中的Unsafe对象,然后调用Unsafe对象的register(…)

        @Override
        public final void register(EventLoop eventLoop, final ChannelPromise promise) {
			...
            if (eventLoop.inEventLoop()) {
                register0(promise);
            } else {
            	...
                eventLoop.execute(new Runnable() {
                    @Override
                    public void run() {
                        register0(promise);
                    }
                });
                ...	
            }
        }

Unsafe的register方法中,判断当前线程是否是eventLoop的线程,如果是,直接调用register0方法注册Channel,否则调用eventLoop的execute方法开启一个任务异步注册Channel。

    private void execute(Runnable task, boolean immediate) {
        boolean inEventLoop = inEventLoop();
        addTask(task);
        if (!inEventLoop) {
            startThread();
            ...
        }
        ...
    }

execute方法首先调用addTask(task)方法,把这个任务放入taskQueue中。然后判断当选线程不是当前EventLoop的线程,因此调用startThread()开启EventLoop的线程,启动事件循环。

在这里插入图片描述

addTask方法就是把这个Runnable放入到NioEventLoop内部的队列当中taskQueue中,在NioEventLoop的事件循环的每一轮的最后,会处理taskQueue中的任务,我们就不细看了。我们接下来看一下startThread方法。

startThread()

    private void startThread() {
        if (state == ST_NOT_STARTED) {
            if (STATE_UPDATER.compareAndSet(this, ST_NOT_STARTED, ST_STARTED)) {
                ...
                doStartThread();
                ...
            }
        }
    }

startThread方法先修改NioEventLoop的状态state为开启,防止再次调用startThread方法的时候重复开启线程。然后调用doStartThread()方法。

    private void doStartThread() {
        ...
        executor.execute(new Runnable() {
            @Override
            public void run() {
                ...
                SingleThreadEventExecutor.this.run();
                ...
            }
        });
    }

doStartThread()方法里面调用了executor的execute方法,异步执行SingleThreadEventExecutor.this.run()方法,这个run方法就会进入到NioEventLoop的run方法中,里面就是事件循环。

这个executor其实是一个单线程的线程池,可以看做就是NioEventLoop的唯一一个线程,startThread方法并没有再创建一个线程,而是往NioEventLoop预先创建好的线程提交一个任务,这个任务会启动NioEventLoop的事件循环。

在这里插入图片描述

run()

接下来进入NioEventLoop的run方法看看,所谓的事件循环是什么。

    @Override
    protected void run() {
        ...
        for (;;) {
            ...
            strategy = select(curDeadlineNanos);
            if (strategy > 0) {
                processSelectedKeys();
            }
            ...
            runAllTasks();
            ...
        }
    }

可以看到,就是先调用select方法,这个select方法自然是调用NioEventLoop的Selector的select方法,当然现在还没有任何Channel被注册,因此这里是不会select到东西的。然后processSelectedKeys()是处理所有就绪事件的方法,因为这里还没有Channel被注册,自然也没有就绪事件发生。因此最后只有runAllTasks()被执行,也就是获取taskQueue中刚刚放进去的任务,这个任务的执行就会触发ServerSocketChannel的注册。

在这里插入图片描述

runAlllTasks方法执行taskQueue中目前唯一一个的任务,也就是刚刚放进去的用于注册ServerSocketChannel的任务,这个任务会调用Unsafe的register0(ChannelPromise promise)方法。

register0(ChannelPromise promise)

        private void register0(ChannelPromise promise) {
				...
                doRegister();
				...
        }

register0调用doRegister()方法,进入AbstractNioChannel的doRegister()方法。

    @Override
    protected void doRegister() throws Exception {
				...
                selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
				...
    }

doRegister方法中把Channel注册到Selector的代码就是上面这一行,javaChannel()返回NIO原生的Channel类型,然后调用register方法,eventLoop().unwrappedSelector()返回的就是Selector,这个Selector作为register方法的参数,这样NIO的Channel就被注册到Selector中了,这里的Channel当然是ServerSocketChannel。

在这里插入图片描述

这里注册完ServerSocketChannel之后,就会调用ChannelInitializer初始化ChannelPipeline。

doBind0(…)

我们回调doBind方法,initAndRegister()方法就已经看完了,再看下面的doBind0方法。

    private static void doBind0(...,final Channel channel,...) {
        channel.eventLoop().execute(new Runnable() {
            @Override
            public void run() {
                ...
                channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
                ...
            }
        });
    }

dobind0()方法也是通过NioEventLoop执行异步任务的方式去绑定端口,channel.bind(localAddress, promise)方法最终会进入NioServerSocketChannel的doBind(SocketAddress localAddress)方法。

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

然后就是调用ServerSocketChannel的bind(SocketAddress local, int backlog)方法绑定指定端口。

在这里插入图片描述

至此,Netty服务端就启动起来,下面是Netty服务端启动的源码流程图。

在这里插入图片描述

服务端启动之后,就可以接收并处理客户端的请求了,后续的流程就放到下一次再作分析。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/715959.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

数据价值管理-数据使用标准

前情提要&#xff1a;数据价值管理是指通过一系列管理策略和技术手段&#xff0c;帮助企业把庞大的、无序的、低价值的数据资源转变为高价值密度的数据资产的过程&#xff0c;即数据治理和价值变现。第一讲介绍了业务架构设计的基本逻辑和思路。 前面我们讲完了数据资产建设标准…

线性卷积(相关)和圆周卷积(相关)以及FFT之间的关系(AEC举例)

时域自适应滤波算法中的线性卷积和线性相关运算量较大&#xff0c;导致计算复杂度升高&#xff0c;我们更愿意把这两个信号变换到频域&#xff0c;通过频域相乘的方式来取代时域复杂度相当高的卷积或相关运算。 预备知识&#xff1a;线性卷积&#xff08;相关&#xff09;和圆…

Origin中增加一列并更新绘图

一、在book当中增加数据列 二、回到绘图中&#xff0c;双击图层 三、修改增加图像的格式 四、根据需要删除图例中多余的部分

Stable Diffusion 有什么推荐的Checkpoint 模型、Lora?

引言 -2k字给讲清楚我最常用的SD模型库、关键词和参数&#xff01; 2022年末我接触sd的时候&#xff0c;还在为可以用Ai绘画而沾沾自喜&#xff0c;现在玩的风生水起&#xff0c;真的感觉没有白接触。除了chatgpt的出现&#xff0c;Ai绘画无意识这两年来的黑科技&#xff0c;如…

接口postman

前后端 前端&#xff1a;是肉眼所能见到的界面 后端&#xff1a;处理数据&#xff0c;数据逻辑 接口&#xff1a;提供前后端交互的通道 接口测试&#xff1a;校验接口返回的响应数据是否与预期的一致 接口测试可以绕过前端&#xff0c;直接对服务器进行测试 请求方式 pos…

论文阅读——ApeGNN- Node-Wise Adaptive Aggregation in GNNs for Recommendation

ApeGNN: Node-Wise Adaptive Aggregation in GNNs for Recommendation ApeGNN&#xff1a;GNN 中的节点自适应聚合以进行推荐 Abstract 近年来&#xff0c;图神经网络&#xff08;GNN&#xff09;在推荐方面取得了长足的进步。基于 GNN 的推荐系统的核心机制是迭代聚合用户-…

安装AutoCAD异常

问题&#xff1a; 安装Autodesk产品时&#xff0c;显示以下消息&#xff0c;且安装未完成。 正在等待操作系统重新启动。 请重新启动计算机以安装 AutoCAD 2024。 操作系统&#xff1a; Windows 10Windows 11 原因&#xff1a; Windows注册表项已损坏。Microsoft Visual C …

APaaS:智能制造助手

资金不足、IT基础架构薄弱...... 车间业务需求不断地在增加...... 都在说数字化&#xff0c;都在说转型...... 随着企业竞争的日益激烈和市场环境的快速变化&#xff0c;企业需要一个灵活、高效、快速响应市场变化的新平台。在这样的背景下&#xff0c;APaaS应运而生&#x…

缠论再研究1-顶底分型

由于现在不是全职写程序了&#xff0c;看起来不是那么辛苦&#xff0c;终于有点精力重新研究缠论了。 之前做过几个月期货&#xff0c;发现真是太难了&#xff0c;布鲁克斯的书写的好是好&#xff0c;终归还是太过复杂&#xff0c;一时半会吸收不了&#xff0c;加之我们程序员…

Flutter 实现软鼠标

文章目录 前言一、如何实现&#xff1f;1、记录鼠标偏移2、MouseRegion获取偏移3、Transform移动图标 二、完整代码三、使用示例总结 前言 flutter在嵌入式系统中运行时&#xff0c;有可能遇到drm鼠标无法使用的情况&#xff0c;但鼠标事件却可以正常接收&#xff0c;此时如果…

全局弹窗组件实现

全局弹窗组件实现 使用函数式组件实现。框架采用Vue、bootstrap。 当我们写好一个组件时&#xff0c;这个组件功能大体上可能都是差不多的&#xff0c;但是要在很多地方调用&#xff0c;通常的组件要在template中引入才行。 在饿了么UI组件中&#xff0c;有的对话框组件只是…

三.苹果支付 - 漏单补单处理

介绍 苹果的支付流程并不复杂&#xff0c;我们可以在很短的时间内实现正常的支付流程。 但是苹果支付有一个很大特点就是慢&#xff0c;包括唤起支付弹窗&#xff0c;点击完成到服务端验单完成&#xff0c;整个流程很长&#xff0c;任何一个环节都有可能因为断网&#xff0c;…

kubesphere踩过的坑,持续更新....

踩过的坑 The connection to the server lb.kubesphere.local:6443 was refused - did you specify the right host… 另一篇文档中 dashboard 安装 需要在浏览器中输入thisisunsafe,即可进入登录页面 ingress 安装的问题 问题描述&#xff1a; 安装后通过命令 kubectl g…

Oracle 是否扼杀了开源 MySQL

Oracle 是否无意中扼杀了开源 MySQL Peter Zaitsev是一位俄罗斯软件工程师和企业家&#xff0c;曾在MySQL公司担任性能工程师。大约15年前&#xff0c;当甲骨文收购Sun公司并随后收购MySQL时&#xff0c;有很多关于甲骨文何时“杀死MySQL”的讨论。他曾为甲骨文进行辩护&#…

【系统架构设计师】一、计算机系统基础知识(指令系统|存储系统|输入输出技术|总线结构)

目录 一、指令系统 1.1 计算机指令 1.2 指令寻址方式 1.3 CISC 与 RISC 1.4 指令流水线 二、存储系统 2.1 分级存储体系 2.2 地址映射 2.3 替换算法 2.4 磁盘 2.4.1 磁盘结构和参数 2.4.2 磁盘调度算法 三、输入输出技术 四、总线结构 五、考试真题练习 一、指令…

【PL理论】(29) OOP:面向对象编程 | 案例研究:C++ 中的类 | 继承 | 继承和指针 | Object-oriented Programming

&#x1f4ad; 写在前面&#xff1a;本章我们将进入 Object-oriented Programming&#xff0c;面向对象编程的讲解&#xff0c;探讨 C 中的类&#xff0c;继承等。 目录 0x00 面向对象编程 0x01 C语言中的结构体 0x02 案例研究&#xff1a;C 中的类 0x03 术语 0x04 继承&…

12.3 Go 测试覆盖率

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:「stormsha的主页」…

关于 python request 的 response 返回 b‘\xa3\xff\xff\x11E .....‘ 类型的数据的解决方案

最近写开发一个爬虫&#xff0c; 程序在本地好好的&#xff0c;返回的是正常的 html&#xff0c; 但是到了生产环境&#xff0c;不知道为什么返回的是一堆乱码 长这样&#xff1a; 查了好几天都没有进展&#xff0c; 对其进行各种转码均无效 今天终于找到解决办法了&#xff…

LabVIEW回热系统热经济性分析及故障诊断

开发了一种利用LabVIEW软件的电厂回热系统热经济性分析和故障诊断系统。该系统针对火电厂回热加热器进行优化&#xff0c;通过实时数据监控与分析&#xff0c;有效提高机组的经济性和安全性&#xff0c;同时降低能耗和维护成本。系统的实施大幅提升了火电厂运行的效率和可靠性&…

永久删除的文件如何恢复?记好这4个方法,轻松恢复文件!

“在清理电脑时&#xff0c;我一不小心把一些还需要的文件永久删除了&#xff0c;不知道大家有没有方法可以恢复这些文件呢&#xff1f;” 在数字时代&#xff0c;我们的生活和工作几乎都离不开电脑和各类存储设备。然而&#xff0c;随着数据的不断增长&#xff0c;误删文件、格…