图说Netty服务端启动过程

news2024/12/25 14:54:20

我们知道Netty是一个基于JDK的nio实现的网络编程框架,那Netty的服务端是怎么启动的呢,包括他是何时register 的,何时 bind 端口的,以及何时开始读取网络中的数据的?

让我们带着这个疑问,通过一个官方的例子来深入探究Netty服务端的启动过程。

PS:本文基于netty源码的4.1分支进行分析。

首先我们拿一个最简单的EchoServer的例子来举例说明,具体的代码如下:

 
  1. EventLoopGroup bossGroup = new NioEventLoopGroup(1);  // 1

  2. EventLoopGroup workerGroup = new NioEventLoopGroup();

  3. try {

  4.    ServerBootstrap b = new ServerBootstrap();

  5.    b.group(bossGroup, workerGroup)

  6.     .channel(NioServerSocketChannel.class) // 2

  7.     .option(ChannelOption.SO_BACKLOG, 100)

  8.     .handler(new LoggingHandler(LogLevel.INFO)) // 3

  9.     .childHandler(new ChannelInitializer<SocketChannel>() {  // 4

  10.         @Override

  11.         public void initChannel(SocketChannel ch) throws Exception {

  12.             ChannelPipeline p = ch.pipeline();

  13.             if (sslCtx != null) {

  14.                 p.addLast(sslCtx.newHandler(ch.alloc()));

  15.             }

  16.             p.addLast(new EchoServerHandler());

  17.         }

  18.     });

  19.    // Start the server.

  20.    ChannelFuture f = b.bind(PORT).sync(); // 5

  21.    // Wait until the server socket is closed.

  22.    f.channel().closeFuture().sync();

  23. } finally {

  24.    // Shut down all event loops to terminate all threads.

  25.    bossGroup.shutdownGracefully();

  26.    workerGroup.shutdownGracefully();

  27. }

从上面的代码来看,在启动的过程中共有5处地方需要我们关注,不过最重要的启动服务端的代码,还是在最后第5步的时候。

为了更加清晰的描述整个启动的过程,也便于我们更好的理解和记忆,我将使用多图形少代码的形式来表达。

首先我把启动过程的一个大致流程画成如下的图:

其中有以下几个核心的方法:

  • channel()

  • handler()

  • childHandler()

  • doBind()

除此之外,还有一个初始化EventLoopGroup类的方法:

  • NioEventLoopGroup()

一、初始化EventLoopGroup

我们从最初的初始化 EventLoopGroup 类开始吧,从源码中可以看到是一层一层的构造方法的调用,然后再super到了父类中,最终会调用到 AbstractEventExecutor 类,具体的调用流程如下图所示:

这个过程中创建了几个重要的实例,我用淡蓝色标记出来了。

首先我们需要知道的是,在Netty中有几个比较重要的类:

  • EventLoop

  • EventLoopGroup

  • EventExecutor

  • EventExecutorGroup

他们之间的关系图如下所示:

EventLoop和EventExecutor说到底都是一种Executor。

然后通过调用ServerBootstrap的group()方法,我们将创建的EventLoopGroup对象分别赋值给了ServerBootstrap的 groupchildGroup 属性。

二、执行channel()方法

初始化完了EventLoopGroup之后,接着就开始执行 channel() 方法了,这个方法很简单,就是通过 ReflectiveChannelFactory 类创建了一个 channelFactory ,这个 channelFactory 后面会很有用,都是通过它来创建需要的Channel实例的。这里我就不贴具体的代码了,具体的执行过程可以用下面的图来表示:

通过调用该方法,ServerBootstrap类的 channelFactory 属性就被赋予了值,并且该ChannelFactory的实现类是通过反射来创建Channel的。

后面在需要创建Channel的时候,会调用该channelFactory的 newChannel() 方法,执行该方法之后,会创建三种非常有用的对象:

  • channel

  • pipeline

  • unsafe

三、执行handler()方法

该方法没有创建其他的对象,只是把用户提供的方法参数中所表示的ChannelHandler对象通过该方法来赋值给ServerBootstrap的 handler 属性。

PS:这里创建的handler在后面的初始化时会使用到

四、执行childHandler()方法

该方法没有创建其他的对象,只是把用户提供的方法参数中所表示的ChannelHandler对象通过该方法来赋值给ServerBootstrap的 childHandler 属性。

PS:这里创建的childHandler在后面的初始化时会使用到

五、执行doBind()方法

Netty启动过程中最复杂,步骤最多的就是这个方法了,不过不用担心,我已经把该方法核心的执行过程整理好了,如下图所示:

这里我推荐大家在读源码的时候,可以拿一张纸,一支笔,用画图的形式把方法的调用过程,以及创建了哪些属性等等这些都记下来,一开始可以不用知道那些方法和属性具体是干什么的。先把整个调用流程理清楚,然后再一点一点细化,由点到面的扩展开来,最终把你那张图丰富成一个完整的调用图。

从图中可以看的出来,doBind方法拆分成了两个核心的方法:

  • initAndRegister()

  • doBind0()

第一个 initAndRegister 方法,从方法名字上就可以看得出来,它主要是执行某个init的过程,然后又执行了某个register的过程。

第二个 doBind0 方法,主要是执行了端口的绑定,然后创建了eventLoop不断的执行JDK中的Selector.select()方法,从注册到selector中的channel中选择符合条件的channel。另外创建了一个task,用来从选中的channel中读取数据,然后把读取到的数据给到childHandler进行处理。

下面让我们来深入到这两个方法的执行过程中去,看看到底发生了什么。

5.1 执行initAndRegister方法

initAndRegister方法的执行过程如下图所示:

initAndRegister方法做的事有两件:init和register。在这之前首先通过channelFactory创建了一个channel。该方法是在初始化EventLoopGroup的时候出现的,可以回头看一下,初始化的过程一共创建了三种对象:channel、unsafe、pipeline。

从该方法中慢慢的往下看,就可以看到,通过channelFactory创建了一个channel对象后,然后又拆分成了两个部分,分别对channel进行了初始化,和对channel进行了register。其中register方法,最终会调用到JDK中最原始的register方法,即把一个channel注册到一个selector中去。

  • init

初始化的过程主要是把用户先前创建的handler和childHandler添加到pipeline中去。

  • register

注册的过程主要是把该channel注册到selector中去,这里的channel就是用来接受客户端连接的。

5.2 执行doBind0方法

doBind0方法的执行过程如下图所示:

doBind0做的事也很明确:bind、select以及runTask。

bind的过程最终是调用到JDK中原生的bind方法,其中在unsafe中执行bind的过程时,除了执行了具体的bind之外,还在NioEventLoop中启动了一个线程,用来不断的执行JDK中selector的select方法。然后读取选中的channel中的数据,最后把读取到的数据丢给childHandler去处理。

JDK的epoll空轮询bug

我们知道JDK中的Selector会出现epoll空轮询的bug,若Selector的轮询结果为空,也没有wakeup或新消息处理,则发生空轮询,此时CPU使用率将达到100%。

Netty是通过重建Selector的方式修复该bug的,具体的做法是:

  • 对Selector的select操作周期进行统计,每完成一次空的select操作进行一次计数,

  • 若在某个周期内连续发生n(SELECTORAUTOREBUILD_THRESHOLD)次空轮询,则触发了epoll死循环bug。

  • 重建Selector,判断是否是其他线程发起的重建请求,若不是则将原SocketChannel从旧的Selector上解除注册,重新注册到新的Selector上,并将原来的Selector关闭。

具体的代码是在NioEventLoop中的select方法中执行的,代码如下:

 
  1. private void select(boolean oldWakenUp) throws IOException {

  2.    Selector selector = this.selector;

  3.    try {

  4.        int selectCnt = 0;

  5.        long currentTimeNanos = System.nanoTime();

  6.        long selectDeadLineNanos = currentTimeNanos + delayNanos(currentTimeNanos);

  7.        for (;;) {

  8.            long timeoutMillis = (selectDeadLineNanos - currentTimeNanos + 500000L) / 1000000L;

  9.            if (timeoutMillis <= 0) {

  10.                if (selectCnt == 0) {

  11.                    selector.selectNow();

  12.                    selectCnt = 1;

  13.                }

  14.                break;

  15.            }

  16.            if (hasTasks() && wakenUp.compareAndSet(false, true)) {

  17.                selector.selectNow();

  18.                selectCnt = 1;

  19.                break;

  20.            }

  21.            int selectedKeys = selector.select(timeoutMillis);

  22.            selectCnt ++;

  23.            if (selectedKeys != 0 || oldWakenUp || wakenUp.get() || hasTasks() || hasScheduledTasks()) {

  24.                break;

  25.            }

  26.            if (Thread.interrupted()) {

  27.                selectCnt = 1;

  28.                break;

  29.            }

  30.            long time = System.nanoTime();

  31.            if (time - TimeUnit.MILLISECONDS.toNanos(timeoutMillis) >= currentTimeNanos) {

  32.                // timeoutMillis elapsed without anything selected.

  33.                selectCnt = 1;

  34.            // 当发生的select次数大于指定的阈值时,重建Selector    

  35.            } else if (SELECTOR_AUTO_REBUILD_THRESHOLD > 0 &&

  36.                    selectCnt >= SELECTOR_AUTO_REBUILD_THRESHOLD) {

  37.                // 重建Selector,以解决JDK中的epoll的bug

  38.                rebuildSelector();

  39.                selector = this.selector;

  40.                // Select again to populate selectedKeys.

  41.                selector.selectNow();

  42.                selectCnt = 1;

  43.                break;

  44.            }

  45.            currentTimeNanos = time;

  46.        }

  47.    } catch (CancelledKeyException e) {

  48.        if (logger.isDebugEnabled()) {

  49.            logger.debug(CancelledKeyException.class.getSimpleName() + " raised by a Selector {} - JDK bug?",

  50.                    selector, e);

  51.        }

  52.        // Harmless exception - log anyway

  53.    }

  54. }

完整的启动过程

通过上面的分析,我们最后来总结一下,Netty服务端在启动的时候做了以下的事情:

  • 1.创建了EventLoopGroup、NioEventLoop的实例,并且创建了一个selector

  • 2.创建了一个channelHandler用来在未来实例化Channel

    • 创建Channel的过程中会一并创建pipeline和unsafe

  • 3.设置了ServerBootstrap的handler和childHandler属性,用以在接收到数据后进行业务逻辑的处理

  • 4.通过channelFactory创建了channel实例,并对其进行了初始化和注册到selector上

  • 5.通过Unsafe调用JDK的bind方法将服务绑定到了端口上,并通过EventLoop创建了一个线程来循环执行以下任务

    • 5.1.执行selector的select方法,并通过计数的方式,满足一定条件的情况下对selector进行重建,以解决JDK的epoll空轮询的bug

    • 5.2.对选中的channel执行读操作,并将读取到的数据丢给childHandler进行处理

一个完整的Netty服务端启动过程如下图所示:

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

webRTC 实现人脸识别

首先我们需要先了解一下什么是webRTC 他能做什么 webRTC主要是帮我们处理多媒体应用&#xff0c;如音视频通话&#xff0c;屏幕共享都可以实现&#xff0c;主要基于浏览器API调用&#xff0c;其底层浏览器会调用native C 等一些库帮我们实现的&#xff0c;而我们在应用层掉API …

欧科云链荣获人民网匠心技术奖,科技创新共造企业发展“强引擎”

什么是匠心精神&#xff1f; 是简单的事情重复做 重复的事情用心做 是对自己热爱的事物 不断坚持、不断钻研的精神 昨日&#xff0c;由人民网主办的“2022人民财经高峰论坛”成功举办&#xff0c;论坛还公布了“第十九届人民匠心奖”获奖名单。欧科云链携旗下“链上天眼”产品荣…

第三十七章 数论——博弈论

第三十七章 数论——博弈论一、Nim游戏1、题目2、结论3、结论验证4、代码二、台阶——Nim游戏1、问题2、思路2、代码三、集合——Nim游戏1、问题2、思路—SG()函数2、代码实现&#xff08;记忆化搜索&#xff09;一、Nim游戏 1、题目 2、结论 这里直接说结论&#xff1a; 假设…

vue导入私有组件和注册全局组件

目录先下载并配置插件导入私有组件注册全局组件先下载并配置插件 导入的时候需要路径,有个符号,但不能提示路径,需要手打路径,会发现很麻烦,这时候可以通过vscode插件来解决 vscode搜索Path Autocomplete 配置插件,点击插件设置—扩展设置,点开任意一个setting.json中编辑,打开…

Yield Guild Games 和 Axie Infinity:迄今为止的旅程

2022 年&#xff0c;菲律宾被认为是全球应用 web3 的中心&#xff0c;在 Chainalysis 的全球加密货币应用指数中排名第二&#xff0c;在拥有最多 MetaMask 用户的国家中排名第三。虽然该国作为 Coins.ph 和 BloomX 等开创性数字资产交易所以及对加密货币友好的 UnionBank 的所在…

Flutter生命周期

一、组件生命周期 flutter组件只有两种&#xff1a;有状态和无状态组件。由于无状态组件效率高&#xff0c;如果不涉及到组件内部的数据存储&#xff0c;尽量多的使用无状态组件 1、StatelessWidget build&#xff1a;组件渲染 调用次数&#xff1a;1次 StatelessWidget是无状…

MYSQL索引和sql优化

基本 索引是帮助数据高效查询的有序数据结构&#xff0c;没有索引进行查询就会进行全表扫描myisam中的.MYI和innodb中的.idb都是存放索引的文件。索引提高查询效率的同时&#xff0c;也降低了更新表的数据&#xff0c;因为数据库中删改查会维护索引的结构。一般提到的索引就是…

安装mysqlclient失败解决办法

简介 系统&#xff1a;MAC 前因&#xff1a;django使用mysql数据库报错django.core.exceptions.ImproperlyConfigured: Error loading MySQLdb module.Django使用MySQL数据库需要加载 MySQLdb模块&#xff0c;需要安装 mysqlclient&#xff08;django2.2以前安装pymysql&#…

ipsec 建立正常后业务端口测试通过但是业务访问故障

某公司ipsec 分支和总部对接成功后&#xff0c;检查发现两端业务测试正常。分支和总部服务器可以互访&#xff0c;分支测试总部服务器80端口开放正常&#xff0c;排除两侧因策略的影响。但是总部服务器web业务和数据库业务均出现无法访问&#xff0c;web页面打不开&#xff0c;…

Es初步检索命令

1、_cat GET /_cat/nodes&#xff1a;查看所有节点 请求 &#xff1a; http://192.168.107.129:9200/_cat/nodes 响应 &#xff1a; 127.0.0.1 15 95 8 0.19 0.16 0.24 dilm * 32bb46713f1b GET /_cat/health&#xff1a;查看 es 健康状况 请求 &#xff1a; http://192.16…

kali - 通过抓包获取FTP连接密码

数据来源 开始实验 实验目标&#xff1a;在win2003主机部署一台FTP服务器&#xff0c;然后winXP通过账户密码连接2003主机的FTP服务器&#xff0c;kali虚拟机抓取数据包获取密码。 实验拓补图 实验流程&#xff1a; 1、开启虚拟机并配置IP 我这里开了一台Kali、一台window…

elmentUI组建中el-date-picker实现限制时间范围精确到小时

elmentUI组建中el-date-picker实现限制时间范围精确到小时 需求要求 时间选择器只能选择今天之前的日期.默认时间是前一天00点~23点后台返回的最小时间和最大时间时间精度限制到小时 开始想着用type"datetimerange"来实现,后来发现控制时间禁用无法实现,后改变思路…

SkyEye助力飞控软件Debug

​01.Debug是什么&#xff1f; 1947年9月9日&#xff0c;美国著名科学家格蕾丝.霍普&#xff08;Grace Hopper&#xff09;与其同伴在对Mark II计算机进行研究时发现&#xff0c;导致计算机无法正常工作的罪魁祸首居然是一只粘在继电器上的小飞蛾。格蕾丝用镊子将飞蛾夹出&…

在腾讯、阿里、字节技术岗工作十年能挣普通公务员一辈子的钱吗?

在腾讯、阿里、字节技术岗工作十年&#xff0c;能挣到普通公务员一辈子的钱&#xff0c;但不一定能存到普通公务员一辈子的钱。 大厂程序员 VS 普通公务员谁更香&#xff0c;一直是争论不断的话题&#xff0c;让我们站在程序员的角度&#xff0c;来回答这一问题。 大厂程序员V…

打造智慧社区数字孪生应用新范式

近日&#xff0c;民政部、中央政法委、中央网信办、国家发展改革委等部门印发了《关于深入推进智慧社区建设的意见》&#xff0c;明确指出依托智慧社区综合信息平台&#xff0c;创新政务服务、公共服务提供方式&#xff0c;推动就业、健康、卫生、等服务“指尖办”、“网上办”…

家装市场“攻守道”

不同于很多属于弹性需求的消费行业&#xff0c;“住”属于刚性需求&#xff0c;因此家装就成为了人们日常生活中的重要交易场景。据《疫情下的家居消费心态调查》问卷显示&#xff0c;60%以上的居民不会因为疫情而放弃装修。大量的装修需求激发了家装的活力&#xff0c;家装市场…

Node.js 模块化(二) 开发包/模块加载机制

1. 开发属于自己的包 1. 需要实现的功能 2. 初始化包的基本结构 3. 初始化 package.json 属性&#xff1a; name&#xff1a;包的名称&#xff08;不能重复&#xff0c;在官网检索一下&#xff0c;避免重复&#xff09; version&#xff1a;版本号 main&#xff1a;入口文件&a…

如何下载并生成等高线

如何下载并生成等高线 发布时间&#xff1a;2018-01-17 版权&#xff1a; 同步视频教程&#xff1a;下载高程等高线使用视频教程-Bigemap GIS Office 专题地图制作视频教程&#xff1a;地图数据应用&#xff08;制作地图效果的基本过程&#xff09;-Bigemap GIS Office 视频…

【vue】关于路由的使用

&#xff08;1&#xff09;路由步骤 根据官方的文档&#xff0c;我们的路由大概需要以下的几种构成 &#xff08;1&#xff09;首先引入组件 &#xff08;2&#xff09;创建routes布局 &#xff08;3&#xff09;创建router对象 &#xff08;4&#xff09;抛出 &#xff0…

最长公共子序列

最长公共子序列一、题目二、思路1、状态转移方程&#xff08;1&#xff09;状态表示&#xff08;2&#xff09;状态转移2、循环设计三、代码一、题目 二、思路 这道题是一个很经典的DP问题&#xff0c;那么我们来看一下如何分析。 DP问题我们需要考虑两个问题&#xff1a;第一…