I/O多路转接 --- UNIX环境高级编程

时间:2021-08-24 09:57:47

I/O多路转接技术:先构造一张有关描述符的列表,然后调用一个函数,知道这些描述符中的一个已准备好进行I/O时,给函数才返回。在返回时,它告诉进程哪些描述符已准备好可以进行I/O。

poll、select、pselect这三个函数使我们能够执行I/O多路转接。

1.select和pselect函数

I/O多路转接的标准函数,还有一个标准函数poll函数,就这两个
       该函数主要用于终端I/O和网络I/O,但它对其他描述符同样起作用。

#include <sys/selecet.h>
     int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

传向select的参数告诉内核,我们所关心的描述符;对于每个描述符我们所关心的状态;愿意等待多长时间;返回值告诉我们,已准备好的的描述符的数量;对于读、写、异常者三个状态中的每一个,哪些描述符已准备好;所以,只用这些返回值,就可调用相应的I/O函数(一般是read和write),并且确知该函数不会阻塞。

参数意义:
    timeout:它指等待的时间:struct timeval{ long tv_sec; long tv_usec; };
             timeout = NULL; 永远等待。如果捕捉到一个信号则终端此无限期等待。当所指定的描述符中的一个已准备好或捕捉到一个信号则返回。如果捕捉到信号一个信号,则返回-1,errno设置为EINTR。
            timeout->tv_sec == 0 && timeout->tv_usec == 0; 完全不等待。测试所有描述符并立即返回。
            timeout->tv_sec != 0 && timeout->tv_usec != 0; 等待指定的秒数和微秒数。当指定的描述符之一已准备好,或当指定的时间值已经超过时立即返回。超时但没有一个描述符准备好则返回0,

中间3个参数readfds、writefds、exceptfds是指向描述符集的指针。这三个描述符集说明了我们关心的可读、可写、=或异常条件的哥哥描述符。每个描述符集存放在一个fs_set数据类型中。这种数据类型为每一可能的描述符保持了一位。

#include <sys/select.h>
          void FD_CLR(int fd, fd_set *set); 将指定位清掉
          int  FD_ISSET(int fd, fd_set *set); 测试仪指定位是否设置;若fd在集中则返回非0,否则返回0;
                            从select返回时,用其测试该集中的一个给定位是否仍旧设置
          void FD_SET(int fd, fd_set *set); 设置一个指定位
          void FD_ZERO(fd_set *set); 清楚所有位,在声明一个描述符集后,必须先清楚所有位。

中间的三个参数中任意一个或全部都可是NULL,表示对相应的状态不关心,如果都为空指针,则select提供了较sleep更精确的定时。
  
  第一个参数nfds,“最大描述符加1”。在3个描述符集中,找出最大描述符编号值,然后加1,就是第一个参数的值。第一个参数实际是要检查的描述符的个数

返回值:
                  -1。表示出错,例如在所指定的描述符都没有准备好时捕捉到一个信号,在此情况下,将不修改其中任何描述符集。
                 0。表示没有描述符准备好。指定的描述符都没有准备好,指定的时间已经超过,则发生这种情况。此时,所有的描述符皆被清0。
                 正返回值。表示已准备好的描述符的个数。是3个描述集中已准备好的描述符数之和,所以同一个描述符已准备好好读和写,返回值中将其记为2.

对于“已准备好”的意思做一些具体说明:
       若对读集(readfds)中的一个描述符的read操作将不会阻塞,则此描述符准备好了
       若对写集(writefds)中的一个描述符的write操作将不会阻塞,则此描述符准备好了
       若异常状态急(exceptfds)中个一个描述符有一个未决异常状态,则此描述符准备好了

对于读、写、异常状态,普通文件描述符总是返回准备好。
           一个描述符阻塞与否并不影响select是否阻塞。
          如果在一个描述符上碰到了文件结尾处,则select认为该描述符是可读的,然后调用read,返回0。不要认为到达结尾处,select会只是一个异常状态。

POSIX.1也定义了一个select的变体,它被称为pselect

#include <sys/select.h>

int pselect(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, const struct timespec *timeout, const sigset_t *sigmask);

除了一下几点外,pselect与select相同:

I/O多路转接 --- UNIX环境高级编程

2.poll函数(多路I/O转接的标准函数之一,还有一个select)
 与select函数类似,返回值与select相同

#include <poll.h>
 int poll(struct pollfd *fds, nfds_t nfds, int timeout);
 与select不同,poll不是为每个状态(读、写、异常)构造一个描述符集,而是构造一个pollfd结构体数组。每个数组元素指定一个描述符编号以及对其关心的状态。

参数:
  第一个参数是结构体数组。struct pollfd{int fd; short events; short revents; };
   每个数组元素的events成员设置的值见表,通过这些值告诉内核我们对描述符关心的是什么。返回时,内核设置revents成员,说明对该描述符已经发生了什么
   【注意】poll函数没有更改events成员,这与select不同,select修改其参数以指示哪一个描述符已准备好。
    I/O多路转接 --- UNIX环境高级编程

当一个描述符被挂断(POLLHUP)后,就不能再写向该描述符。但仍可能从该描述符读取到数据

第二个参数,nfds。fds数组中的元素数由nfds说明

第三个参数timeout。
            -1.永远等待。返回值情况与select相同
            0.不等待。测试所有描述符并立即返回。
            >0。等待timeout毫秒。返回值情况与select相同。
              【注】如果系统不提供毫秒级分辨率,则timeout值取整到最近的支持值。

文件结束与挂断之间的区别。
           如果正从终端输入数据,并键入文件结束字符,POLLIN打开,于是就可读文件结束指示(read返回0)。POLLHUP在revents中没有打开。
           如果正在读调制解调器,并且电话线已挂断,则在revents中将接到POLLHUP通知。

与select一样。不论一个描述符是否阻塞,都不影响poll是否阻塞。