I/O多路复用之select()系统调用
为什么要使用I/O多路复用
应用程序常常需要在多于一个文件描述符上阻塞:例如响应键盘输入(stdin)、进程间通信以及同时操作多个文件。
在不使用线程(怎么理解线程的存在呢?我么可以举一个例子。当我们运行qq这个进程的时候,是可以执行不同的任务的。例如,我们可以在使用qq发送消息的同时来收发文件。而这两个不同的任务就是利用线程来完成的),尤其是独立处理每一个文件的情况下,进程无法在多个文件描述符上同时阻塞。如果文件都处于准备好被读写的状态,同时操作多个文件描述符是没有问题的。但是,一旦在该过程中出现一个未准备好的文件描述符(就是说,如果一个read()
被调用,但没有读入数据),则这个进程将会阻塞,不能再操作其他文件。可能阻塞只有几秒钟,但是应用无响应也会造成不好的用户体验。然而,如果文件描述符始终没有任何可用数据,就可能一直阻塞下去。
如果使用非阻塞I/O,应用可以发起I/O请求并返回一个特别的错误,从而避免阻塞。但是,从两个方面来讲,这种方法效率较差。首先,进程需要以某种不确定的方式不断发起I/O操作,直到某个打开的文件描述符准备好进行I/O。其次,如果程序可以睡眠的话将更加有效,可以让处理器进行其他工作,直到一个或更多文件描述符可以进行I/O时再唤醒。
三种I/O多路复用方案
I/O多路复用允许应用在多个文件描述符上同时阻塞,并在其中某个可以读写时收到通知。这时I/O多路复用就成了应用的关键所在。
I/O多路复用的设计遵循一下原则:
1、I/O多路复用:当任何文件描述符准备好I/O时告诉我
2、在一个或更多文件描述符就绪前始终处于睡眠状态
3、唤醒:哪个准备好了?
4、在不阻塞的情况下处理所有I/O就绪的文件描述符
5、返回第一步,重新开始
Linux提供了三种I/O多路复用方案:select
、poll
、epoll
。
先来说说select()
select()系统调用的声明
|
|
其中fd_set
是select
机制中提供的一种数据结构,实际上是一long
类型的数组,每一个数组元素都能与一打开的文件句柄(不仅是socket
句柄,还是其他文件或命名管道或设备句柄)建立联系,建立联系的工作由程序员完成,当调用select()
时,由内核根据IO状态修改fd_set
的内容,由此来通知执行了select()
的进程哪一个socket
或文件发生了可读或可写事件
监测的文件描述符可以分为三类,分别等待不同的事件。监测readfds
集合中的文件描述符,确认其中是否有可读数据(也就是说,确认好了的文件描述符的读操作可以无阻塞的完成)。监测writefds
集合中的文件描述符,确认其中是否有一个写操作可以不阻塞地完成。监测exceptfds
中的文件描述符,确认其中是否有出现异常发生或者出现带外数据(这种情况只适用于套接字)。指定的集合可能为空(NULL)。相应的,select()
则不对此类事件进行监测。
成功返回时,每个集合只包含对应类型的I/O就绪的文件描述符。举个例子,readfds
集合中有两个文件描述符:7和9.当调用返回时,如果7还在集合中,该文件描述符就准备好进行无阻塞I/O了。如果9已不在集合中,它可能在被读取时会发生阻塞。出现错误返回-1。
第一个参数n,等于所有集合中文件描述符的最大值加1。这样,select()
的调用者需要找到最大的文件描述符值,并将其加1后传给第一个参数。
timeout
参数是一个指向timeval
结构体的指针,定义如下:
|
|
如果这个参数不是NULL,即使此时没有文件描述符处于I/O就绪状态,select()
调用也将在tv_sec秒、tv_usec微秒
后返回。即 select在timeout时间内阻塞,超时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回。
如果时限中的两个值都是0,调用会立即返回,并报告调用时所有事件对应的文件描述符均不可用,且不等待任何后续事件。
若将NULL以形参传入,即不传入时间结构,就是将select置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止
举个select()的小例子
|
|