Nettyの源码分析
本篇为Netty系列的最后一篇,按照惯例会简单介绍一些Netty相关核心源码。
1、Netty启动源码分析
代码就使用最初的Netty服务器案例,在bind这一行打上断点,观察启动的全过程:

由于某些方法的调用链过深,节约篇幅,不会一张张地截图,只会对最终结果或者关键部分进行说明分析:
doBind 是一个重点方法,其中包含了:
- initAndRegister:初始化ServerSocketChannel并将ServerSocketChannel注册到selector的方法
- doBind0:ServerSocketChannel绑定端口号的方法

1.1、initAndRegister
首先会调用channelFactory工厂类的方法得到一个Channel
相当于NIO中的:
ServerSocketChannel ssc = ServerSocketChannel.open();
然后进入init方法,关键点在于,利用刚刚得到的channel对象,创建了一个流水线,并且添加了一个ChannelInitializer 处理器,监听初始化事件,在初始化事件中,使用eventLoop所在的NIO线程,提交一个任务,向pipeline中新增一个ServerBootstrapAcceptor用于处理新连接。
真正的调用时机是在AbstractChannel的register0中pipeline.invokeHandlerAddedIfNeeded();方法调用时被执行

然后进入.register(channel):

经过一系列的调用链,最终会进入AbstractChannel的register 方法:
关键点:首先会判断当前线程是否是NIO线程,此时是主线程,所以会进入else分支:

在try代码块中,会进行线程切换,由NIO线程负责注册。

我们选择NIO线程,进入register0方法,在register0方法中,有三个关键方法doRegister() 、pipeline.invokeHandlerAddedIfNeeded(); 和safeSetSuccess(promise);
1.1.1、 doRegister()

上图中框出的这一行代码,相当于NIO中的
SelectionKey sscKey = ssc.register(selector, 0, attach);
pipeline.invokeHandlerAddedIfNeeded(); 该方法被执行时会回调ServerBootstrap中init方法的p.addLast,

1.1.2、safeSetSuccess(promise);
该方法是给主线程的final ChannelFuture regFuture 结果。参数中的promise和主线程的regFuture 是同一个。

1.2、doBind0
doBind0实际上是主线程注册的一个regFuture监听回调对象中的方法。当initAndRegister 返回结果后,才会触发回调对象中的operationComplete方法:

在方法内部依旧是保证任务由NIO所在线程执行:

经过一系列的调用,找到了AbstractChannel中的bind方法,bind方法中又有两个重点:

doBind进行端口号绑定

对应NIO中的:
ssc.bind(new InetSocketAddress(8080));
然后会进入if代码块判断,如果目前ServerSocketChannel处于Active状态,就触发流水线上所有的active事件。

最后定位到AbstractChannel中的doBeginRead方法,在方法中注册一个接受连接事件:

相当于NIO中的
sscKey.interestOps(SelectionKey.OP_ACCEPT);
小结:
Netty的启动流程大致可以分为三部分:
- 创建ServerSocketChannel对象。
- 将ServerSocketChannel对象注册到selector上。
- ServerSocketChannel进行端口绑定。
其中,创建ServerSocketChannel对象是由主线程进行的,在将ServerSocketChannel对象注册到selector上时,会进行线程切换,由NIO线程去完成注册以及后续的端口绑定。
在创建ServerSocketChannel对象后,会向ServerSocketChannel的流水线上先注册一个ChannelInitializer事件,加入acceptor handler,但是是在第二步注册后调用流水线的invokeHandlerAddedIfNeeded触发。
端口绑定的方法dobind是regFuture的回调,第二步注册后会向promise中存放结果,由NIO所在线程执行端口绑定,绑定完成后触发NioServerSocketChannel的active事件,设置关注连接事件。
2、EventLoop源码分析
在翻源码之前,我们简单地复习一下什么是EventLoop:
EventLoop是一个不断循环的线程,用于处理所有注册到其上的事件。每一个Channel在创建时会被分配到一个EventLoop上,并且与其绑定,后续该Channel的所有事件都由这个EventLoop进行处理。
EventLoop既可以处理IO事件,也可以处理普通事件或定时事件。
我们重点看它的NioEventLoop实现,NioEventLoop 主要由selector,线程,任务队列组成。
2.1、selector何时被创建
NioEventLoop 有两个selector,可以理解成selector是经过封装优化的,而unwrappedSelector是原始的selector。

它们是在构造方法中被初始化 :


2.2、NioEventLoop 的NIO线程何时启动?
通过下面的案例代码,观察NIO线程启动的时机:
public class TestEventLoop {public static void main(String[] args) {EventLoop eventLoop = new NioEventLoopGroup().next();eventLoop.execute(()->{System.out.println("test");});}
}
进入execute方法:
首先if代码块会检查任务对象是否为空。
然后通过inEventLoop(); 方法检查当前线程是否是NIO线程,此时false。

进入startThread()方法,第一次的state必然和ST_NOT_STARTED相等,进入最外层的if块。如果此时没有其他线程修改状态,则通过第二个if块中的CAS操作将状态修改为2,并且进入doStartThread()方法

doStartThread()方法是启动NIO线程的核心方法:

在 SingleThreadEventExecutor.this.run();中,会根据不同的事件执行对应的操作:

如果没有任务,会进入SelectStrategy.SELECT分支,陷入阻塞。
NIO线程是懒加载的,只有在执行execute方法时才会被创建。
2.3、提交普通任务会不会结束select阻塞?
在select方法内部,会调用有时限的阻塞方法,默认时间是1000ms,在这个期间如果被唤醒则会解除阻塞。

在提交任务的execute中,有一个wakeup方法,我们选择它的NIO实现:

如果不是当前NIO线程的任务,并且CAS成功(因为唤醒操作只需要调用一次wakeup方法,如果多个线程同时调用多次和调用一次的效果是一样的,多次调用影响性能。),才会调用唤醒方法:

2.4、循环时什么时候会进入SelectStrategy.SELECT分支?
进入SelectStrategy.SELECT分支的情况是没有任务:

如果有任务会调用selectNowSupplier的get()方法返回一个selectNow()

selectNow() 方法的作用是立刻查看selector上有无IO事件,如果有则会将IO事件也一起拿到,如果没有就返回0。
2.5、NIO空轮询bug的体现以及Netty的解决方法
什么是NIO的空轮询bug?指的是selector.select(timeoutMillis); 没有到超时时间,期间也没有任务或者事件,但是NIO线程没有在这一行陷入阻塞,而是不断地进行空循环。
这种bug主要是出现在linux环境下,在Netty框架中对其进行了解决:
关键点在于引入了一个计数器,每循环一次计数器+1

当设置了阈值并且循环的次数超过了阈值,就可以认为发生了这个bug,会调用selectRebuildSelector 方法重建一个selector,并且将原有的key以及事件复制过去
阈值的默认值是512次,或者通过参数进行设置:

2.6、ioRatio的作用
在NioEventLoop的run方法中有一段关于处理IO事件和普通事件的逻辑:

其中涉及到了ioRatio,它在成员变量中的默认值是50。
如果它的值为100,则会执行所有的IO事件和普通事件。
否则会对执行普通任务的时间进行计算,用当前时间 - IO事件发生前的当前时间 = IO事件的消耗时间。 假设为4s,然后用 4 * (100 - 50)/ 50 = 4s,得到普通任务的执行时间也为4s,如果在规定的时间内没有执行完普通任务,则会停止执行。
2.7、selectedKeys优化
在创建selector时,会通过反射将 Selector 实现类中的就绪事件集合替换为 SelectedSelectionKeySet

SelectedSelectionKeySet 底层为数组实现,可以提高遍历性能:

这一行是取出附件,在将ServerSocketChannel绑定到selector时,附件对象是Channel。
final Object a = k.attachment();
满足下面的if代码块判断,会进入processSelectedKey(k, (AbstractNioChannel) a); 方法,在这个方法里会根据不同的事件类型做出判断并且执行:

3、accpet源码分析
在原先的NIO中,一旦有事件发生,则会执行以下的代码逻辑:
//1 阻塞直到事件发生
selector.select();Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
while (iter.hasNext()) { //2 拿到一个事件SelectionKey key = iter.next();//3 如果是 accept 事件if (key.isAcceptable()) {//4 执行 acceptSocketChannel channel = serverSocketChannel.accept();channel.configureBlocking(false);//5 关注 read 事件channel.register(selector, SelectionKey.OP_READ);}// ...
}
我们来看一下上面的代码在Netty中的实现过程:
接着2.7中的代码,进入最后一个分支的read方法:

选择AbstractNioMessageChannel 实现,关键代码有以下三处
3.1、doReadMessages(readBuf)
我们选择NioServerSocketChannel的实现:

在SocketChannel ch = SocketUtils.accept(javaChannel()); 这一行代码中,会得到一个SocketChannel,相当于:
SocketChannel channel = serverSocketChannel.accept();
然后会把这个SocketChannel封装在一个NioSocketChannel对象中,并且存放在参数中的list,然后返回:

在NioSocketChannel父类的构造方法中也会设置channel为非阻塞,相当于:
channel.configureBlocking(false);

3.2、allocHandle.incMessagesRead(localRead);
这个方法的主要作用是接受客户端的连接。
3.3、pipeline.fireChannelRead(readBuf.get(i));
这个方法的作用是触发 read 事件,让 pipeline 上的 handler 处理,触发的是ServerBootstrapAcceptor 上的channelRead 事件:
主要看try中的代码:

又回到了register方法中,不过这次线程切换是从NIO的Boss切换到worker:

切换到worker线程:

进入doRegister

注册事件,相当于:
channel.register(selector, 0);

最后关注read事件:

一路跳转到doBeginRead中,执行关注read事件的逻辑:

大致流程和accpet类似,最大的区别是由Worker线程完成。
4、read源码分析
当客户端连接上服务器并且触发了一次write操作时,服务器首先会触发连接操作:

跳过,下一次会触发读取操作:

config.getAllocator(); 会分配一个ByteBufAllocator

得到byteBuf:

在循环中进行读取的逻辑:

相关文章:
Nettyの源码分析
本篇为Netty系列的最后一篇,按照惯例会简单介绍一些Netty相关核心源码。 1、Netty启动源码分析 代码就使用最初的Netty服务器案例,在bind这一行打上断点,观察启动的全过程: 由于某些方法的调用链过深,节约篇幅…...
MySQL远程登录
root是超级管理员,默认情况下,root不能作为远程登录的用户名,远程登录前,需要将登录的数据库在本地登录,修改权限,输入: update user set host % where user root ; 回车键,再输…...
html的作业
目录 作业题目 1.用户注册 A图 B代码 2.工商银行电子汇款单 A图 B代码 3.李白诗词 A图 B代码 4.豆瓣电影 A图 B代码 学习产出: 作业题目 1.用户注册 A图 B代码 <!DOCTYPE html> <html lang"zh"> <head><meta charset&qu…...
【TORCH】查看dataloader里的数据,通过dataloader.dataset或enumerate
文章目录 dataloader.dataset示例代码使用自定义数据集使用 MNIST 数据集 说明 enumerate示例代码说明使用 MNIST 数据集的例子 dataloader.dataset 是的,您可以直接访问 train_loader 的数据集来查看数据,而不必通过 enumerate 遍历数据加载器。可以通…...
KDTree 简单原理与实现
介绍 K-D树是一种二叉树的数据结构,其中每个节点代表一个k维点,可用于组织K维空间中的点,其中K通常是一个非常大的数字。二叉树结构允许对多维空间中的点进行非常有效的搜索,包括最近邻搜索和范围搜索,树中的每个非叶…...
[c++] 可变参数模版
前言 可变参数模板是C11及之后才开始使用,学校的老古董编译器不一定能用 相信大家在刚入门c/c时都接触过printf函数 int printf ( const char * format, ... ); printf用于将数据格式化输出到屏幕上,它的参数非常有意思,可以支持任意数量,任意类型的多参数.而如果我们想实现类…...
QWidget窗口抗锯齿圆角的一个实现方案(支持子控件)2
QWidget窗口抗锯齿圆角的一个实现方案(支持子控件)2 本方案使用了QGraphicsEffect,由于QGraphicsEffect对一些控件会有渲染问题,比如列表、表格等,所以暂时仅作为研究,优先其他方案 在之前的文章中&#…...
数据结构之“队列”(全方位认识)
🌹个人主页🌹:喜欢草莓熊的bear 🌹专栏🌹:数据结构 前言 上期博客介绍了” 栈 “这个数据结构,他具有先进后出的特点。本期介绍“ 队列 ”这个数据结构,他具有先进先出的特点。 目录…...
密码学复习
目录 基础 欧拉函数 欧拉函数φ(n)定义 计算方法的技巧 当a=a_1*a_2*……*a_n时 欧拉定理 剩余系 一些超简单密码 维吉尼亚 密钥fox 凯撒(直接偏移) 凯特巴氏(颠倒字母表) 摩斯密码(字母对应电荷线) 希尔(hill)密码 一些攻击 RSA 求uf+vg=1 快速幂模m^…...
【文献解析】一种像素级的激光雷达相机配准方法
大家好呀,我是一个SLAM方向的在读博士,深知SLAM学习过程一路走来的坎坷,也十分感谢各位大佬的优质文章和源码。随着知识的越来越多,越来越细,我准备整理一个自己的激光SLAM学习笔记专栏,从0带大家快速上手激…...
Http 实现请求body体和响应body体的双向压缩方案
目录 一、前言 二、方案一(和http header不进行关联) 二、方案二(和http header进行关联) 三、 客户端支持Accept-Encoding压缩方式,服务器就一定会进行压缩吗? 四、参考 一、前言 有时请求和响应的body体比较大,需要进行压缩,以减少传输的带宽。 二、方案一(和…...
C++(Qt)-GIS开发-简易瓦片地图下载器
Qt-GIS开发-简易瓦片地图下载器 文章目录 Qt-GIS开发-简易瓦片地图下载器1、概述2、安装openssl3、实现效果4、主要代码4.1 算法函数4.2 瓦片地图下载url拼接4.3 多线程下载 5、源码地址6、参考 更多精彩内容👉个人内容分类汇总 👈👉GIS开发 …...
誉天教育7月开班计划:为梦想插上腾飞的翅膀!
随着夏日的脚步渐近,誉天教育也迎来了新一轮的学习热潮。在这个充满活力和希望的季节里,我们精心策划了7月的开班计划,旨在为广大学子提供一个优质、高效的学习平台,助力他们追逐梦想,实现自我价值。 本月 Linux云计算…...
STM32基础篇:GPIO
GPIO简介 GPIO:即General Purpose Input/Output,通用目的输入/输出。就是一种片上外设(内部模块)。 对于STM32的芯片来说,周围有一圈引脚,有时需要对引脚进行读写(读:从外部输入一…...
HTTPS 发送请求出现TLS握手失败
最近在工作中,调外部接口,发现在clientHello步骤报错,服务端没有返回serverHello。 从网上找了写方法,都没有解决; 在idea的vm options加上参数: -Djavax.net.debugSSL,handshake 把SSL和handshake的日…...
数字化精益生产系统--IFS财务管理系统
IFS财务管理系统是一款功能丰富、高效且灵活的企业财务管理软件,广泛应用于多个行业和不同规模的企业中。以下是对IFS财务管理系统的功能设计:...
基于SpringBoot的校园台球厅人员与设备管理系统
本系统是要设计一个校园台球厅人员与设备管理系统,这个系统能够满足校园台球厅人员与设备的管理及用户的校园台球厅人员与设备管理功能。系统的主要功能包括首页、个人中心、用户管理、会员账号管理、会员充值管理、球桌信息管理、会员预约管理、普通预约管理、留言…...
免杀笔记 ---> Session0--DLL注入
刚更新完上一篇,于是我们就马不停蹄的去跟新下一篇!! Session0注入 :: 各位看官如果觉得还不错的可以给博主点个赞💕💕 这次,我把这个脚本直接传到Github上了 喜欢的师傅点个Star噢…...
如何做好IT类的技术面试?
我们在找工作时,需要结合自己的现状,针对意向企业做好充分准备。作为程序员,你有哪些面试IT技术岗的技巧? 方向一:分享你面试IT公司的小技巧 我分享一些基于广泛观察和用户反馈的面试IT公司的小技巧: 技术准…...
A7 配置方式Master SPI如何更改位宽
在 FPGA 完成自初始化后,INIT 释放,FPGA 对模式引脚 (M[2:0]) 进行采样,以确定使用哪种配置模式。当模式引脚 M[2:0] 001 时,FPGA 开始以大约 3 MHz 的频率在 CCLK 上输出时钟。随后,FCS_B 驱动为低电平,紧…...
基于Springboot+Vue的办公管理系统
角色: 管理员、员工 技术: 后端: SpringBoot, Vue2, MySQL, Mybatis-Plus 前端: Vue2, Element-UI, Axios, Echarts, Vue-Router 核心功能: 该办公管理系统是一个综合性的企业内部管理平台,旨在提升企业运营效率和员工管理水…...
Web中间件--tomcat学习
Web中间件–tomcat Java虚拟机详解 什么是JAVA虚拟机 Java虚拟机是一个抽象的计算机,它可以执行Java字节码。Java虚拟机是Java平台的一部分,Java平台由Java语言、Java API和Java虚拟机组成。Java虚拟机的主要作用是将Java字节码转换为机器代码&#x…...
GO协程(Goroutine)问题总结
在使用Go语言来编写代码时,遇到的一些问题总结一下 [参考文档]:https://www.topgoer.com/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/goroutine.html 1. main()函数默认的Goroutine 场景再现: 今天在看到这个教程的时候,在自己的电…...
C# 表达式和运算符(求值顺序)
求值顺序 表达式可以由许多嵌套的子表达式构成。子表达式的求值顺序可以使表达式的最终值发生 变化。 例如,已知表达式3*52,依照子表达式的求值顺序,有两种可能的结果,如图9-3所示。 如果乘法先执行,结果是17。如果5…...
数学建模-滑翔伞伞翼面积的设计,运动状态计算和优化 !
我们考虑滑翔伞的伞翼面积设计问题以及运动状态描述。滑翔伞的性能主要取决于伞翼面积、气动特性以及飞行员的重量。我们的目标是建立数学模型来描述滑翔伞的运动状态,并优化伞翼面积的设计。 一、问题分析 滑翔伞在飞行过程中受到重力、升力和阻力的作用。升力和阻力与伞翼面…...
Modbus RTU与Modbus TCP详解指南
目录 1. Modbus协议基础 1.1 什么是Modbus? 1.2 Modbus协议历史 1.3 Modbus协议族 1.4 Modbus通信模型 🎭 主从架构 🔄 请求响应模式 2. Modbus RTU详解 2.1 RTU是什么? 2.2 RTU物理层 🔌 连接方式 ⚡ 通信参数 2.3 RTU数据帧格式 📦 帧结构详解 🔍…...
LangChain 中的文档加载器(Loader)与文本切分器(Splitter)详解《二》
🧠 LangChain 中 TextSplitter 的使用详解:从基础到进阶(附代码) 一、前言 在处理大规模文本数据时,特别是在构建知识库或进行大模型训练与推理时,文本切分(Text Splitting) 是一个…...
对象回调初步研究
_OBJECT_TYPE结构分析 在介绍什么是对象回调前,首先要熟悉下结构 以我们上篇线程回调介绍过的导出的PsProcessType 结构为例,用_OBJECT_TYPE这个结构来解析它,0x80处就是今天要介绍的回调链表,但是先不着急,先把目光…...
GraphRAG优化新思路-开源的ROGRAG框架
目前的如微软开源的GraphRAG的工作流程都较为复杂,难以孤立地评估各个组件的贡献,传统的检索方法在处理复杂推理任务时可能不够有效,特别是在需要理解实体间关系或多跳知识的情况下。先说结论,看完后感觉这个框架性能上不会比Grap…...
嵌入式面试常问问题
以下内容面向嵌入式/系统方向的初学者与面试备考者,全面梳理了以下几大板块,并在每个板块末尾列出常见的面试问答思路,帮助你既能夯实基础,又能应对面试挑战。 一、TCP/IP 协议 1.1 TCP/IP 五层模型概述 链路层(Link Layer) 包括网卡驱动、以太网、Wi‑Fi、PPP 等。负责…...
