I/O多路复用之select

I/O多路复用:

I/O多路复用是指内核一旦发现进程指定的一个或者多个IO条件准备就绪后,它就通知该进程的应用程序,应用程序就可以马上完成响应的I/O操作,而不需要等待系统完成I/O操作,这样大大提高了效率。I/O多路复用实质上是实现了单线程来处理并发请求。
系统为我们提供了多个函数来实现多路复用输入/输出模型,例如:select,poll,epoll,这里我们先讲select模型。

一、select工作原理:

select系统调用是为了让我们监控程序中多个文件句柄的变化的,程序会停在select这里等待,直到被监视的文件句柄有一个或者多个发生了变换。(这里我们说的文件句柄就理解为文件描述符,是一个一个的整数)也就是说,select是可以同时等待多个文件描述符就绪的,要么是读事件就绪,要么是写事件就绪。
select在内核中的执行流如图所示:
这里写图片描述

简单来说:select将所有fd放入一个集合中,不停轮询遍历该集合,并将未就绪的fd踢出集合,系统可以通过集合来调动就绪fd,只要fd就绪就会被放入集合处理,可以等价于select管理了所有的fd。

二、select函数理解:

函数原型:

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

参数解释:
先来了解两个结构体:
struct fd_set结构体:
可以理解为一个集合,并且以位图形式表示,这个集合中存放的是文件描述符(文件句柄),这可以是我们所说的普通意义的文件。UNIX下一切皆文件,所以socket就是一个文件,socket句柄就是一个文件描述符。与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(int fd, fd_set* set);//清除集合

timeval结构体:

struct timeval:
{
     long tv_sec;/*second*/
     long tv_usec;/*microsecond*/
}

timeval结构体用来设置select()的等待时间,如果在这段时间内监听的文件描述符没有事件发生则返回0。该结构体有以下三种可能:
(1)永远等待下去:仅在有一个文件描述符准备好I/O后才返回,因此可以将timeout设置为空指针
(2)等待一段固定时间:在有一个文件描述符准备好I/O时返回,但是不超过由该参数所指向的timeval结构体中指定的秒数和毫秒数
(3)不等待:检查文件描述符后立即返回,称为轮询(polling)。因此,该参数指向的timeval结构体中的定时器的值必为0。
在前两种情况下,如果进程捕获了一个信号并从信号处理程序返回,那么等待一般会被中断。

  • timeout 的设置:

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

  • nfds:

需要监视的最大文件描述符值+1,上边说过fd_set中存放的是文件描述符,它其实是告诉操作系统去监控的文件描述符的集合大小,但它是从0开始表示的,因此这里的文件描述符的个数就为最大值+1。

  • 三个流集合:

fd_set* readfds:读流集合,希望从这些描述符中读内容
fd_set* writefds:写流集合,希望向这些描述符中写内容
fd_set* exceptfds:异常流集合,中间过程发送了异常。
这三个参数指定我们要让内核测试读、写、异常条件的文件描述符。如果对某一个的条件不感兴趣,就把它设置为空指针。

函数的返回值:
执行成功:返回文件描述符状态已经改变的个数;
返回0表示:在描述词状态改变之前timeout已经超时,没有返回;
返回-1:发生错误,错误原因存于errno,此时参数rdset,wrset,exset变成不可预测的值。
错误值可能为:
EBADF 文件描述词为无效的或该文件已关闭
EINTR 此调用被信号所中断
EINVAL 参数n 为负值。
ENOMEM 核心内存不足

三、select模型的理解

  1. 理解select模型的关键是理解fd_set,由于fd_set在底层是以位图的形式存放的,因此select模型可以监控的文件描述的的多或少取决于sizeof(fd_set)的值。
  2. 将fd加入select监控集的同时,需要使用一个数据结构array保存放到select监控集中的fd,一是用于select返回后,array作为源数据和fd_set进行FD_ISSET判断;二是select返回后会把以前加入的但并无事件发生的fd清空,则每次开始select前都要重新从array取得fd的值并逐一加入,扫描array的同时取得fd最大值maxfd,用于select的第一个参数。可见select模型必须在select之前先遍历array(加入fd,取得maxfd),select返回后循环array(FD_ISSET判断是否有时间发生)

四、对于select的总结

  1. 每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大。
  2. 同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大。
  3. select支持的文件描述符数量很少,默认是1024。

五、代码实现select模型

代码上传至github:https://github.com/lybb/Linux/blob/master/select/select.c

关于测试:可以使用telnet工具测试…..

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值