一文搞懂网络IO和java中的IO模型

目录

1.绪论

2.IO分类

3.用户空间和内核空间

4.同步阻塞IO

5.同步非阻塞IO

6.IO多路复用

6.1 基本原理

6.2 linux对IO多路复用的实现方式

6.3.1 select

1.实现原理

2.缺点

6.3.2 poll

1.实现原理

6.3.3 epoll

1.epoll数据结构

2.epoll的函数

3.epoll的优点

4. epoll的两种触发模式

6.3 reactor模型

7.信号驱动IO

7.1 原理

7.2 缺点

8.异步IO

8.1 原理

8.2 缺点

9. 同步 VS 异步 / 阻塞 VS 非阻塞

9.1 同步 VS 异步

9.2 阻塞 VS 非阻塞

10.总结

11.引用


1.绪论

只要涉及网络传输,就一定需要IO,而高性能的网络IO也是保证框架性能的基石。比如redis性能如此高的原因主要有两个,一是高性能的网络组件,二是redis的几乎所有操作都是基于内存。以及大名顶顶的网络框架Netty和Tomcat,有如此高的性能,都离不开优秀的网络IO模型设计。

2.IO分类

在《Unix网络编程》这本书中将网络IO分为了5类,主要是同步阻塞IO,同步非阻塞IO,异步IO多路复用,信号驱动,异步非阻塞IO这5类。而在java中又有BIO、NIO、AIO这三种,那他们的关系是什么呢。

BIO:其实就是同步非阻塞IO。

NIO:NIO英文名称其实是New IO,它对应的其实是IO多路复用,而IO多路复用其实是对同步非阻塞IO的优化,所以将NIO称之为同步非阻塞IO也不无道理。

AIO:其实就是异步非阻塞IO。

3.用户空间和内核空间

在前面讲Mmap的时候,我们说过计算机为了保证内核安全,不允许用户直接操控驱动程序对硬件进行修改。而是操作系统向用户暴露接口,用户如果要操纵内存或者CPU需要调用操作系统提供的接口完成操作。

而计算机为了保证操作系统的运行不被用户程序访问到,所以将寻址空间(简单来说就是内存)分为两部分,分别是内核空间和用户空间。用户如果要读写数据,需要先将数据读入到内核空间的缓存中,然后拷贝到用户空间的缓存中。同理,写数据时,也需要先将数据写入到用户空间缓存,再拷贝到内核空间缓存,最后写入到磁盘或者网卡中。

4.同步阻塞IO

同步阻塞IO其实就是java中的BIO,它的步骤如图所示。

可以看出同步阻塞在用户进程调用内核的recvfrom方法后,会一直等待,直到结果返回。在内核缓冲区无数据并且拷贝期间,用户线程会一直等待。

同步阻塞IO的第一个阶段也是阻塞状态,第二个阶段也是阻塞状态。

5.同步非阻塞IO

可以看出同步非阻塞IO是会一直循环调用recvfrom方法,询问内核进程数据是否到达,他和同步阻塞IO的主要区别是在数据未就绪的时候,线程并不会阻塞,而是一直巡询问操作系统。

同步非阻塞IO尽管在数据未就绪的时候未阻塞,但是它在这段时间内并没有干其他事情,而是一直在与操作系统交互,这样其实读取数据的耗费时间和同步阻塞IO是一样的,而且会频繁与CPU交互,性能可能更低。那为什么还会出现这种IO模型呢?现在的同步非阻塞IO是一个线程读取数据的时候都会与操作系统进行交互,判断当前是否数据就绪。那我们可以不可以让一个专门的线程来替多个线程去询问操作系统是否就绪呢?答案是可以的,就是后面将要介绍的IO多路复用。

同步非阻塞IO的第一个阶段也是非阻塞状态,第二个阶段是阻塞状态。

6.IO多路复用

6.1 基本原理

可以看出IO多路复用的步骤如下:

1.进程调用操作系统提供的select函数,监听多个socket连接,如果所有socket都没有数据,进程便会阻塞等待;

2.如果某个socket数据就绪后,便会给进程返回readable,并唤醒进程;

3.进程调用操作系统的recvfrom方法,将数据从内核缓冲区拷贝到用户缓冲区;

4.给用户进程返回结果。

IO多路复用主要是利用select方法,可以监听多个socket,这也是其性能高的原因。

6.2 linux对IO多路复用的实现方式

在linux中,万物皆文件,而每个文件都有句柄-文件描述符fd来表示。而linux中的IO多路复用就是利用一个进程监听多个fd。一般有三种实现方式,select,poll,epoll。

6.3.1 select

1.实现原理
//类型别名,__fd_mask其实就是long int的别名
typedef long int __fd_mask;
typedef struct {//fds_bits是一个长度为 1024/32 = 32 位的long数组//c语言的long类型占32个字节,所以这个数组共有1024位//如果位为0表未就绪,为1表示就绪__fd_mask fds_bits[__FD_SETSIZE / __NFDBITS];
...
} fd_set;//linux提供的select函数
int select(int nfds, //需要监听的最大的文件描述符值+1fd_set *readfds, //需要监听的读事件的文件描述符位数组fd_set *writefds, //需要监听写事件的文件描述符数组fd_set *exceptfds, //需要监听异常时间的fd数组struct timeval *timeout //如果超过改时间,还是没有数据,便返回,null:永不超时,0:不等待,大于0:固定等待时间
);

步骤如下:

1.进程如果想听多个fd,select提供了3个长度为32的long类型的数组(总共用1024位),将这个数组的需要监听的fd设置为1,并且拷贝到内核空间中,这里以读fd(readfds)数组为例子;

2.内核空间如果没有数据到达,便休眠,如果有fd到达,会将对应的readfds中对应位置设置为1,并且拷贝到用户空间;

3.用户空间变量readfds数组,得到哪些fd就绪;

4.针对这些fd,调用revcfrom函数,将对应fd在内核缓冲区中的数据拷贝到用户缓冲区。

2.缺点

其实从上面的实现我们也看出主要缺点如下:

1.select采用1024位的数组来存储哪些fd需要被监听,所以select最多只支持同时监听1024个fd;

2.select会频繁的将需要监听的fd数组从内核空间到用户空间相互拷贝;

3.select函数不会返回具体哪些fd已经就绪,而是整个fds数组,所以用户空间想要获取到哪些fd就绪,需要遍历整个数组。

6.3.2 poll

poll是对select的改进

1.实现原理
struct pollfd {int fd; //需要监听的fdshort int events; //想要监听的事件类型short int revents; //实际发生的事件类型
}
//poll函数
int poll(struct pollfd *fds,  //需要监听的事件,采用链表存储nfds_t nfds, //需要监听的pollfd个数int timeout; 超时时间
);

poll相对于select其实就解决了上面的select采用1024位的数组来存储fd导致select每次最多只能监听1024个fd问题。poll采用链表存储,理论上监听的fd个数没有上限,但是如果返回的还是整个链表,所以用户进程想要获取到哪些fd被监控,还是需要遍历整个链表,若监听的fd数量很多,反而性能很差。

6.3.3 epoll

1.epoll数据结构

epoll在内存空间,采用红黑树来存储需要监听fd,用链表来存储就绪的fd。

2.epoll的函数

epoll向用户空间提供了几个函数来操作器内部的数据结构:

epoll_create:创建一个epoll实例,内部是event poll,返回对应的句柄epfd int epoll_create(int size);

int epoll_create(int size);

epoll_ctl:用户进程调用epoll_ctl可以将待监听的fd加到红黑树上去,并且为期其绑定一个回调函数,当该fd就绪的时候,会将这个fd加入到就绪链表头部;

// 2.将一个FD添加到epoll的红黑树中,并设置ep_poll_callback
// callback触发时,就把对应的FD加入到rdlist这个就绪列表中
int epoll_ctl(int epfd,  // epoll实例的句柄int op,    // 要执行的操作,包括:ADD、MOD、DELint fd,    // 要监听的FDstruct epoll_event *event // 要监听的事件类型:读、写、异常等
);

epoll_wait:调用该函数会在用户态创建一个event数组,用于接收就绪的fd,如在监听的红黑树中有fd到达,会触发回调函数,并将其加入到就绪链表中。并将数据加入到events数组中,拷贝给用户进程,此时events数组中就是已经就绪的fd。

// 3.检查rdlist列表是否为空,不为空则返回就绪的FD的数量
int epoll_wait(int epfd,                   // epoll实例的句柄struct epoll_event *events, // 空event数组,用于接收就绪的FDint maxevents,              // events数组的最大长度int timeout   // 超时时间,-1用不超时;0不阻塞;大于0为阻塞时间
);
3.epoll的优点

其实上面epoll已经解决了select的三个问题:

1.存储长度问题:采用红黑树来存储需要监听的fd,理论上没有上限;

2.频繁拷贝问题:用户需要监听某个fd的时候只需要调用epoll_ctl方法将其加入到红黑树上即可;

3.结果遍历问题:epoll采用单独的就绪链表来存储就绪的fd,所以只会将就绪的fd拷贝大用户空间传过来的events数组中。

4. epoll的两种触发模式

当我们调用epoll_wait的时候,可以得到事件通知,epoll有两种事件通知方式:

  • LevelTriggered:简称LT,也叫做水平触发。只要某个FD中有数据可读,每次调用epoll_wait都会得到通知。

  • EdgeTriggered:简称ET,也叫做边沿触发。只有在某个FD有状态变化时,调用epoll_wait才会被通知。

主要区别是:如果内核缓冲区中数据较多,一次性不能完全拷贝到用户缓冲区中,如果是LT模式,下一次这个fd还会再就绪链表上面,而ET模式,下一次读取便不会再就绪链表上。我们一般采用LT模式。

6.3 reactor模型

reactor模型主要是将主要分成两部分,分别是selector和handler,selector其实就是调用前面的epoll_wait函数,等待客户端的建立连接请求或者读写请求,如果请求很多,selector也可以交给线程处理。handler主要根据对应的请求类型交给不同的线程池处理。这其实对应的就是netty中的boossGroup和workerGroup。步骤如下:

1.客户端发现建立连接请求给selector;

2.selector监听到客户端请求后,发现是建立连接,类型为accept,交给acceptor处理;

3.accptor会建立为该客户端建立一个chnnel并且注册到selector上去,监听事件为读写;

4.客户端发送读请求给selector,selector判断事件类型为read,将其给处理读IO操作的handler处理。

7.信号驱动IO

7.1 原理

信号驱动IO步骤如下:

1.用户进程调用sigaction函数,内核函数会监听对应fd,此时用户进程不用阻塞可以做其他操作。

2.当用数据就绪时,递交回调信号给用户进程;

3.用户进程收到回调信号过后,调用revcfrom函数将内核空间的数据拷贝到用户空间。

7.2 缺点

1.当IO操作过多时,SIGIO处理函数不能及时处理可能导致信号队列溢出;

2.内核空间与用户空间的频繁信号交互性能也较低。

8.异步IO

8.1 原理

1.用户进程调用aio_read函数,并且给信号绑定一个回调函数;

2.用户进程等待数据,如果数据到达,将数据从内核空间拷贝到用户空间,并且给用户进程返回信号;

3.触发用户进程中信号绑定回调函数,处理数据。

8.2 缺点

异步IO在等待数据和将数据从内核空间拷贝到用户空间这整个过程,用户进程都是不阻塞的。所以其性能特别高,但是和信号驱动一样,如果IO特别多,可能导致信号队列溢出等问题。

9. 同步 VS 异步 / 阻塞 VS 非阻塞

9.1 同步 VS 异步

其实从上面可以看出,同步和异步的最主要的区别是第二阶段从内核缓冲区拷贝到用户缓冲区这个过程是否需要阻塞等待。同步IO会阻塞等待,而异步IO不会。

9.2 阻塞 VS 非阻塞

阻塞和非阻塞的主要确保在第一阶段,等待数据就绪这个过程是用户进程是否会阻塞等待。

10.总结

本文主要介绍了网络中的几种IO模型,并且分析了他们的优缺点。在现在各种框架中,最常用的还是IO多路复用这一模型。同步阻塞IO和同步非阻塞IO在整个过程中,可以认为用户进程是阻塞的,这两种模型的吞吐量是相对较低的。信号驱动IO在等待数据的过程中是非阻塞的,异步IO在整个过程中都是非阻塞的。按理想情况,这两种IO模型应该是吞吐量很大的,但是如果在并发很高的场景下,可能导致内核进程为特别多的信号监听fd,导致吞吐量降低。所以IO多路复用是同步阻塞IO和异步IO的折中,它结合reactor模型,也能够拥有很好的吞吐量,成为现在的主流选择。

11.引用

图解Linux select机制_从内核到应用

Redis入门到实战教程,深度透析redis底层原理+redis分布式锁+企业解决方案+黑马点评实战项目

一文搞懂Reactor模型与实现

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JS小应用:从图床获取的html代码中提取IMG标签并提取图片复制到剪贴板

JS小应用:从图床获取的html代码中提取IMG标签并提取图片复制到剪贴板 问题产生 自己做站长,为了节省银子,难免要用到图床。有的图床可以直接给你URL,这当然是最好的情况: 而有的图床,却禁用了鼠标右键&am…

Null Pointer Exception: 如何快速定位和修复?️

Null Pointer Exception: 如何快速定位和修复?💡🛠️ Null Pointer Exception: 如何快速定位和修复?💡🛠️摘要引言正文内容什么是Null Pointer Exception?🤔NPE的常见原因&#x1f…

3D魔方lua核心脚本制作

制作不易,请好好欣赏 U→R→F→D→L→B 废话不多说,上脚本 --魔方基本运行程序 --星空露珠优化脚本lua --主核心来自分享 --666 --[=[ #G4=I 1 # 2-----------2------------1 # | U1(0) U2(1) U3(2) | # …

Java中的集合相关知识汇总

总结 Java集合 从数据结构可以分为:数组、Set、Map、队列、栈;从多线程安全可以分为线程安全与非线程安全的集合从关联关系可以总结如下(不包含多线程安全类): 点线框表示接口; 折线框表示抽象类; 实线框表示实现类…

【只出现一次的数字 III】python刷题记录

R2-位运算专题. 目录 哈希表 位运算 ps: 一眼哈希表啊 哈希表 class Solution:def singleNumber(self, nums: List[int]) -> List[int]:dictdefaultdict(int)ret[]for num in nums:dict[num]1for key in dict.keys():if dict[key]1:ret.append(key)return ret怎么用位…

[C++][STL源码剖析] 详解AVL树的实现

目录 1.概念 2.实现 2.1 初始化 2.2 插入 2.2.1 旋转(重点) 左单旋 右单旋 双旋 2.❗ 双旋后,对平衡因子的处理 2.3 判断测试 完整代码: 拓展:删除 1.概念 二叉搜索树虽可以缩短查找的效率,但…

Jeecgboot仪表盘设计器使用https时访问报错

问题 仪表盘设计器设计好后,Nginx配置域名发送https请求时,/drag/page/queryById、/drag/page/addVisitsNumber仍发送http请求。导致发送下面错误: 原因 仪表盘设计器里设计的页面是由后端生成返回给前端的,后端是根据后端服…

java算法day27

java算法day27 动态规划初步总结509 斐波那契数杨辉三角打家劫舍完全平方数 动态规划初步总结 如果你感觉某个问题有很多重叠子问题,使用动态规划是最有效的。 动态规划的过程就是每一个状态一定是由上一个状态推导出来的,这一点就区分于贪心了。贪心是…

mysql死锁排查

Mysql 查询是否存在锁表有多种方式,这里只介绍一种最常用的 1、查看正在进行中的事务 SELECT * FROM information_schema.INNODB_TRX 2、查看正在锁的事务 SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS; 3、查看等待锁的事务 SELECT * FROM INFORMATION_SCHEMA.I…

视频VIP收费会员播放帝国CMS模板HTML5自适应手机多种运营模式

采用帝国CMS最新版核心制作,自适应响应式手机平板浏览,手机浏览器非常舒服哦!多种运营模式。用户中心逻辑和页面,都已经制作完整,可以搭建后稍微修改即可使用! 模板特点: 支持多集和单集播放&…

Kafka动态授权认证:利用SASL/SCRAM机制提升安全性

摘要 Apache Kafka是一个流行的分布式流处理平台,其安全性对于保护数据传输至关重要。SASL/SCRAM(Simple Authentication and Security Layer/Salted Challenge Response Authentication Mechanism)是一种认证机制,可以为Kafka集…

从华为出走的工控龙头,豪横收购法国顶尖软件龙头~

导语 大家好,我是社长,老K。专注分享智能制造和智能仓储物流等内容。 新书《智能物流系统构成与技术实践》 近日,业界传来震撼消息,华为系企业汇川科技正式宣布,已完成对法国顶尖工业软件企业Irai的全资收购。 这一战略…

【LLM】-12-部署Langchain-Chatchat-0.3.x版本

目录 1、0.3与0.2的功能对比 2、0.3.x支持多种部署方式 2.3、源码安装 2.3.1、项目源码下载 2.3.2、创建conda环境 2.3.3、安装poetry 2.3.4、安装依赖库 2.3.5、项目初始化 2.3.6、初始化知识库 2.3.7、启动服务 2.3.8、配置说明 2.3.8.1、basic_settings.yaml 2…

一馆多用,四季皆宜:气膜体育馆的优势与应用—轻空间

促进城市体育发展 装配式气膜体育馆以其便捷的安装、灵活的使用和多功能性,迅速在全国范围内得到推广。这种体育场馆不仅适用于篮球、羽毛球、网球等传统室内运动,还能根据需要灵活改造成游泳馆、滑冰场等特殊场地。这种多功能性使得气膜体育馆在城市中得…

甄选范文“论数据分片技术及其应用”软考高级论文,系统架构设计师论文

论文真题 数据分片就是按照一定的规则,将数据集划分成相互独立、正交的数据子集,然后将数据子集分布到不同的节点上。通过设计合理的数据分片规则,可将系统中的数据分布在不同的物理数据库中,达到提升应用系统数据处理速度的目的。 请围绕“论数据分片技术及其应用”论题…

【ThingsBoard初体验】本地运行源码踩坑记录

前言 运行源码之前,请先编译源码。这很重要!!! 官网源码编译教程:http://www.ithingsboard.com/docs/user-guide/contribution/yuanmabianyi/ 如果编译过程中出现报错,请看我上一篇文章:【Thing…

使用ssh-remote连接远程vscode运行yolo项目时的一点坑

使用ssh-remote连接远程vscode运行yolo项目时的一点坑 1.坑1 因为我是直接下载的release包,然后运行 pip install -e .来下载依赖的,那么这个时候需要使用YOLO时都需要在下载的release文件的目录下的py文件才能生效 比方说我下载的yolov8(ultralytic…

从功能出发:优化超市商品陈列,助力销售额提升

随着时代的发展,竞争的加剧,人们的生活节奏加快,时间观念越来越强。在这种情形下,作为超市,怎样为顾客提供一个舒适方便的购物环境,尽可能让顾客逛完整个卖场,满足一站式购足呢?除了…

[PM]面试题-工作问题

画一个原型需要多久?写一篇PRD文档需求多久? 时间长短取决于项目规模和业务难度, 规模大难度高,就要花费很长的时间, 规模下难度低时间就短, 一般来说, 1-2周的时间就可以完成原型和RED文档 市场需求文档写什么? 从打到下进行编写, 大的方面以市场为主体,包括市场规模, 发…

【中项】系统集成项目管理工程师-第9章 项目管理概论-9.1PMBOK的发展与9.2项目基本要素

前言:系统集成项目管理工程师专业,现分享一些教材知识点。觉得文章还不错的喜欢点赞收藏的同时帮忙点点关注。 软考同样是国家人社部和工信部组织的国家级考试,全称为“全国计算机与软件专业技术资格(水平)考试”&…