Netty源码学习4——服务端是处理新连接的netty的reactor模式

news2024/9/23 13:25:25

零丶引入

在前面的源码学习中,梳理了服务端的启动,以及NioEventLoop事件循环的工作流程,并了解了Netty处理网络io重要的Channel ,ChannelHandler,ChannelPipeline。

这一篇将学习服务端是如何构建新的连接。

一丶网络包接收流程

当客户端发送的网络数据帧通过网络传输到网卡时,网卡的DMA引擎将网卡接收缓冲区中的数据拷贝到DMA环形缓冲区,数据拷贝完成后网卡硬件触发硬中断,通知操作系统数据已到达。

随后网卡中断处理程序将DMA环形缓冲区的数据拷贝到sk_buffer,sk_buffer位于内核中,它提供了一个缓冲区,使得网卡中断程序可以将他接收到的数据暂存起来,避免数据丢失和切换。

随后发起软中断,网络协议栈会处理数据包,对数据包进行解析,路由,分发(根据目的端口号,分发给对应的应用程序,通过网络编程套接字,应用程序可以监听指定端口号,并接受网络协议栈的数据包)

  • 当新的连接建立时,网络协议处理栈会将这个连接的套接字标记为可读,并生成一个accept事件,这个事件通知应用程序有新的连接需要处理

  • 当已经建立的连接上有数据到达时,网络协议处理栈会将套接字标记为刻度,并生成一个read事件,这个事件通知应用程序有数据可供读取

  • 当应用程序向已经建立的连接写入数据时,如果写缓冲区有足够的空间,写操作会立即完成,不会产生write事件。但如果写缓冲区已满,那么写操作将被暂停,当写缓冲区有足够的空间时,write事件将被触发,通知应用程序可以继续写入数据。

也就是说netty 服务端程序会监听不同的网络事件,并进行处理,这也是源码学习的切入点!

二丶服务端NioEventLoop处理网络IO事件

image-20231119144133597

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

image-20231119144828807

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

image-20231119145215627

可以看到无论是accept事件还是read事件都是调用AbstractNioChannel的Unsafe#read方法

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

image-20231119145637315

那么NioServerSocketChannel和NioSocketChannel是什么时候Accept or read事件感兴趣的昵?

三丶NioServerSocketChannel设置对accept事件感兴趣

重点在ServerBootstrap#bind中,此方法会调用doBind0

image-20231119151109334

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

如下是NioServerSocketChannel的unsafe#bind的内容:

image-20231119152218478

主要完成两部分操作:

  • 调用java原生ServerSocketChannel#bind方法,进行端口绑定,这样操作系统网络协议栈在分发网络数据的时候,才直到该分发到这个端口的ServerSocketChannel

  • 向EventLoop中提交一个pipeline.fireChannelActive()的任务,将在pipeline上触发channelActive方法,HeadContext#channelActive将被调用到

image-20231119152722699

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

image-20231119153004210

四丶服务端处理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;
                    // 触发channelRead
                    pipeline.fireChannelRead(readBuf.get(i));
                }
                readBuf.clear();
                allocHandle.readComplete();
                // 触发channelReadComplete
                pipeline.fireChannelReadComplete();

               // 省略
            } finally {
               // 省略
            }
        }

这里出现一个RecvByteBufAllocator.Handle,这里不需要过多关注,在NioServerSocketChannel建立连接的过程中,它负责控制是否还需要继续读取数据

image-20231119211749627

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

image-20231119212822535

如上将jdk SocketChannel包装NioSocketChannel的时候会设置SocketChannel非阻塞并在属性readInterestOp记录感兴趣事件为read

包装生成的NioSocketChannel会放到List中,后续每一个就绪的连接会一次传播ChannelRead,并最终传播ChannelReadComplete

image-20231119213310003

1.channeRead事件的传播

上面说到NioEventLoop读取NioServerSocketChannel上的accept事件,将每一个新连接封装为NioServerChannel后,将依次触发channelRead。

如下是ServerBootstrapAcceptor#channelRead方法,可以看到它会将读取生成的NioServerChannel注册到childGroup,这里的childGroup就是ServerBootstrap启动时候指定EventLoopGroup(主从reactor模式中的从reactor)

image-20231119215440660

也就是说主reactor负责处理accept事件,从reactor负责处理read事件

2.channelReadComplete事件传播

大多数人看到 channelReadComplete 都会认为这是 Netty 读取了完整的数据,然而有时却不是这样。channelReadComplete 其实只是表明了本次从 Socket 读了数据,该方法通常可以用来进行一些收尾工作,例如发送响应数据或进行资源的释放等。channelReadComplete方法在每次读取数据完成后,即使没有更多的数据可读,也会被调用一次。

五丶netty对多种reactor模式的支持

这里其实可以看出netty对多种reactor模式(单线程,多线程,主从reactor)的支持

image-20231119221320996

我们其实可以通过修改bossGroup,和workerGroup使netty使用不同的reactor模式

六丶将NioSocketChannel注册到从reactor

上面我们说到主reactor监听accept事件后传播channelRead事件,最终由ServerBootstrapAcceptor调用childGroup#register将包装生成的NioSocketChannel注册到从reactor(也就是workerGroup——EventLoopGroup)下面我们看看这个注册会发生什么

image-20231119221631909

首先workerGroup这个EventLoopGroup会调用next方法选择出一个EventLoop执行register,然后

  • 将NioSocketChannel中的jdk SockectChannel注册到Selector中,并将NioSocketChannel当作附件,这样selector#select到事件的时候,可以从附件中拿到网络事件对应的NioSocketChannel

image-20231119222336843

  • 触发handlerAdd

image-20231119223342982

  • 这一步触发ChannelHandler#handlerAdded

  • 最终会调用到childHandler中指定的ChannelInitializer,它会将我们指定的ServerHandler(这里可以扩展我们的业务处理逻辑)加到NioSockectChannel的pipeline中

image-20231119223655423

  • 触发ChannelRegistered

  • 触发channelActive

  • 由于这是一个新连接,是第一次注册到EventLoop,因此会触发channelActive

  • 这将调用到DefaultChannelPipeline的HeadContext#readIfIsAutoRead,最终就和我们第三节的【NioServerSocketChannel设置对accept事件感兴趣】差不多——HeadContext#readIfIsAutoRead会调用NioSockectChannel的read方法,最终调用到NioSockectChannel#unsafe的read方法——将注册对read事件感兴趣

image-20231119225442369

七丶再看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

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

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

相关文章

​使用SPDK lib搭建自己的NVMe-oF Target应用​

一. 背景 NVMe是一种抽象的传输协议层&#xff0c;旨在提供可靠的NVMe命令和数据传输。NVMe over Fabric (NVMe-oF) 实现了NVMe标准在PCIe总线上的扩展&#xff0c;可以支持数据中心的网络存储&#xff0c;它支持多种传输方式包括FC&#xff0c;RDMA和TCP。NVMe-oF服务(应用) 是…

【已解决】移动号码在移动网上营业厅更换为8元保号套餐

有很多人的副卡基本是为了接收银行卡短信&#xff0c;平时基本不打电话和用流量&#xff0c;每个月固定消费在18-30左右&#xff0c;很浪费。今天发现在网上营业厅就可以修改8元保号套餐&#xff0c;分享给大家。 保号套餐 有以下两种&#xff1a; 解决办法&#xff1a; 1、…

redis非关系型数据库

redis非关系型数据库&#xff0c;缓存型数据库 关系型数据库和非关系型数据库的区别 关系型数据库 关系型数据库是一个结构化的数据库。 记录方式&#xff1a;行和列 行的作用&#xff1a;记录对象的属性 列的作用&#xff1a;声明对象 表与表之间是有关联的&#xff1a…

LR学习笔记——初识lightroom

文章目录 介绍图库界面修改照片界面 介绍 Lightroom是Adobe公司开发的一款用于图片后期处理制作的软件&#xff0c;被称为Adobe Photoshop Lightroom。其增强的校正工具、强大的组织功能以及灵活的打印选项可以帮助加快图片后期处理速度&#xff0c;将更多的时间投入拍摄。 相…

Web自动化测试工具的关键功能

当涉及到 Web 应用程序开发和维护&#xff0c;自动化测试工具发挥着至关重要的作用。这些工具不仅仅简化了测试过程&#xff0c;还有助于保障软件质量、提高生产力和减少人力资源的投入。以下是Web自动化测试工具提供的关键功能&#xff1a; 1. 自动化脚本录制和回放功能 Web 自…

口袋参谋:如何通过布局“问大家”,快速提高宝贝转化!

问大家对于中小卖家来说&#xff0c;是非常适合的&#xff0c;因为我们完全可以靠对问大家的布局&#xff0c;提高宝贝的转化率。 问大家的作用 问大家主要是方便买家在购买前&#xff0c;了解商品的一些问题&#xff0c;而作为该不该购买的参考。对于卖家来说&#xff0c;是…

GaussDB新特性Ustore存储引擎介绍

1、 Ustore和Astore存储引擎介绍 Ustore存储引擎&#xff0c;又名In-place Update存储引擎&#xff08;原地更新&#xff09;&#xff0c;是openGauss 内核新增的一种存储模式。此前的版本使用的行存储引擎是Append Update&#xff08;追加更新&#xff09;模式。相比于Append…

亚马逊第二个大语言模型 Olympus 即将上线

据外媒爆料&#xff0c;亚马逊正在训练他的第二个大语言模型——Olympus&#xff0c;很有可能在今年12月份上线。亚马逊计划将Olympus接入在线零售商店、Echo等设备上的Alexa语音助手&#xff0c;并为AWS平台提供新的功能。据说这个大语言模型规模达到2万亿&#xff08;2000B&a…

C#入门(13):特性Attribute

C# 特性&#xff08;Attributes&#xff09;是用于在运行时为程序元素&#xff08;如类、方法、属性等&#xff09;添加声明性信息的一种方式。这些信息可以在程序运行时通过反射&#xff08;Reflection&#xff09;访问。特性可以用来控制程序行为、添加元数据或者影响程序的运…

为何公司强调流程员工总是觉得反感?

在企业管理中&#xff0c;流程设计对于提高效率和降低风险至关重要。然而&#xff0c;很多企业在流程设计时常犯一些常见的错误&#xff0c;导致基层员工对流程感到烦扰&#xff0c;甚至产生抵触情绪。本文将通过分析一个企业的报销流程问题&#xff0c;探讨如何优化流程以提高…

mysqlbinlog使用记录

首先要确认mysql启用了binlog功能。一般默认启用。 mysql> select log_bin; ----------- | log_bin | ----------- | 1 | ----------- 然后确认binlog目录 mysql> select log_bin_basename; ---------------------------- | log_bin_basename | -----…

【【VDMA彩条显示实验之四 含C语言代码】】

VDMA彩条显示实验之四 含C语言代码 VTC 手册简介 所有的视频都需要有时序 有时序的地方就需要有 时序控制器 VTC的 主要作用是 产生 视频时序 相对于上一节 在这里 我们会理解的更多 观察 这个 HB 信号 其实这个和上一节的图片差不多 在 行同步信号 前面就是前沿 在 行同步…

【数据结构初阶】单链表SLlist

描述 不同于顺序表&#xff0c;顺序表的数据是存储在一个连续的空间里的 而链表它是链接起来的结构体地址。 所以我们不用像顺序表一样先创建一块空间出来&#xff0c;而是创建一个能存数据节点和节点与下一个节点之间的连接&#xff1b; 所以&#xff1a;“一个能存数据节点…

基于SpringBoot+Redis的前后端分离外卖项目-苍穹外卖(六)

新增菜品 1.1 需求分析与设计1.1.1 产品原型1.1.2 接口设计1.1.3 表设计 2.2 代码开发2.2.1 文件上传实现2.2.2 新增菜品实现 2.3 功能测试 1.1 需求分析与设计 1.1.1 产品原型 后台系统中可以管理菜品信息&#xff0c;通过 新增功能来添加一个新的菜品&#xff0c;在添加菜品…

BUUCTF [BJDCTF2020]just_a_rar 1

BUUCTF:https://buuoj.cn/challenges 题目描述&#xff1a; 来源&#xff1a;https://github.com/BjdsecCA/BJDCTF2020 密文&#xff1a; 下载附件&#xff0c;解压得到一个.rar压缩包。 解题思路&#xff1a; 1、根据压缩包的名字提示我们使用4位纯数字进行破解。使用ARCH…

006 OpenCV腐蚀膨胀

目录 一、环境 二、形态学原理 三、代码演示 一、环境 本文使用环境为&#xff1a; Windows10Python 3.9.17opencv-python 4.8.0.74 二、形态学原理 在图像处理中&#xff0c;腐蚀和膨胀是两种基本的形态学操作&#xff0c;它们可以有效地用于图像的噪声减少、形状分析和…

目标检测 Faster RCNN全面解读复现

Faster RCNN 解读 经过R-CNN和Fast RCNN的积淀&#xff0c;Ross B. Girshick在2016年提出了新的Faster RCNN&#xff0c;在结构上&#xff0c;Faster RCNN已经将特征抽取(feature extraction)&#xff0c;proposal提取&#xff0c;bounding box regression(rect refine)&…

红海云签约广州无线电集团,头部国企人力资源数字化转型全面加速

广州无线电集团创立于1995年2月28日&#xff0c;是在1960年&#xff08;国家第二个五年计划期间&#xff09;隶属于国家第四机械工业部&#xff08;后国家电子工业部&#xff09;的国营第七五〇厂的基础上整体注册而成&#xff0c;其历史可以追溯至1956年在公私合营背景下组建的…

JDY蓝牙注意事项

波特率设置&#xff1a;9600&#xff0c;不接受115200&#xff0c;或者38400. 不同于WiFi测试&#xff0c;jdy蓝牙不接受AT"指令&#xff0c;可以使用“ATVERSION"指令测试 安信可公司的那个蓝牙指令在这里没有用&#xff0c;不知道是不是生产的公司不一样