【IO多路转接】Select,Poll

五种IO模型

在介绍Select和Poll之前简单介绍一下IO

IO,即输入和输出,通过调用recv/read/send/write等系统调用将数据从缓冲区拷贝到另一个缓冲区,所以在IO的过程中,缓冲区是否为满以及数据是否就绪是非常重要的两个条件,因此在拷贝之前,OS通常需要等待资源就绪(如调用scanf时,OS需要等待我们向控制台中输入才能实现拷贝)
因此,IO的本质:等待 + 拷贝

其中分为五种IO模型:

  • 阻塞IO

在内核将数据准备好之前,系统将会一直等待

  • 非阻塞IO

如果内核还未将数据准备好,系统调用仍然会直接返回,并且返回EWOULDBLOCK错误码

  • 信号驱动IO

内核将数据准备好之后,使用SIGIO信号通知进程进行IO操作

  • IO多路转接(多路复用)

与阻塞IO类似,但不同点在于,IO多路转接可以同时等待多个fd的就绪状态

  • 异步IO

在内核将数据拷贝完成之后通知进程(而信号驱动是通知进程何时拷贝数据)

我们在这里主要介绍的是IO多路转接这一模型中的内容

Select

系统提供select函数来实现多路转接的IO模型

Select函数

#include<sys/select.h>

int select(int nfds, fd_set *readfds, fd_set *writefds,
                  fd_set *exceptfds, struct timeval *timeout);

参数解释

  • 参数nfds是需要监视所有文件描述符中数值最大的值+1
  • readfds,writefds,exceptfds分别是可读文件描述符集合,可写文件描述符集合以及异常文件描述符集合
  • timeout参数为结构体timeval,用来设置select()的等待时间

关于timeout取值

  • NULL:表示select()没有timeout,则select将被一直阻塞,直到有文件描述符上发生了事件
  • 0:只检测文件描述符的状态并立即返回,并不等待外部事件发生
  • 特定时间值:表示select等待特定时间值,如果此时间内没有外部事件发生,则select将超时返回

关于fd_set结构体

typedef struct
  {
    /* XPG4.2 requires this member name.  Otherwise avoid the name
       from the global namespace.  */
#ifdef __USE_XOPEN
    __fd_mask fds_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;
typedef long int __fd_mask;

实际上,fd_set是一个数组,表示一种位图结构,通过设置位图中对应位置的值来表示监视的文件描述符状态的变化

例如假设listensock的fd为3,当连接到来时,select将检测到读事件就绪,将readfds[3]上的0设置为1

提供了一系列操作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的全部位

函数返回值

  • 成功则返回文件描述符状态已改变的个数
  • 返回0表示select等待超过了timeout时间
  • 错误返回-1

Select的特点

  1. 可监控文件描述符的个数由fd_set的大小决定,例如,如果sizeof(fd_set) = 512,则可监控文件描述符的个数为512*8=4096个(1字节=8bit)
  2. 在使用select时,通常需要一个数组来保存需要监控的fd
    • 在select返回之后,数组中的fd可以作为源数据与FD_ISSET进行判断
    • select返回之后会清空没有发生事件的fd,因此每次select之前都需要将数组中的fd重新逐一加入,并且可以扫描数组,得到maxfd并+1作为select的第一个参数

Select常见程序片段

int fd_array[sizeof(fd_set) * 8];
fd_set rfds;	//以读事件为例
int maxfd = fdarray_[0].fd;
for(int i = 0;i < sizeof(fd_set) * 8;++i)
{
	if(fd_array[i]为读事件)
		FD_SET(fd_array[i], &rfds);
	if (maxfd < fdarray_[i].fd)
        maxfd = fdarray_[i].fd;
}
select(maxfd + 1, &rfds, nullptr, nullptr, nullptr);
for(int i = 0;i < sizeof(fd_set) * 8;++i)
{
	if((FD_ISSET(fd_array_[i].fd, &rfds)) && (fd_array[i]为读事件))
	{
		//TODO
	}
}

Select的缺点

  • 每次调用select之前都需要手动设置fd集合,从接口使用角度来说很不方便
  • 每次调用select之前都需要将fd集合从用户态拷贝到内核态,fd很多时开销比较大
  • 同时每次调用select都需要在内核遍历传入的fd集合,也会造成开销大的问题
  • select支持的文件描述符个数太少

为了将Select进行优化,因此在Select的基础上实现了Poll

Poll

Poll函数

#include <poll.h>

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

//pollfd结构体
struct pollfd 
{
	int   fd;         /* file descriptor */
	short events;     /* requested events */
	short revents;    /* returned events */
};

参数解释

  • fds是poll监听的pollfd结构体列表,其中包括所监听文件描述符,监听事件以及返回事件
  • nfds表示fds的长度
  • timeout为poll函数超时时间,单位为毫秒(ms)

关于poll中的事件

在这里插入图片描述

在这里插入图片描述

函数返回值

  • 返回值大于0,表示poll监听的文件描述符就绪返回
  • 返回值等于0,表示poll等待超时
  • 返回值小于0,表示等待出错

Poll常见程序片段

struct pollfd fd_array[N];	//也可以动态管理
for (int i = 0; i < N; i++)
{
	fd_array[i].fd = defaultfd;
	fd_array[i].events = defaultevent;
	fd_array[i].revents = defaultevent;
}
poll(fd_array, N, timeout);
for (int i = 0; i < N; i++)
{
	if(fd_array[i].revents & POLLIN)//POLLIN表示读事件
	{
		//TODO
	}
}

Poll的优点

  • poll不需要通过fdset位图来设置文件描述符状态,只需要pollfd指针实现即可
  • pollfd中包括了文件描述符以及事件类型,封装性更好
  • poll没有最大数量限制

Poll的缺点

  • 同select一样,poll需要轮询pollfd以获取文件描述符的准备状态
  • 每次调用poll都需要将大量pollfd从用户态拷贝到内核态,增大了开销

因此,就有了另一种更优化方案 — Epoll。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值