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 驱动为低电平,紧…...
C++实现分布式网络通信框架RPC(3)--rpc调用端
目录 一、前言 二、UserServiceRpc_Stub 三、 CallMethod方法的重写 头文件 实现 四、rpc调用端的调用 实现 五、 google::protobuf::RpcController *controller 头文件 实现 六、总结 一、前言 在前边的文章中,我们已经大致实现了rpc服务端的各项功能代…...
K8S认证|CKS题库+答案| 11. AppArmor
目录 11. AppArmor 免费获取并激活 CKA_v1.31_模拟系统 题目 开始操作: 1)、切换集群 2)、切换节点 3)、切换到 apparmor 的目录 4)、执行 apparmor 策略模块 5)、修改 pod 文件 6)、…...
Mybatis逆向工程,动态创建实体类、条件扩展类、Mapper接口、Mapper.xml映射文件
今天呢,博主的学习进度也是步入了Java Mybatis 框架,目前正在逐步杨帆旗航。 那么接下来就给大家出一期有关 Mybatis 逆向工程的教学,希望能对大家有所帮助,也特别欢迎大家指点不足之处,小生很乐意接受正确的建议&…...
oracle与MySQL数据库之间数据同步的技术要点
Oracle与MySQL数据库之间的数据同步是一个涉及多个技术要点的复杂任务。由于Oracle和MySQL的架构差异,它们的数据同步要求既要保持数据的准确性和一致性,又要处理好性能问题。以下是一些主要的技术要点: 数据结构差异 数据类型差异ÿ…...
使用van-uploader 的UI组件,结合vue2如何实现图片上传组件的封装
以下是基于 vant-ui(适配 Vue2 版本 )实现截图中照片上传预览、删除功能,并封装成可复用组件的完整代码,包含样式和逻辑实现,可直接在 Vue2 项目中使用: 1. 封装的图片上传组件 ImageUploader.vue <te…...
Cinnamon修改面板小工具图标
Cinnamon开始菜单-CSDN博客 设置模块都是做好的,比GNOME简单得多! 在 applet.js 里增加 const Settings imports.ui.settings;this.settings new Settings.AppletSettings(this, HTYMenusonichy, instance_id); this.settings.bind(menu-icon, menu…...
视频字幕质量评估的大规模细粒度基准
大家读完觉得有帮助记得关注和点赞!!! 摘要 视频字幕在文本到视频生成任务中起着至关重要的作用,因为它们的质量直接影响所生成视频的语义连贯性和视觉保真度。尽管大型视觉-语言模型(VLMs)在字幕生成方面…...
ABAP设计模式之---“简单设计原则(Simple Design)”
“Simple Design”(简单设计)是软件开发中的一个重要理念,倡导以最简单的方式实现软件功能,以确保代码清晰易懂、易维护,并在项目需求变化时能够快速适应。 其核心目标是避免复杂和过度设计,遵循“让事情保…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...
安全突围:重塑内生安全体系:齐向东在2025年BCS大会的演讲
文章目录 前言第一部分:体系力量是突围之钥第一重困境是体系思想落地不畅。第二重困境是大小体系融合瓶颈。第三重困境是“小体系”运营梗阻。 第二部分:体系矛盾是突围之障一是数据孤岛的障碍。二是投入不足的障碍。三是新旧兼容难的障碍。 第三部分&am…...
