I/O复用

Posted guhowo

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了I/O复用相关的知识,希望对你有一定的参考价值。

概念

内核一旦发现进程指定的一个或者多个I/O条件就绪(也就是说输入已准备好被读取,或者描述符已能承受更多的输出),它就通知进程。这个能力称为I/O复用(I/O multiplexing)。

使用场景

当处理多个多个文件描述符或者监听多个socket时,必须使用I/O复用。

如果一个服务器要同时处理TCP和UDP,一般要使用I/O复用。

I/O模型

阻塞式I/O模型

非阻塞式I/O模型

I/O复用(select和poll)

信号驱动式I/O模型

异步I/O模型

 

什么叫数据准备好?

数据准备分两个阶段:1、等待网络数据到达,数据被复制到内核缓冲区,即内核空间;2、把内核数据复制到用户进程缓冲区,即用户空间

 

阻塞式I/O模型

以recvfrom系统调用为例,用户调用recvfrom,然后切换到内核态,内核等待数据到来,此时有可能阻塞(如果网络数据还没到达的话)。然后网络数据到达后将内核缓冲区数据复制到用户空间,返回成功。这时候从内核态再切换到用户态,处理数据报。

类似这种进程一直等待数据到来,从系统调用开始到它返回,整段时间是被阻塞的。成功返回后开始处理数据报。 (进程等待的时候是被挂起休眠么?还是在干嘛?

非阻塞式I/O模型

把一个套接字或FD设置为非阻塞,就是通知内核,当数据没准备好时,返回一个错误,而不是一直等待。 如果有数据准备好,则复制到用户空间并返回成功指示。

常见的用法是用一个循环去不断的调用recvfrom,去查看属否有数据准备好,这种方式被称为轮询(polling)。应用进程持续轮询内核,这么做消耗大量CPU资源。所以这种模型很少见。

I/O复用模型

有了I/O复用,如select,poll,我们就可以调用select或者poll,阻塞在这两个系统调用上,而不是阻塞在真正的I/O系统调用上。

我们调用select,等待fd或者socket变为可读。当select返回套接字可读这一条件时,我们再调用recvfrom把所有可读数据从内核复制到用户空间。 

信号驱动式I/O模型

 用的非常少,项目中目前还没有见过。略。

异步I/O模型

Linux的AIO目前还不成熟,故不做深入。 

同步、异步、阻塞、非阻塞

 同步和异步关注的是事件就绪时消息通知的方式。由调用者主动询问的方式是同步,由被调用方(往往是内核)主动通知调用方任务完成的方式是异步调用。

阻塞和非阻塞关注的是接口调用后等待数据返回时的状态。被挂起无法执行其他操作的是阻塞,可以理解返回去完成其他认识的是非阻塞模型。

select函数

该函数让进程指示内核等待多个socket,只有在任何一个socket准备好了(可读,可写)或者等待超时后才唤醒该进程。

函数原型如下:

1 #include <sys/select.h>
2 #include <sys/time.h>
3 
4 int select(int maxfdp1,fd_set *readset,fd_set *writeset,fd_set *exceptset,const struct timeval *timeout)
返回值:就绪描述符的数目,超时返回0,出错返回-1

 函数参数介绍如下:

(1)第一个参数maxfdp1指定待测试的描述符的个数,其值为最大待测试的fd+1。(因此把该参数命名为maxfdp1),描述字0、1、2...maxfdp1-1均将被测试。因为文件描述符是从0开始的。

(2)中间的三个参数readset、writeset和exceptset指定我们要让内核测试读、写和异常条件的描述字。如果对某一个的条件不感兴趣,就可以把它设为空指针。struct fd_set可理解为一个字符集,可用以下四个宏进行设置:

   void FD_ZERO(fd_set *fdset);           //清空集合

          void FD_SET(int fd, fd_set *fdset);   //将一个给定的文件描述符加入集合之中

          void FD_CLR(int fd, fd_set *fdset);   //将一个给定的文件描述符从集合中删除

          int FD_ISSET(int fd, fd_set *fdset);   // 检查集合中指定的文件描述符是否可以读写 

 (3)struct timeval设定等待时间,告知内核等待所指定描述符最多多长时间,秒和微秒组成。

struct timeval {
    long tv_sec;       //seconds
    long tv_usec;      //micro seconds    
}

timeval参数有三种可能:

第一种是空指针,表示无限等待下去,此时select为阻塞模式;

第二种为一个有效值,表示等待时间。在等待时间内描述符就绪则返回描述符,否则返回0;

第三种是设置为0,根本不等待直接返回,这称为轮询。

 

以上是关于I/O复用的主要内容,如果未能解决你的问题,请参考以下文章

Redis 和 I/O 多路复用

Redis 和 I/O 多路复用

I/O多路复用——epoll函数

I/O复用

I/O复用之 select和epoll

I/O复用之 select和epoll