Linux--IO多路复用(select,poll,epoll)

IO多路复用——select,poll,epoll

IO多路复用是一种操作系统技术,旨在提高系统处理多个输入输出操作的性能和资源利用率。与传统的多线程或多进程模型相比,IO多路复用避免了因阻塞IO而导致的资源浪费和低效率问题。它通过将多个IO操作合并到一个系统调用中,允许程序同时等待多个文件描述符(如sockets、文件句柄等)变为可读或可写状态,然后再执行实际的IO操作。

在IO多路复用的实现中,常用的系统调用包括select()poll()epoll()。这些机制允许程序监视多个描述符,一旦某个描述符就绪(通常是读就绪或写就绪),程序就会被通知进行相应的读写操作。这个过程通常涉及两个阶段:

  1. 等待数据到达:程序等待数据从IO设备传输到内核空间。在这个阶段,IO多路复用的系统调用会阻塞,直到至少有一个描述符准备好进行IO操作。

  2. 数据复制:当一个或多个描述符就绪时,程序负责将数据从内核空间复制到用户空间(进程或线程的缓冲区)。这第二个阶段是实际的读写操作,它在IO多路复用的上下文中是同步的,因为程序需要自己执行数据的读写。

尽管select()poll()epoll()都是同步IO操作,但它们提供了一种有效的方式来处理并发IO,降低了系统开销,并提高了并发处理能力。与此不同,异步IO(AIO)模型进一步简化了IO操作,因为它允许操作系统自动处理数据从内核到用户空间的复制过程,无需程序显式调用读写操作。这意味着在异步IO模型中,读写操作由操作系统在后台完成,从而进一步提高了应用程序的效率和响应性。

a411a9791264d372df052436f83741e8.png

select

概述

  • 系统提供了select函数来实现多路复用输入/输出模型

  • select系统调用是用来让我们的程序监视多个文件描述符的状态变化的

  • 程序会停在select函数等待,直到被监视的文件描述符有一个或者多个发生了状态改变。

c03874c465c6dc86eb3b53af27bd076b.png

函数

int select(int nfds, fd_set *readfds, fd_set *writefds,
    fd_set *exceptfds, struct timeval *timeout);
  • 函数参数:

参数 说明
nfds 是需要监视的最大的文件描述符值+1
readfds 需要检测的可读文件描述符的集合
writefds 需要检测的可写文件描述符的集合
exceptfds 需要检测的异常文件描述符的集合
timeout 当timeout等于NULL:则表示select()没有timeout,select将一直被阻塞,直到某个文件描述符上发生了事件;
当timeout为0:仅检测描述符集合的状态,然后立即返回,并不等待外部事件的发生。
当timeout为特定的时间值:如果在指定的时间段里没有事件发生,select将超时返回。
返回 ——
> 0 返回文件描述词状态已改变的个数
== 0 代表在描述词状态改变前已超过timeout时间,没有返回
< 0 错误原因存于errno,此时参数readfds,writefds, exceptfds和timeout的值变成不可预测,错误值可能为:
EBADF:文件描述词为无效的或该文件已关闭
EINTR:此调用被信号所中断
EINVAL:参数n 为负值
ENOMEM:核心内存不足
  • 其中:可读,可写,异常文件描述符的集合是一个fd_set类型,fd_set是系统提供的位图类型,位图的位置是否是1,表示是否关系该事件。例如:

输入时:假如我们要关心 0 1 2 3 文件描述符
    0000 0000->0000 1111 比特位的位置,表示文件描述符的编号
    比特位的内容 0 or 1 表示是否需要内核关心
输出时:
    0000 0100->此时表示文件描述符的编号
    比特位的内容 0 or 1哪些用户关心的fd 上面的读事件已经就绪了,这里表示2描述符就绪了
  • 系统提供了关于fd_set的接口,便于我们使用位图:

void FD_CLR(int fd, fd_set *set); // 用来清除描述词组set中相关fd 的位
int FD_ISSET(int fd, fd_set *set); // 用来测试描述词组set中相关fd 的位是否为真
void FD_SET(int fd, fd_set *set); // 用来设置描述词组set中相关fd的位
void FD_ZERO(fd_set *set); // 用来清除描述词组set的全部位

执行流程:

  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)阻塞等待,表示最大文件描述符+1是6,监控可读事件,立即返回。

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

优缺点

  • 优点:

  1. 可监控的文件描述符个数取决与sizeof(fd_set)的值。一般大小是1024,但是fd_set的大小可以调整。

  2. 将fd加入select监控集的同时,还要再使用一个数据结构array保存放到select监控集中的fd。①是用于再select 返回后,array作为源数据和fd_set进行FD_ISSET判断。②是select返回后会把以前加入的但并无事件发生的fd清空,则每次开始select前都要重新从array取得fd逐一加入(FD_ZERO最先),扫描array的同时取得fd最大值maxfd,用于select的第一个参数。

缺点:

  1. 每次调用select, 都需要手动设置fd集合, 从接口使用角度来说也非常不便。

  2. 每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大。

  3. 同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大。

  4. select支持的文件描述符数量太小。

实例

#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/select.h>

const static int MAXLINE = 1024;
const static int SERV_PORT = 10001;

int main()
{
    int i , maxi , maxfd, listenfd , connfd , sockfd ;
    /*nready 描述字的数量*/
    int nready ,client[FD_SETSIZE];
    int n ;
    /*创建描述字集合,由于select函数会把未有事件发生的描述字清零,所以我们设置两个集合*/
    fd_set rset , allset;
    char buf[MAXLINE];
    socklen_t clilen;
    struct sockaddr_in cliaddr , servaddr;
    /*创建socket*/
    listenfd = socket(AF_INET , SOCK_STREAM , 0);
    /*定义sockaddr_in*/
    memset(&servaddr , 0 ,sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(SERV_PORT);
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

    bind(listenfd, (struct sockaddr *) & servaddr , sizeof(servaddr));
    listen(listenfd , 100);
    /*listenfd 是第一个描述字*/
    /*最大的描述字,用于select函数的第一个参数*/
    maxfd = listenfd;
    /*client的数量,用于轮询*/
    maxi = -1;
    /*init*/
    for(i=0 ;i<FD_SETSIZE ; i++)
        client[i] = -1;
    FD_ZERO(&allset);
    FD_SET(listenfd, &allset);

    for (;;)
    {
        rset = allset;
        /*只select出用于读的描述字,阻塞无timeout*/
        nre
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Rice嵌入式

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值