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

news2024/12/28 19:34:16

目录

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, //需要监听的最大的文件描述符值+1
     fd_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; //需要监听的fd
    short 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、DEL
    int fd,    // 要监听的FD
    struct 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数组,用于接收就绪的FD
    int 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.coloradmin.cn/o/1962907.html

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

相关文章

【实践出真知】使用Docusaurus将md文档组织起来就是一个网站(写API文档,写教程、写日记、写博客的有福了)

文章目录 前言一、Docusaurus 是什么?二、一键生成网站框架并预览1. 系统需求2. 脚手架项目网站(一键生成网站框架)3. 生成的目录内容4. 网站运行与展示 总结 前言 前段时间,学习Flet,访问到Flet中文网,被…

魔方财务新版QRuser用户中心主题

本主题支持魔方财务3.5.7版本!可自由切换魔方财务3.5.7版本与其他版本。本主题基于官方default开发,主要面向企业,三端自适应,支持并完美适配多语言。界面精美,简洁清新,主题内新增多处bootstrap-select的调…

软考系统架构师-计算机网络基础

目录 3.1 网络的基本概念 3.2 通信技术 3.3 网络技术 3.4 组网技术 1.网络设备及其工作层级 2.网络协议 (1)应用层协议。 (2)传输层协议。 (3)网络层协议。 3.交换机 4&#xff0e…

Speckly:基于Speckle文档的RAG智能问答机器人

前言 Speckly 是一个基于 检索增强生成 (RAG) 技术的智能问答机器人,它能像一位经验丰富的工程师,理解你的问题,并从 Speckle 文档中精准地找到答案。更厉害的是,它甚至可以帮你生成代码片段! 🚀 本文将详…

linux:基本权限

1、权限与用户之间的关系 在Linux系统中,针对文件定义了三种身份,分别是属主(owner)、属组(group)、其他人(others),每一种身份又对应三种权限,分别是可读(readable)、可写(writable)、可执行(excutable)。 2、如何修改一个文件的…

快团团等社区团购类小区物资团购怎么按商品批量退款?

疫情期间,小区物资团的配送需要达到一定的起送件数,对于一些没有达到起送件数的商品,如何快速地批量退款呢?按照下列操作,只需四步,就可以对某一商品批量退款。 第1步:进入团购页面&#xff0c…

JavaScript(二)变量

一、两种注释方式 // 这是当行注释/* 这是多行注释 这是多行注释 */二、变量是什么 变量就是一个可以存放“数值”的容器,这个“数值”可以是数字、字符串、函数等。 变量不是数值本身,它是一个用于存储数值的容器,你可以把变量想象成一个个…

解决断点问题导致项目没有完全启动bug

场景: 项目启动正常,启动日志也正常打印,但是无法判断是否启动完毕,访问接口也进不了服务 原因: 启动前调试项目打断点时 不晓得打到了某个层面的断点 具体是哪忘了,导致项目没有完全启动,启…

WIFI7:引领智能驾驶新未来

近年来,智能驾驶技术飞速发展,从最初的初级的辅助驾驶逐步迈向高度自动驾驶,这一变化历程深刻依赖的是高效、稳定且前沿的无线通信技术的支撑。WIFI7,作为无线通信领域的最新里程碑,凭借其前所未有的性能提升与功能拓展…

一级指针和一维数组

文章目录 🍊自我介绍🍊一级指针和一维数组🍊a , &a[0]和&a之间的关系 你的点赞评论就是对博主最大的鼓励 当然喜欢的小伙伴可以:点赞关注评论收藏(一键四连)哦~ 🍊自我介绍 Hello,大家好…

Tugraph的安装部署

文章目录 一、安装Docker二、拉取TuGraph镜像三、访问web端 一、安装Docker http://t.csdnimg.cn/djJYX 二、拉取TuGraph镜像 https://hub.docker.com/search?qtugraph 拉取的时间会有些长 docker pull tugraph/tugraph-compile-ubuntu18.04如docker镜像拉取失败&#xff…

IDEA管理远程仓库Git

1、模拟项目 新建一个文件夹,用来这次演示 用IDEA来打开文件夹 2、创建仓库 在IDEA中给该文件夹创建本地仓库和远程仓库 在菜单栏找到VCS选择Share project on Gitee 在弹窗中输入描述信息 接下来会出现以下弹窗 点击ADD后,在gitee上会创建远程仓库 …

敏捷产品经理实训:助力产品负责人掌握敏捷方法,提升产品开发效率

在当今快节奏的市场环境中,产品经理和产品负责人需要快速响应市场变化,推动产品创新,以满足用户不断变化的需求。敏捷产品经理实训课程专为产品经理和产品负责人设计,旨在帮助他们掌握敏捷方法,提高团队协作和产品开发…

python dash框架

Dash 是一个用于创建数据分析型 web 应用的 Python 框架。它由 Plotly 团队开发,并且可以用来构建交互式的 web 应用程序,这些应用能够包含图表、表格、地图等多种数据可视化组件。 Dash 的特点: 易于使用:Dash 使用 Python 语法…

二叉树的介绍及其顺序结构的实现

Hello, 亲爱的小伙伴们,你们的作者菌又回来了,之前我们学习了链表、顺序表、栈等常见的数据结构,今天我们将紧跟之前的脚步,继续学习二叉树。 好,咱们废话不多说,开始我们今天的正题。 1.树 1.1树的概念和…

Vue3+.NET6前后端分离式管理后台实战(三十二)

1,Vue3.NET6前后端分离式管理后台实战(三十二)

Java上门做饭平台系统小程序源码

🍽️解锁新生活方式!揭秘“上门做饭平台系统”的五大魅力🌟 🏠【懒人福音,美食直达家门】 在这个快节奏的时代,谁不想下班后直接享受热腾腾的家常美味呢?上门做饭平台系统就是你的私人厨师团队…

java基础概念07-switch语句

一、switch定义 二、基本语法 switch (expression) { case value1: // 当expression的值等于value1时执行的代码 break; // 可选 case value2: // 当expression的值等于value2时执行的代码 break; // 可选 // 你可以有任意数量的case语句 default: // 可选 // 当没有…

【Android驱动06】GMS兼容性测试CTS --环境搭建、测试执行、结果分析

CTS即Compatibility Test Suite意为兼容性测试,是Google推出的Android平台兼容性测试机制。其目的是尽早发现不兼容性,并确保软件在整个开发过程中保持兼容性。只有通过CTS认证的设备才能合法的安装并使用Google market等Google应用。 一,搭…