多路复用

article/2025/10/19 10:09:49

讲多路复用先我觉得有必要讲一下什么是阻塞IO、非阻塞IO、同步IO、异步IO这几个东西;linux的五种IO模型:

1)阻塞I/O(blocking I/O)

2)非阻塞I/O(nonblocking I/O)

3) I/O复用(select和poll)(I/O multiplexing)

4)信号驱动I/O(signal driven I/O (SIGIO))

5)异步I/O(asynchronous I/O (the POSIX aio_functions))。

 

POSIX(可移植操作系统接口)把同步IO操作定义为导致进程阻塞直到IO完成的操作,反之则是异步IO。同步异步的概念描述的是用户线程与内核的交互方式:同步是指用户线程发起IO请求后需要等待或者轮询内核IO操作完成后才能继续执行;而异步是指用户线程发起IO请求后仍继续行,当内核IO操作完成后会通知用户线程,或者调用用户线程注册的调函数。

上面的概念看着就很不好理解,这也常常让我傻傻分不清,关于同步和异步,又去查了关于同步和异步,找到一个比较好理解的说法

同步和异步其实指的是,请求发起方对消息结果的获取是主动发起的,还是等被动通知的。如果是请求方主动发起的,一直在等待应答结果(同步阻塞),或者可以先去处理其他的事情,但要不断轮询查看发起的请求是否有应答结果(同步非阻塞 )因为不管如何都要发起方主动获取消息结果,所以形式上还是同步操作。如果是由服务方通知的,也就是请求方发出请求后,要么在一直等待通知(异步阻塞),要么就先去干自己的事了(异步非阻塞),当事情处理完成之后,服务方会主动通知请求方,它的请求已经完成,这就是异步。异步通知的方式一般是通过状态改变,消息通知,或者回调函数来完成,大多数时候采用的都是回调函数。

就可以很清楚知道,同步等待应答结果,是主动去获取消息结果,而异步是等待通知,等别人给他通知,怎么理解这两个的区别呢,后面介绍IO模式的时候慢慢说

下面关于IO模型转至:https://www.cnblogs.com/euphie/p/6376508.html

 

阻塞IO模型

使用recv的默认参数一直等数据直到拷贝到用户空间,这段时间内进程始终阻塞。A同学用杯子装水,打开水龙头装满水然后离开。这一过程就可以看成是使用了阻塞IO模型,因为如果水龙头没有水,他也要等到有水并装满杯子才能离开去做别的事情。很显然,这种IO模型是同步的。

 

非阻塞IO模型

改变flags,让recv不管有没有获取到数据都返回,如果没有数据那么一段时间后再调用recv看看,如此循环。B同学也用杯子装水,打开水龙头后发现没有水,它离开了,过一会他又拿着杯子来看看……在中间离开的这些时间里,B同学离开了装水现场(回到用户进程空间),可以做他自己的事情。这就是非阻塞IO模型。但是它只有是检查无数据的时候是非阻塞的,在数据到达的时候依然要等待复制数据到用户空间(等着水将水杯装满),因此它还是同步IO。

 

IO复用模型

这里在调用recv前先调用select或者poll,这2个系统调用都可以在内核准备好数据(网络数据到达内核)时告知用户进程,这个时候再调用recv一定是有数据的。因此这一过程中它是阻塞于select或poll,而没有阻塞于recv,有人将非阻塞IO定义成在读写操作时没有阻塞于系统调用的IO操作(不包括数据从内核复制到用户空间时的阻塞,因为这相对于网络IO来说确实很短暂),如果按这样理解,这种IO模型也能称之为非阻塞IO模型,但是按POSIX来看,它也是同步IO,那么也和楼上一样称之为同步非阻塞IO吧。

这种IO模型比较特别,分个段。因为它能同时监听多个文件描述符(fd)。这个时候C同学来装水,发现有一排水龙头,舍管阿姨告诉他这些水龙头都还没有水,等有水了告诉他。于是等啊等(select调用中),过了一会阿姨告诉他有水了,但不知道是哪个水龙头有水,自己看吧。于是C同学一个个打开,往杯子里装水(recv)。这里再顺便说说鼎鼎大名的epoll(高性能的代名词啊),epoll也属于IO复用模型,主要区别在于舍管阿姨会告诉C同学哪几个水龙头有水了,不需要一个个打开看(当然还有其它区别)。

 

信号驱动IO模型

通过调用sigaction注册信号函数,等内核数据准备好的时候系统中断当前程序,执行信号函数(在这里面调用recv)。D同学让舍管阿姨等有水的时候通知他(注册信号函数),没多久D同学得知有水了,跑去装水。是不是很像异步IO?很遗憾,它还是同步IO(省不了装水的时间啊)。

 

异步IO模型

调用aio_read,让内核等数据准备好,并且复制到用户进程空间后执行事先指定好的函数。E同学让舍管阿姨将杯子装满水后通知他。整个过程E同学都可以做别的事情(没有recv),这才是真正的异步IO。

 

总结,IO分两阶段:

1.数据准备阶段

2.内核空间复制回用户进程缓冲区阶段

一般来讲:阻塞IO模型、非阻塞IO模型、IO复用模型(select/poll/epoll)、信号驱动IO模型都属于同步IO,因为阶段2是阻塞的(尽管时间很短)。只有异步IO模型是符合POSIX异步IO操作含义的,不管在阶段1还是阶段2都可以干别的事。

 

看完上面的介绍就能清楚什么是同步和异步了,对于IO而言,同步IO不管怎样都还是要read将数据读出来,而异步IO就通知你有数据来就可以直接对数据进行处理,就像个小孩子,同步小朋友肚子饿了,再怎么偷懒,饭菜家里人做好了,他也还是自己去打饭,然后吃,而异步小朋友是个小霸王,要别人打好饭送到面前喂他,他再决定是躺着吃还是坐着吃。

 

阻塞和非阻塞,可以fcntl设置文件成非阻塞,要想非阻塞操作普通的文件时,open(const char *pathname, int flags, mode_t mode);参数flags加上O_NONBLOCK或O_NDELAY。对于网络socket的文件描述符,recv(int sockfd, void *buf, size_t len, int flags);最后一个参数flag设置成MSG_DONTWAIT,对于阻塞的socket也可以setsockopt设置超时时间,可能还有很多处理方法,这里只是说几个我知道的~,接下来就主要说多路复用了!也不会说很详细具体怎么用吧,就是说一下多路复用select、poll、epoll的区别,各自的优缺点什么的。

 

多路复用select、poll、epoll

网上看到的几个比较有趣的对比,来源https://www.cnblogs.com/wt645631686/p/8528912.html

1、select大妈 每一个女生下楼, select大妈都不知道这个是不是你的女神, 她需要一个一个询问, 并且select大妈能力还有限, 最多一次帮你监视1024个妹子;

poll大妈不限制盯着女生的数量, 只要是经过宿舍楼门口的女生, 都会帮你去问是不是你女神;

epoll大妈不限制盯着女生的数量, 并且也不需要一个一个去问. 那么如何做呢? epoll大妈会为每个进宿舍楼的女生脸上贴上一个大字条,上面写上女生自己的名字, 只要女生下楼了, epoll大妈就知道这个是不是你女神了, 然后大妈再通知你;

 

2、一个epoll场景:一个酒吧服务员(一个线程),前面趴了一群醉汉,突然一个吼一声“倒酒”(事件),你小跑过去给他倒一杯,然后随他去吧,突然又一个要倒酒,你又过去倒上,就这样一个服务员服务好多人,有时没人喝酒,服务员处于空闲状态,可以干点别的玩玩手机。至于epoll与selectpoll的区别在于后两者的场景中醉汉不说话,你要挨个问要不要酒,没时间玩手机了。io多路复用大概就是指这几个醉汉共用一个服务员。

 

select

相关函数

FD_ZERO(fd_set* fds) //清空集合
FD_SET(int fd, fd_set* fds) //将给定的描述符加入集合
FD_ISSET(int fd, fd_set* fds) //判断指定描述符是否在集合中
FD_CLR(int fd, fd_set* fds) //将给定的描述符从文件中删除
int select(int max_fd, fd_set *readset, fd_set *writeset, fd_set *exceptset, struct timeval *timeout); 

 

select监视并等待多个文件描述符的属性发生变化,它监视的属性分3类,分别readfds(文件描述符有数据到来可读)writefds(文件描述符可写)、和exceptfds(文件描述符异常)调用后select函数会阻塞,直到有描述符就绪(有数据可读、可写、或者有错误异常),或者超时timeout 指定等待时间)发生函数才返回。当select()函数返回后,可以通过遍历 fdset,来找到究竟是哪些文件描述符就绪。 select函数的返回值是就绪描述符的数目,超时时返回0,出错返回-1;需要注意的还有select第一个参数max_fd指待监听的fd的总个数,它的值是待监听的最大文件描述符加1

fd_set的定义

typedef struct

{

/*XPG4.2requiresthismembername.Otherwiseavoidthename

fromtheglobalnamespace.*/

#ifdef__USE_XOPEN

__fd_maskfds_bits[__FD_SETSIZE/__NFDBITS];

#define__FDS_BITS(set)((set)->fds_bits)

#else

__fd_mask__fds_bits[__FD_SETSIZE/__NFDBITS];

#define__FDS_BITS(set)((set)->__fds_bits)

#endif

}fd_set;

在Linux内核有个参数__FD_SETSIZE定义了每个FD_SET的句柄个数中,这也意味着select所用到的FD_SET是有限的,也正是这个原因select()默认只能同时处理1024个客户端连接请求:
/linux/posix_types.h:
#define __FD_SETSIZE 1024 

还需要注意的是,select返回之后没有发生事件的fd的位会被清空,例如:

(1)执行fd_set set;FD_ZERO(&set);则set用位表示是...0000,0000。

(2)若fd=5,执行FD_SET(fd,&set);后set变为...0001,0000(第5位置为1)

(3)若再加入fd=2,fd=1,则set变为...0001,0011

(4)执行select(6,&set,0,0,0)阻塞等待

(5)若fd=1,fd=2上都发生可读事件,则select返回,此时set变为...0000,0011。注意:没有事件发生的fd=5被清空。

所以每次select轮询完之后,如果还需要再用到select,需要重新调用FD_SET();将文件描述符加入到监听事件里 

select的缺点:

1. 单个进程能够监视的文件描述符的数量存在最大限制,通常是1024,当然可以更改数量,但由于select采用轮询的方式扫描文件描述符,文件描述符数量越多,性能越差;根据fd_size的定义它的大小为32个整数大小(32位机器为32*32,所有共有1024bits可以记录fd),每个fd一个bit,所以最大只能同时处理1024个fd

2. 内核 / 用户空间内存拷贝问题,select需要复制大量的句柄数据结构,产生巨大的开销;

3. select返回的是含有整个句柄的数组,应用程序需要遍历整个数组才能发现哪些句柄发生了事件;

4. select的触发方式是水平触发,应用程序如果没有完成对一个已经就绪的文件描述符进行IO操作,那么之后每次select调用还是会将这些文件描述符通知进程。

 

Poll:

select()和poll()系统调用的本质一样。poll()的机制与 select() 类似,与 select() 在本质上没有多大差别,管理多个描述符也是进行轮询,根据描述符的状态进行处理,但是 poll() 没有最大文件描述符数量的限制(但是数量过大后性能也是会下降)。poll() 和 select() 同样存在一个缺点就是,包含大量文件描述符的数组被整体复制于用户态和内核的地址空间之间,而不论这些文件描述符是否就绪,它的开销随着文件描述符数量的增加而线性增大 

#include <poll.h>struct pollfd{int fd; /* 文件描述符 */short events; /* 等待的事件 */short revents; /* 实际发生了的事件 */} ;int poll(struct pollfd *fds, nfds_t nfds, int timeout);

第一个参数用来指向一个struct pollfd类型的数组,每一个pollfd结构体指定了一个被监视的文件描述符,指示poll()监视多个文件描述符。每个结构体的events域是监视该文件描述符的事件掩码,由用户来设置这个域。revents域是文件描述符的操作结果事件掩码,内核在调用返回时设置这个域,events域中请求的任何事件都可能在revents域中返回。

第二个参数同样是待监听的最大文件描述符加一

第三个参数是超时时间

相比select模型,poll使用链表保存文件描述符,因此没有了监视文件数量的限制,但其他三个缺点依然存在。

 

epoll:

相关函数:

1.调用epoll_create()建立一个epoll对象(在epoll文件系统中为这个句柄对象分配资源)
2. 调用epoll_ctl向epoll对象中添加或删除监听的文件描述符
3. 调用epoll_wait收集发生的事件的连接

 

Epoll触发模式LT模式和ET模式: 

epoll有LT(evel triggered水平触发)和ET(edge-triggered边沿触发)两种触发模式,LT是默认的模式,ET是“高速”模式。LT模式下,只要这个fd还有数据可读,每次 epoll_wait都会返回它的事件,提醒用户程序去操作,而在ET(边缘触发)模式中,它只会提示一次,直到下次再有数据流入之前都不会再提示了,无论fd中是否还有数据可读。ET和LT的区别,LT事件不会丢弃,而是只要读buffer里面有数据可以让用户读,则不断的通知你。而ET则只在事件发生之时通知。可以简单理解为LT是水平触发,而ET则为边缘触发。LT模式只要有事件未处理就会触发,而ET则只在高低电平变换时(即状态从1到0或者0到1)触发。

 

epoll的优点:

1、没有最大并发连接的限制,能打开的FD的上限远大于1024(1G的内存上能监听约10万个端口)
2、效率提升,不是轮询的方式,只监听你感兴趣并添加进去的文件描述符不会随着文件描述符的增加效率下降。只有活跃可用的文件描述符才会调用callback函数;即Epoll最大的优点就在于它只管你“活跃”的连接,而跟连接总数无关,因此在实际的网络环境中,Epoll的效率就会远远高于select和poll。

 

 

选择select,poll,epoll时要根据具体的使用场合以及这三种方式的自身特点。

1、表面上看epoll的性能最好,但是在连接数少并且连接都十分活跃的情况下,select和poll的性能可能比epoll好,毕竟epoll的通知机制需要很多函数回调。

2、select低效是因为每次它都需要轮询。但低效也是相对的,视情况而定,也可通过良好的设计改善

 


http://chatgpt.dhexx.cn/article/ovSJQyJk.shtml

相关文章

io多路复用的原理和实现_IO多路复用机制详解

select&#xff0c;poll&#xff0c;epoll机制区别总结: 服务器端编程经常需要构造高性能的IO模型&#xff0c;常见的IO模型有四种&#xff1a; (1)同步阻塞IO(Blocking IO)&#xff1a;即传统的IO模型。 (2)同步非阻塞IO(Non-blocking IO)&#xff1a;默认创建的socket都是…

【多路复用器介绍】

【多路复用器介绍】意义 作用 实现 意义逻辑电路原理结构与真值表逻辑电路 实现代码参考资料 意义 多路复用器将接收的复合数据流&#xff0c;依照信道分离数据&#xff0c;并将它们送到对应的输出线上&#xff0c;故称为解多路复用器。 实际生活中&#xff0c;使用多路复用器…

多路复用技术(频分多路复用、时分多路复用和波分多路复用)

基带信号就是将数字信号1或0直接用两种不同的电压来表示&#xff0c;然后送到线路上去传输。 宽带信号则是将基带信号进行调制后形成的频分复用模拟信号。 多路复用技术的基本原理是&#xff1a;各路信号在进入同一个有线的或无线的传输媒质之前&#xff0c;先采用调制技术把…

8、多路复用技术

这一节&#xff0c;我们介绍信道的多路复用&#xff0c;作为数据通信基础的收尾知识点&#xff0c;这个知识点并没有特别复杂的地方&#xff0c;主要是理解不同的复用技术的特点&#xff0c;在一些考试中也没有多少考点&#xff0c;或者说不做重点。 多路复用技术 先从字面上来…

TCP/IP多路复用

所有网络通信的本质目标就是进程间通信。 除了寻址&#xff08;Addressing&#xff09;&#xff0c;IP 协议还有一个非常重要的能力就是路由。 寻址告诉我们去往下一个目的地该朝哪个方向走&#xff0c;路由则是根据下一个目的地选择路径。寻址更像在导航&#xff0c;路由更像…

多路复用,讲的很明白

作者&#xff1a;罗志宇 链接&#xff1a;https://www.zhihu.com/question/32163005/answer/55772739 来源&#xff1a;知乎 著作权归作者所有。商业转载请联系作者获得授权&#xff0c;非商业转载请注明出处。 假设你是一个机场的空管&#xff0c; 你需要管理到你机场的所有…

全网最详细的 I/O 多路复用解析

前言 IO多路复用目前在大厂的面试中&#xff0c;一般在两个地方可能会被问到&#xff0c;一个是在问到网络这一块的时候&#xff0c;另一个是在问到 Redis 这一块的时候&#xff0c;因为 Redis 底层也是使用了IO多路复用&#xff0c;所以整体来说 IO多路复用&#xff0c;也算是…

计算机网络基础之多路复用技术

温故: 1、单工传输&#xff1a;单工传输只支持数据在一个方向上传输&#xff0c;数据传送只能在一个方向上进行&#xff0c;任何时候都不能改变方向&#xff0c;就像公路上的单行道&#xff0c;例如无线电广播。 2、半双工传输&#xff1a;半双工传输允许数据在两个方向上传输&…

《JAVA核心知识》学习笔记(JVM)-1

JVM (1) 基本概念&#xff1a; JVM 是可运行 Java 代码的假想计算机 &#xff0c;包括一套字节码指令集、一组寄存器、一个栈、 一个垃圾回收&#xff0c;堆 和 一个存储方法域。 JVM 是运行在操作系统之上的&#xff0c;它与硬件没有直接 的交互 Hotspot JVM 后台运行的系统线…

多路复用技术概述

概述频分复用(Frequency Division Multiplexing)时分复用(Time Division Multiplexing)波分复用(Wave Division Multiplexing)码分复用(Code Division Multiplexing) 概述 数据是在物理链路的信道中传输的&#xff0c;通常一条链路上会有多条信道。在默认情况下&#xff0c;一…

计算机网络-多路复用

什么是多路复用技术呢&#xff1f; 多路复用(multiplexing)&#xff0c;简称复用&#xff0c;是通信技术中的基本概念 。 事实上&#xff0c;多路复用技术的原理就是&#xff0c;把通信资源或者说是链路、信道资源进行的划分&#xff0c;分成一系列的资源片。把这些资源片分配…

一、多路复用

1.什么是多路复用 数据通信系统或计算机网络系统中&#xff0c;传输媒体的带宽或容量往往会大于传输单一信号的需求&#xff0c;为了有效地利用通信线路,希望一个信道同时传输多路信号&#xff0c;这就是所谓的多路复用技术(Multiplexing)。采用多路复用技术能把多个信号组合起…

分类变量回归: R语言中哑变量编码本质

本篇描述分类变量如何进行回归&#xff08;翻译自http://www.sthda.com/english/articles/40-regression-analysis/163-regression-with-categorical-variables-dummy-coding-essentials-in-r/&#xff09; 分类变量(也称为因子或定性变量)是可以将观测数据分组的变量。它们有…

python哑变量转换为类别变量

就是get_dummies&#xff08;&#xff09;功能的逆变化&#xff0c;把哑变量重新变为类别变量 原先的数据 转变后的数据 代码如下&#xff1a; df.columns[1,2,3,4,5,6]df df[df1].stack().reset_index() df.columns[A,B,C] print(df) del df[A] del df[C]

回归模型中的哑变量

在构建回归模型时&#xff0c;如果自变量X为连续性变量&#xff0c;回归系数β可以解释为&#xff1a;在其他自变量不变的条件下&#xff0c;X每改变一个单位&#xff0c;所引起的因变量Y的平均变化量&#xff1b;如果自变量X为二分类变量&#xff0c;例如是否饮酒&#xff08;…

Python超实用小技巧:分类变量转化为哑变量(附哑变量详解)

代码示例 features ["Pclass", "Sex", "SibSp", "Parch"]# 筛选出分类变量用来建模X pd.get_dummies(train[features])# 把分类变量转化为哑变量 哑变量详解 定义&#xff1a;哑变量&#xff08;DummyVariable&#xff09;&#xf…

多元线性回归哑变量设置方法

多元线性回归是研究一个连续型变量和其他多个变量间线性关系的统计学分析方法&#xff0c;如果在自变量中存在分类变量&#xff0c;如果直接将分类变量和连续性变量统一纳入模型进行分析是有问题的&#xff0c;尤其是无序分类资料&#xff0c;即使进入了模型&#xff0c;也难以…

matlab虚拟变量,不要再稀里糊涂的做回归了:如何设置哑变量

原标题&#xff1a;不要再稀里糊涂的做回归了&#xff1a;如何设置哑变量 虚拟变量 ( Dummy Variables) 又称虚设变量、名义变量或哑变量&#xff0c;用以反映质的属性的一个人工变量&#xff0c;是量化了的自变量&#xff0c;通常取值为0或1。引入哑变量可使线形回归模型变得更…

哑变量与逻辑回归

哑变量与逻辑回归 数据 部分数据&#xff1a; admit,gre,gpa,rank 0,380,3.61,3 1,660,3.67,3 1,800,4,1 1,640,3.19,4 0,520,2.93,4 1,760,3,2 1,560,2.98,1 0,400,3.08,2 1,540,3.39,3 0,700,3.92,2 0,800,4,4 0,440,3.22,1 1,760,4,1 0,700,3.08,2 1,700,4,1导入库 numpy…

哑变量处理

dummyVars(formula, data, sep “.”, levelsOnly FALSE, fullRank FALSE, …) sep:因子变量名及其级别之间的可选分隔符。使用sep NULL表示没有分隔符(即模型的正常行为)。 data4 <- read.csv("玩家玩牌数据.csv") head(data4) library(VIM) aggr(data4,pro…