Netty源码学习4——服务端是处理新连接的netty的reactor模式
零丶引入
在前面的源码学习中,梳理了服务端的启动,以及NioEventLoop事件循环的工作流程,并了解了Netty处理网络io重要的Channel ,ChannelHandler,ChannelPipeline。
这一篇将学习服务端是如何构建新的连接。
一丶网络包接收流程

当客户端发送的网络数据帧通过网络传输到网卡时,网卡的DMA引擎将网卡接收缓冲区中的数据拷贝到DMA环形缓冲区,数据拷贝完成后网卡硬件触发硬中断,通知操作系统数据已到达。
随后网卡中断处理程序将DMA环形缓冲区的数据拷贝到sk_buffer,sk_buffer位于内核中,它提供了一个缓冲区,使得网卡中断程序可以将他接收到的数据暂存起来,避免数据丢失和切换。
随后发起软中断,网络协议栈会处理数据包,对数据包进行解析,路由,分发(根据目的端口号,分发给对应的应用程序,通过网络编程套接字,应用程序可以监听指定端口号,并接受网络协议栈的数据包)
-
当新的连接建立时,网络协议处理栈会将这个连接的套接字标记为可读,并生成一个accept事件,这个事件通知应用程序有新的连接需要处理
-
当已经建立的连接上有数据到达时,网络协议处理栈会将套接字标记为刻度,并生成一个read事件,这个事件通知应用程序有数据可供读取
-
当应用程序向已经建立的连接写入数据时,如果写缓冲区有足够的空间,写操作会立即完成,不会产生write事件。但如果写缓冲区已满,那么写操作将被暂停,当写缓冲区有足够的空间时,write事件将被触发,通知应用程序可以继续写入数据。
也就是说netty 服务端程序会监听不同的网络事件,并进行处理,这也是源码学习的切入点!
二丶服务端NioEventLoop处理网络IO事件

如上是NioEventLoop的运行机制,在《Netty源码学习2——NioEventLoop的执行》中我们进行了大致流程的学习,这一篇我么主要关注其run中处理网络IO事件的部分。

无论是否优化,最终都是拿到就绪的SelectionKey,循环处理每一个就绪的网络事件,如下便是处理的逻辑:

可以看到无论是accept事件还是read事件都是调用AbstractNioChannel的Unsafe#read方法
Unsafe是对netty对底层网络事件处理的封装,下面我们先看下AbstractNioChannel的类图,可以看到NioServerSocketChannel,和NioSocketChannel都使用继承了AbstractNioChannel,只是父类有所不同

那么NioServerSocketChannel和NioSocketChannel是什么时候Accept or read事件感兴趣的昵?
三丶NioServerSocketChannel设置对accept事件感兴趣
重点在ServerBootstrap#bind中,此方法会调用doBind0

doBind0会调用Channel#bind,然后处理ChannelPipeline#bind的执行,由于bind是出站事件,将从DefaultChannelPipeline的TailContext开始执行,然后调用到HeadContext#bind方法,最终会调用NioServerSocketChannel的unsafe#bind方法
如下是NioServerSocketChannel的unsafe#bind的内容:

主要完成两部分操作:
-
调用java原生ServerSocketChannel#bind方法,进行端口绑定,这样操作系统网络协议栈在分发网络数据的时候,才直到该分发到这个端口的ServerSocketChannel
-
向EventLoop中提交一个pipeline.fireChannelActive()的任务,将在pipeline上触发channelActive方法,HeadContext#channelActive将被调用到

-
这里将调用到Channel#read方法,最终会调用到HeadContext#read

四丶服务端处理Accept事件
前面我们说到,NioEventLoop处理accept事件和read事件都是调用unsafe#read方法,如下是NioServerSocketChannel#unsafe的read方法
public void read() {assert eventLoop().inEventLoop();final ChannelConfig config = config();final ChannelPipeline pipeline = pipeline();final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle();allocHandle.reset(config);boolean closed = false;Throwable exception = null;try {try {do {//读取数据int localRead = doReadMessages(readBuf);if (localRead == 0) {break;}if (localRead < 0) {closed = true;break;}// 计数allocHandle.incMessagesRead(localRead);} while (continueReading(allocHandle));} catch (Throwable t) {exception = t;}int size = readBuf.size();for (int i = 0; i < size; i ++) {readPending = false;// 触发channelReadpipeline.fireChannelRead(readBuf.get(i));}readBuf.clear();allocHandle.readComplete();// 触发channelReadCompletepipeline.fireChannelReadComplete();// 省略} finally {// 省略}}
这里出现一个RecvByteBufAllocator.Handle,这里不需要过多关注,在NioServerSocketChannel建立连接的过程中,它负责控制是否还需要继续读取数据

ServerSocketChannel类提供了accept()方法,用于接受客户端的连接请求,返回一个SocketChannel代表了一个底层的TCP连接。

如上将jdk SocketChannel包装NioSocketChannel的时候会设置SocketChannel非阻塞并在属性readInterestOp记录感兴趣事件为read
包装生成的NioSocketChannel会放到List中,后续每一个就绪的连接会一次传播ChannelRead,并最终传播ChannelReadComplete

1.channeRead事件的传播
上面说到NioEventLoop读取NioServerSocketChannel上的accept事件,将每一个新连接封装为NioServerChannel后,将依次触发channelRead。
如下是ServerBootstrapAcceptor#channelRead方法,可以看到它会将读取生成的NioServerChannel注册到childGroup,这里的childGroup就是ServerBootstrap启动时候指定EventLoopGroup(主从reactor模式中的从reactor)

也就是说主reactor负责处理accept事件,从reactor负责处理read事件
2.channelReadComplete事件传播
大多数人看到 channelReadComplete 都会认为这是 Netty 读取了完整的数据,然而有时却不是这样。channelReadComplete 其实只是表明了本次从 Socket 读了数据,该方法通常可以用来进行一些收尾工作,例如发送响应数据或进行资源的释放等。channelReadComplete方法在每次读取数据完成后,即使没有更多的数据可读,也会被调用一次。
五丶netty对多种reactor模式的支持
这里其实可以看出netty对多种reactor模式(单线程,多线程,主从reactor)的支持

我们其实可以通过修改bossGroup,和workerGroup使netty使用不同的reactor模式
六丶将NioSocketChannel注册到从reactor
上面我们说到主reactor监听accept事件后传播channelRead事件,最终由ServerBootstrapAcceptor调用childGroup#register将包装生成的NioSocketChannel注册到从reactor(也就是workerGroup——EventLoopGroup)下面我们看看这个注册会发生什么

首先workerGroup这个EventLoopGroup会调用next方法选择出一个EventLoop执行register,然后
-
将NioSocketChannel中的jdk SockectChannel注册到Selector中,并将NioSocketChannel当作附件,这样selector#select到事件的时候,可以从附件中拿到网络事件对应的NioSocketChannel

-
触发handlerAdd

-
这一步触发ChannelHandler#handlerAdded
-
最终会调用到childHandler中指定的ChannelInitializer,它会将我们指定的ServerHandler(这里可以扩展我们的业务处理逻辑)加到NioSockectChannel的pipeline中

-
触发ChannelRegistered
-
触发channelActive
-
由于这是一个新连接,是第一次注册到EventLoop,因此会触发channelActive
-
这将调用到DefaultChannelPipeline的HeadContext#readIfIsAutoRead,最终就和我们第三节的【NioServerSocketChannel设置对accept事件感兴趣】差不多——HeadContext#readIfIsAutoRead会调用NioSockectChannel的read方法,最终调用到NioSockectChannel#unsafe的read方法——将注册对read事件感兴趣

七丶再看Netty的Reactor模式

笔者认为netty的reactor有以下几个要点
-
ServerBootstrap#bind方法
-
不仅仅会绑定端口,还会触发channelActive事件,从而使DefaultChannelPipeline中的HeadContext触发netty channel unsafe#beginRead,注册ServerSockectChannel对accept感兴趣
-
NioEventLoop处理新连接
-
这一步Netty 使用Selector进行IO多路复用,当accept事件产生的时候,调用
NioServerSocketChannel#unsafe的read方法,这一步会将新连接封装NioSocketChannel,然后将对应连接的套接字注册到Selector上,然后传播channeRead事件
-
ServerBootstrapAcceptor 对channeRead事件的处理
-
笔者认为这是netty reactor模式的核心,它将NioSocketChannel注册到从reactor上,让子reactor负责处理NioSocketChannel上的事件,并最终注册SocketChannel对read事件感兴趣!
和tomcat的reactor(《Reactor 模式与Tomcat中的Reactor 》)有异曲同工之妙,只是netty Pipeline的设计让整个流程更具备扩展性,当然也增加了源码学习的复杂度doge
文章转载自:Cuzzz
原文链接:https://www.cnblogs.com/cuzzz/p/17842964.html
相关文章:
Netty源码学习4——服务端是处理新连接的netty的reactor模式
零丶引入 在前面的源码学习中,梳理了服务端的启动,以及NioEventLoop事件循环的工作流程,并了解了Netty处理网络io重要的Channel ,ChannelHandler,ChannelPipeline。 这一篇将学习服务端是如何构建新的连接。 一丶网络包…...
8、信息打点——系统篇端口扫描CDN服务负载均衡WAF
文章目录 获取网络信息获取服务信息获取阻碍信息 获取网络信息 服务器厂商信息(阿里云?腾讯云?) 收集的原因:如果服务器架设在云上,不同厂商的一些配置信息不同。判断方法:直接在ip/域名网站搜相…...
Ant Design for Figma设计系统组件库 支持变量 非社区版
Ant Design for Figma 是基于 Ant Design 设计系统的 Figma 组件库,提供丰富的 UI 组件和交互功能,帮助设计师快速构建高质量的 Figma 设计稿。 Ant Design for Figma 继承了 Ant Design 的设计理念和风格,提供丰富的 UI 组件和交互功能&…...
k8s的高可用集群搭建,详细过程实战版
kubernetes高可用集群的搭建 前面介绍过了k8s单master节点的安装部署 今天介绍一下k8s高可用集群搭建 环境准备: vip :192.168.121.99 keeplive master01:192.168.121.153 centos7 master02:192.168.121.154 centos7 master03&a…...
【20年扬大真题】编写对数组求逆的递归算法
【20年扬大真题】 编写对数组求逆的递归算法 void swap(int* a, int* b) {int tmp *b;*b *a;*a tmp; } void Ni(int arr[],int left,int right) {if (left > right) {return;}swap(&arr[left], &arr[right]);Ni(arr, left 1, right - 1); } int main() {int ar…...
日志门面slf4j与常用的日志框架Log4j,Logback和Log4j2
slf4j 是众多日志框架接口的集合(俗称日志门面),它不负责具体的日志实现,只在编译时负责寻找合适的日志框架进行绑定,各日志框架通过扩展jar包中的适配器与slf4j建立适配 SLF4J可以和Log4j、Logback、Log4j2、JUL等日志框架配合使用,这里主要…...
使用ssh在本地环境(Windows)连接虚拟机以及其中的docker容器
配置虚拟机防火墙 防火墙的一系列操作需要root权限,默认是没有root密码的,所以首先需要设置root密码: sudo passwd root按提示完成root密码设置 切换到root账户 su root启用22端口并重启防火墙 firewall-cmd --permanent --add-port22/tc…...
没收到Win11 23H2正式版的推送怎么升级到23H2
没收到Win11 23H2正式版的推送怎么升级到23H2?用户反映自己没有收到Win11 23H2正式版的更新推送,又想升级为23H2版本。接下来小编给大家详细介绍不同的升级方法,帮助更多的用户完成Win11 23H2系统的更新,升级后就能体验到Win11 23…...
SpringBoot整合Redis使用基于注解的缓存
环境准备 注解 EnableCaching CacheConfig CacheConfig 提供了一种在类级别共享公共缓存相关设置的机制。 | 参数 | 作用 | | | — | — | — | | cacheNames | 使用在类上的默认缓存名称 | | | keyGenerator | 用于类的默认KeyGenerator的bean名称 | | | cacheManager | 自定…...
STM32:时钟树原理概要
在一般情况下只要在CubeIDE中将RCC下的高速时钟源设置成晶振,随后在时钟配置中把HCLK设置到最大频率(比如STM32F103的最高频率是72MHZ ),CubeIDE就会帮我们自动调节其它参数到合适的值。这样我们芯片就可以全速运行了。 一、时钟信…...
Python量化--诺贝尔奖获得者布莱克-斯科尔斯期权定价公式在日间交易中的应用
“我们不能让你在不了解一点期权定价基础知识的情况下离开麻省理工学院,”Andrew Lo 教授在麻省理工学院的 15.401 金融理论课上对学生们说道。虽然我还不是麻省理工学院的学生,但这句话给了我一个直觉:期权定价一定极其重要。由于像麻省理工学院毕业生这样的精英金融人士都…...
Redis 5 种基本数据类型详解
Redis 共有 5 种基本数据类型:String(字符串)、List(列表)、Set(集合)、Hash(散列)、Zset(有序集合)。 这 5 种数据类型是直接提供给用户使用的&…...
LeetCode8-字符串转换整数(atoi)
目录 1.大神解法2.我的辣鸡解法:3.整数相加的溢出判断(chaGPT代码)4.整数相乘溢出判断(chatGPT代码) 到目前为止比较简单容易理解的一个代码: 参考链接: 🔗:【8. 字符串转换整数 String to Integer (atoi) 【LeetCode 力扣官方题解】-哔哩哔哩】 1.大神解法 累乘和…...
算法分析与设计课后练习22
设W(5,7,10,12,15,18,20)和M35,使用过程SUMOFSUB找出W种使得和数等于M的全部子集并画出所生成的部分状态空间树...
芯片IO口不加电阻会怎样?
芯片IO口不加电阻会怎样? 可能会导致以下几个后果: 1.高电流问题,IO口没有电阻限流,当与外部设备直接连接时,就可能会导致过大的电流流过IO口,这就可能损坏IO口,引起短路或烧坏其它电路组件。像…...
智慧化工园区信息化整体解决方案:PPT全53页,附下载
关键词:智慧化工园区建设方案,智慧化工园区建设规范,智慧化工园区建设指南 一、售智慧化工园区建设背景 随着工业化、信息化和数字化进程的加速,化工园区面临着越来越多的挑战,如安全生产、环境保护、能源消耗等问题…...
深度学习之三(卷积神经网络--Convolutional Neural Networks,CNNs)
概念 卷积神经网络(Convolutional Neural Networks,CNNs)是一种特殊的神经网络结构,专门用于处理具有网格状结构(如图像、音频)的数据。CNN 在计算机视觉领域取得了巨大成功,广泛应用于图像识别、物体检测、图像生成等任务。以下是 CNN 的主要理论概念: 在数学中,卷…...
竞赛选题 目标检测-行人车辆检测流量计数
文章目录 前言1\. 目标检测概况1.1 什么是目标检测?1.2 发展阶段 2\. 行人检测2.1 行人检测简介2.2 行人检测技术难点2.3 行人检测实现效果2.4 关键代码-训练过程 最后 前言 🔥 优质竞赛项目系列,今天要分享的是 行人车辆目标检测计数系统 …...
【链表的说明、方法---顺序表与链表的区别】
文章目录 前言什么是链表链表的结构带头和不带头的区别 链表的实现(方法)遍历链表头插法尾插法任意位置插入一个节点链表中是否包含某个数字删除链表某个节点删除链表中所有关键字key清空链表所有节点 ArrayList 和 LinkedList的区别总结 前言 什么是链…...
彻底解决electron-builder安装问题与npm下载配置问题
electron-builder这个工具每次安装最少要耗费我整整一天的时间。由于只需安装一次即可使用就没去做好笔记,但有时候涉及到更新,或者换了新电脑,这个环境还得重新安装。为了避免下次安装浪费一整天时间,特此做好笔记。 虽然网上找了很多资料但都不详细,现在我们从底层来理解…...
利用 Taotoken 多模型聚合能力为智能体应用构建灵活后端
🚀 告别海外账号与网络限制!稳定直连全球优质大模型,限时半价接入中。 👉 点击领取海量免费额度 利用 Taotoken 多模型聚合能力为智能体应用构建灵活后端 在构建智能体应用时,一个常见的挑战是如何为不同的任务选择合…...
LangForce方法:强化VLA模型语言依赖,提升分布外泛化能力并保留语言核心功能
LangForce方法:强化VLA模型语言依赖,提升分布外泛化能力并保留语言核心功能当前VLA模型常依赖视觉线索而非语言指令,在新场景下表现不佳。论文提出的LangForce方法,通过引入对数似然比损失,强化模型对语言的依赖&#…...
逆向工程实现GitHub Copilot HTTP API:解锁AI代码补全的无限集成可能
1. 项目概述:一个反向工程的“桥梁”如果你是一名开发者,并且对 GitHub Copilot 的智能代码补全能力印象深刻,但同时又希望能在自己偏爱的编辑器、IDE,甚至是命令行工具里直接调用它的能力,那么purocean/expose-github…...
电磁兼容(EMC)设计实战:从干扰源头到系统防护的完整指南
1. 电磁兼容(EMC)设计的核心逻辑 电磁兼容设计就像给电子设备打造一套"防干扰盔甲"。想象一下,你正在用手机通话时,突然听到收音机杂音——这就是典型的电磁干扰现象。EMC设计要解决两个核心问题:不让自家设…...
多模态大模型评测框架VLMEvalKit:从原理到实践,实现高效公平的模型评估
1. 项目概述:一个开箱即用的多模态大模型评测工具箱 如果你最近在折腾多模态大模型,不管是想复现论文里的SOTA结果,还是想给自己训的模型做个“体检”,又或者只是想快速对比一下ChatGPT-4V、Gemini Pro Vision这些闭源巨头的实力…...
手把手教你:在无外网环境下搞定VSCode插件离线安装(附下载地址拼接技巧)
企业内网开发环境高效配置指南:VSCode插件离线部署实战 在高度安全管控的企业研发环境中,外网隔离是常见的安全策略。当新入职的工程师第一次打开内网电脑上的VSCode时,面对空空如也的插件市场,那种无从下手的焦虑感我深有体会。三…...
基于MCP协议的学术成果商业化AI管道:从论文到商业机会的自动化桥梁
1. 项目概述:从象牙塔到市场的自动化桥梁看到apifyforge/academic-commercialization-pipeline-mcp这个项目标题,我的第一反应是:终于有人把学术界和产业界之间那道无形的墙,用代码给砌出了一条自动化通道。这个项目本质上是一个“…...
对比按需计费与TokenPlan在长期项目中的成本体感差异
🚀 告别海外账号与网络限制!稳定直连全球优质大模型,限时半价接入中。 👉 点击领取海量免费额度 对比按需计费与TokenPlan在长期项目中的成本体感差异 在长期运行的AI项目中,成本控制是一个持续优化的过程。不同的计费…...
基于Python与MediaPipe的手势控制系统:从原理到实战
1. 项目概述:用摄像头读懂你的手,让手势成为新鼠标如果你厌倦了每天在键盘和鼠标之间来回切换,或者只是单纯想体验一下《少数派报告》里汤姆克鲁斯隔空操作电脑的酷炫感,那么这个基于Python的手势控制系统绝对值得你花时间折腾一下…...
资本意志下的工程师生存指南:从高通裁员看技术与商业的博弈
1. 从一封信到四千七百张解雇单:当资本意志敲响工程师的门在科技行业,尤其是半导体这个以创新为生命线的领域,我们常常沉浸于晶体管密度、架构革新和制程竞赛的技术叙事中。然而,2015年夏天,一封来自华尔街的公开信&am…...
