多路复用(

article/2025/10/19 10:18:34

apue 多路复用

需求来自用户,用户的需求来自实际的使用场景。在实际运用中,一个系统或者程序需要处理的事件并不是只有一个或一类,而是存在各种各样的事件在一小段事件内一起发生,此时按照没学多线程的逻辑的处理方式就是这样:

if(read(事件1) > 0)		//阻塞
{处理事件1;
}
if(read(事件2) > 0)		//阻塞
{处理事件2}

这种就会导致如果事件1阻塞了,会导致如果事件2准备好了,也没办法处理。根据上一篇多线程的内容,我们可以让线程各自阻塞,通过操作系统来调度不同的线程运行,大致如下:

typdef work_ctx_s
{
int fd_business1;
int fd_business2;
};
int main()
{pthread_t			tt;pthread_attr_t  	attr;worker_ctx_t		ctx;pthread_create(&tt,&attr,fd_business1,&ctx);pthread_create(&tt,&attr,fd_business2,&ctx);while(){;}
}
void *business1_worker(void *args)
{worker_ctx_t	ctx = args;if(read(ctx->business1,buf,sizeof(buf)) > 0){执行事件1}
}
void *business2_worker(void *args)
{worker_ctx_t	ctx = args;if(read(ctx->business2,buf,sizeof(buf)) > 0){执行事件2}
}

当有多个多种事件同一段时间发生时,多进程/多线程模式可以解决,但是创建进程和创建线程都是需要时间开销的。在编写服务器客户端程序的时候,如果服务器性能一般,但是客户端连接的又太多的时候,这会造成很大的代价。该篇要说的多路复用就是解决这中问题的办法之一。通过多路复用来监听,是否有事件发生,并把发生的事件是什么告诉服务器端。这种方法叫做多路复用。

多路复用 select()函数实现逻辑


文章目录

  • apue 多路复用
  • 前言
  • 一、学前预备
      • 同步与异步
      • 阻塞与非阻塞
  • 二、实现多路复用的三种方法
    • 1.select()函数实现多路复用
    • 2.poll()函数实现多路复用
    • 3.epoll()函数实现多路复用
  • 总结


前言

学习多路复用之前我们需要掌握一些相关的知识

比如同步与异步,阻塞与非阻塞。


提示:以下是本篇文章正文内容,下面案例可供参考

一、学前预备

同步与异步

同步(sync)和异步(async)的概念描述的是用户线程与内核的交互方式。
同步是指用户线程发起IO请求后需要等待或者轮询内核IO操作完成后才能继续执行;
异步是指用户线程发起IO请求后仍继续执行,当内核IO操作完成后会通知用户线程,或者调用用户线程注册的回调函数。

阻塞与非阻塞

阻塞和非阻塞的概念描述的是用户线程调用内核操作的方式。
阻塞是指IO操作在没有接收完数据或者没有得到结果之前不会返回,需要彻底完成后才返回到用户空间;
非阻塞是指IO操作后立即返回给用户一个状态值,无需等到IO操作彻底完成。

在Linux下进行网络编程是,服务器端编程需要构造高性能的IO模型,常见的IO模型有五种:

  1. 同步阻塞IO(blocking IO):发送方发送请求之后一直等待响应,接收方处理请求时进行的的IO操作如果不能马上等到返回结果,就一直等到返回结果后,才响应发送方,期间不能进行其它工作。即传统的IO模型。网络编程都是从listen()、read()、write()等接口开始的,这些接口都是阻塞型的,想要不阻塞,我们可以使用多进程或者多线程。

  2. 同步非阻塞IO(Non-blocking IO):发送方发送请求之后,一直等待响应,接收方处理请求时进行的IO操作如果不能马上的得到结果就立即返回,去做别的事情,但是发送方没有得到请求处理的结果,一处于等待状态,当IO操作完成以后,将完成状态和结果通知接收方,接收方再响应发送方,发送方才进入下一次请求过程。

  3. IO多路复用(IO multiplexing):一句话解释就是单线程或单进程同时检查若干文件描述符是否可以执行IO操作的能力。IO多路复用模型是建立在内核提供的多路分离函数select基础之上的,使用select函数可以避免同步非阻塞IO模型中轮询等待的问题。

  4. 信号驱动IO(signal driven IO):调动sigaltion系统调用,当内核中IO数据就绪时以SIGIO信号通知请求进程,请求进程再把数据从内核读入到用户空间,这一步是阻塞的。

  5. 异步IO(Asynchronous IO):即经典的Proactor设计模式,也称为异步阻塞IO,“真正”的异步IO需要操作系统更强的支持,在IO多路复用模型中,事件循环将文件句柄的状态事件通知给用户线程,由用户线程自行读取数据、处理数据,而在异步IO模型中,当用户线程收到通知时,数据已经被内核读取完毕,并放在了用户线程指定的缓冲区内,内核在IO完成后通知用户线程直接使用即可。

这是一个讲同步和异步,阻塞和非阻塞的,我觉得挺清楚的链接

二、实现多路复用的三种方法

1.select()函数实现多路复用

select()函数允许进程指示内核等待多个事件(文件描述符)中的任何一个发生,并只在有一个或多个事件发生或经历一段指定事件后才唤醒它,然后接下来判断究竟是哪个文件描述符发生了事件并进行响应的处理。
一下是对select()函数的一些说明:

#include <sys/select.h>
#include <sys/time.h>int select(int max_fd,fd_set *readset,fd_set *writeset,fd_set *exceptset,struct timeval *timeout)该函数的返回值是就绪描述符的数目,超时返回0,出错返回-1;
参数说明如下:
第一个参数:max_fd是指待测试的fd的总个数,它的值是待测试的最大文件描述符加1,假设需要检测的文件描述符是7,那么Linux内核实际也要监测0~7。
中间三个参数readset,writeset,exceptset指定要让内核测试读、写和异常条件的fd集合,如果不需要测试可以设置为NULL。
最后一个参数是设置select的超时事件,如果设置为NULL则用不超时。其中最后一个参数结构体如下:
struct timeval
{long tv_sec;		//secondslong tv_usec;		//micriseconds
};与该函数一起使用的函数有以下:
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)	//将给定的描述符从文件中删除

基于select的IO复用模型的单进程执行可以为多个客户端服务,这样可以减少创建线程所需要的CPU时间片或内存资源的开销;并且几乎所有的平台上都支持select(),具有良好的跨平台性。
接下来说说缺点:①每次调用select()都需要把fd集合从用户态拷贝到内核态,之后内核需要遍历所有传递进来的fd,这是如果客户端fd很多时会导致系统的开销很大。②单个进程能够监视的文件描述符的数量存在最大限制,在Linux上一般为1024,可以通过setrlimit()函数、修改宏定义甚至重新编译内核等方式来提升这一限制,但是提升了文件描述符数量之后又会引发问题①导致效率低下。

2.poll()函数实现多路复用

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

下面来介绍以下poll()函数的原型:

#include <poll.h>int poll(struct pollfd *fds,nfds_t nfds,int timeout);参数说明:
第一个参数类型是结构体 pollfd
struct pollfd
{int		fd;		//文件描述符short	events	//等待的事件short	revents	//实际发生了的事件
};
第一个参数用来指向该结构体类型的数组,每一个pollfd结构体指定了一个被监视的文件描述符,指示poll()监视多个文件描述符,每个结构体的events域是监视该文件描述符的事件掩码,
由用户来设置这个域。revents域是文件描述符的操作结果事件掩码,events域中请求的任何事件都可能在revents域中返回。第二个参数nfds指定数组中监听的元素个数第三个参数timeout指定等待的毫秒数,无论IO是否准备好,poll都会返回。timeoout指定为负数值表示无线超时,使poll()一直挂起直到一个指定事件发生;timeout为0表示poll调用立即返回并列出准备好I/O的文件描述符,但并不等待其它的事件。也即是,该文件描述符的IO一旦准备好就直接选举出来。

3.epoll()函数实现多路复用

epoll是Linux内核为处理
大批量文件描述符而对poll做了改进的,相当于是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。有一点原因是在获取事件的时候,它不需要遍历整个被监听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入ready队列的描述符集合就行了。
.
此外还有一点就是epoll处理提供select/poll那种IO事件的水平触发外,还提供了边缘触发,这一点使得用户空间程序有可能缓存IO状态,减少epoll_wait/epoll_pwait的调用,提高应用程序效率。

epoll的设计和实现与select完全不同,epoll通过在Linux内核中申请一个简易的文件系统,把原先的select/poll调用分成了3个部分:

  1. 调用epoll_create()建立一个epoll对象(在epoll文件系统中为这个句柄对象分配资源)
  2. 调用epoll_ctl向epoll对象中添加这100万个连接的套接字
  3. 调用epoll_wait收集发生的事件的连接

这三个函数的说明如下:

#include <sys/epoll.h>
int epoll_create(int size)系统调用epoll_create()创建了一个新的epoll实例,其对应的兴趣列表初始化为空。若成功创建返回文件描述符,
若创建出错返回-1,参数size指定了我们想要通过epoll实例来检查的文件描述符的个数,该参数并不是一个上限,而是告诉内核应该如何为内部数据结构划分初始大小。从Linux2.6.8依赖,size参数被忽略不用int epoll_ctl(int epfd,int op,int fd,struct epoll_event  *ev)该函数能够修改由文件描述符epfd所代表的epoll实例中的兴趣列表,若成功就返回0,若失败就返回-1;
参数说明:
第一个参数epfd是epoll_create()的返回值;
第二个参数op是用来指定需要执行的操作,可以是EPOLL_CTL_ADD(将描述符fd添加到epoll实例中的兴趣列表中去)EPOLL_CTL_MOD(修改描述符上设定的事件)EPOLL_CTL_DEL(将文件描述符从兴趣列表中移除)
第三个参数指明了要修改兴趣列表中的哪一个文件描述符的设定
第四个参数ev是指向结构体epoll_event的指针第四个参数的结构体如下:
struct epoll_event
{uint32_t	events;		//epoll eventsepoll_data_t data;		//user data
};
typedef union epoll_data
{void 		*ptr;int			fd;uint32_t	u32;uint64_t	u64;
}eppoll_data_t;参数ev为文件描述符fd所作的设置- events字段是一个位掩码,它指定了我们为待检查的描述符fd上所感兴趣的事件集合;- data字段是一个联合体,当描述符fd稍后称为就绪态时,联合成员可用来指定传回给调用进程的信息int epoll_wait(int epfd,struct epoll_event *evlist,int maxevents,int timeout);系统调用epoll_wait()返回实例中处于就绪态的文件描述符的信息,数组evlist中的元素个数,如果在timeout超时间间隔内没有任何文件描述符处于就绪态的话就返回0,出错返回-1.参数说明:
第一个参数是epoll_create()的返回值
第二个参数所指向的结构体中返回的是有关就绪态文件描述符的信息,数组evlist的空间由调度者负责申请;
第四个参数是timeout用来确定epoll_wait()的阻塞行为
1. -1:调用将一直阻塞,直到兴趣列表中的文件描述有事件产生或捕捉到一个信号为止
2.  0:执行一次非阻塞式的检查,看兴趣列表中的描述符产生了哪个事件
3.  >0:调用将阻塞至多timeout毫秒,直到文件描述符上有事件发生,或者直到捕捉到 一个信号为止

数组evlist中,每个元素返回的都是单个就绪态文件描述符的信息。events字段返回了在该描述符上已经发生的事件掩码。
data字段返回的是我们在描述符上使用epoll_ctl()注册感兴趣的事件时在ev.data中所指定的值。注意,data字段是唯一可获知同
这个事件相关的文件描述符的途径。因此,当我们调用epoll_ctl()将文件描述符添加到感兴趣列表中时,应该要么将ev.date.fd设
为文件描述符号,要么将ev.date.ptr设为指向包含文件描述符号的结构体。
当我们调用epoll_ctl()时可以在ev.events中指定的位掩码以及由epoll_wait()返回的evlist[].events中的值如下所示:

在这里插入图片描述

默认情况下,一旦通过epoll_ctl()的EPOLL_CTL_ADD操作将文件描述符添加到epoll实例的兴趣列表中后,它会保持激活状态
(即,之后对epoll_wait()的调用会在描述符处于就绪态时通知我们)直到我们显示地通过epoll_ctl()的EPOLL_CTL_DEL操作将
其从列表中移除。如果我们希望在某个特定的文件描述符上只得到一次通知,那么可以在传给epoll_ctl()的ev.events中指定
EPOLLONESHOT标志。如果指定了这个标志,那么在下一个epoll_wait()调用通知我们对应的文件描述符处于就绪态之后,这
个描述符就会在兴趣列表中被标记为非激活态,之后的epoll_wait()调用都不会再通知我们有关这个描述符的状态了。如果需
要,我们可以稍后用过调用epoll_ctl()的EPOLL_CTL_MOD操作重新激活对这个文件描述符的检查。


总结

下一篇更新实现的具体代码


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

相关文章

多路复用

讲多路复用先我觉得有必要讲一下什么是阻塞IO、非阻塞IO、同步IO、异步IO这几个东西&#xff1b;linux的五种IO模型&#xff1a; 1)阻塞I/O&#xff08;blocking I/O&#xff09; 2)非阻塞I/O&#xff08;nonblocking I/O&#xff09; 3) I/O复用(select和poll)&#xff08;…

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…