I/O 多路转接

I/O 多路转接,为了使用这种技术,先构造一张我们感兴趣的描述符的列表,然后调用一个函数,直到这些描述符中的一个已准备好进行 I/O 时,该函数才返回。poll、pselect 和 select 这3个函数使得我们能够指向 I/O 多路转接。

函数 select

传给 select 的参数告诉内核:

  • 我们所关心的描述符
  • 对于每个描述符我们所关心的条件(是否想从一个给定的描述符读,是否想写一个给定的描述符,是否关心一个给定描述符的异常条件)
  • 愿意等待多长时间(可以永远等待、等待一个固定的时间或者根本不等待)

从 select 返回时,内核告诉我们:

  • 已准备好的描述符的总数量;
  • 对于读、写或异常这 3 个条件中的每一个,哪些描述符已准备好。

使用这种返回信息,就可调用相应的 I/O 函数(一般是 read 或 write),并且确知该函数不会阻塞。

#include 

int select(int maxfdp1, fd_set *restrict readfds, fd_set *restrict wirtefds, fd_set *restrict exceptfds, struct timeval *restrict tvptr);
//返回值:准备就绪的描述符数目;若超时,返回0,若出错,返回-1

先说明最后一个参数,它指定愿意等待的时间长度,单位是秒和微妙。有以下 3 种情况:

  • tvptr == NULL    永远等待。如果捕捉到一个信号则中断此无限期等待。当所指定的描述符中的一个已准备好或捕捉到一个信号则返回。如果捕捉到一个信号,则 select 返回 -1,errno 设置为 EINTR。
  • tvpr -> tv_sec == 0 && tvptr -> tv_usec == 0    根本不等待。测试所有指定的描述符并立即返回。
  • tvptr -> tv_sec != 0 || tvptr -> tv_usec != 0     等待指定的秒数和微妙数。当指定的描述符之一已准备好,或当指定的时间值已经超过时立即返回。如果在超时到期时还没有一个描述符准备好,则返回值是 0.

中间 3 个参数 readfds、writefds 和 exceptfds 是指向描述符集的指针。这 3 个描述符集说明了我们关心的可读、可写或处于异常条件的描述符集合。每个描述符集存储在一个 fd_set 数据类型中。

对于 fd_set 数据类型,唯一可以进行的处理是:分配一个这种类型的变量,将这种类型的一个变量值赋给同类型的另一个变量,或对这种类型的变量使用下列 4 个函数的一个:

#include 

int FD_ISSET(int fd, fd_set *fdset);
//返回值:若 fd 在描述符集中,返回非 0 值;否则,返回0

void FD_CLR(int fd, fd_set *fdset);

void FD_SET(int fd, fd_set *fdset);

void FD_ZERO(fd_set *fdset); 

这些接口可实现为宏或函数。调用 FD_ZERO 将一个 fd_set 变量的所有位设置为 0.要开启描述符集中的一位,可以调用 FD_SET。调用 FD_CLR 可以清除一位。最后,调用 FD_ISSET 测试描述符集中的一个指定位是否已打开。

在声明了一个描述符集之后,必须用 FD_ZERO 将这个描述符集置为 0,然后在其中设置我们关心的各个描述符的位。

select 的中间 3 个参数中的任意一个可以是空指针,这表示对相应条件并不关心。

select 第一个参数 maxfdp1 的意思是“最大文件描述符编号加 1”。考虑所有 3 个描述符集,在 3 个描述符集中找出最大描述符编号值,然后加 1,这就是第一个参数值。也可将第一个参数设置为 FD_SETSIZE 这是 中的一个常量,它指定最大描述符数(经常是 1024)。

select 有 3 个可能的返回值

  1. 返回值 -1 表示出错。这是可能发送的,例如,在所指定的描述符一个都没有准备好时捕捉到一个信号。在此种情况下,一个描述符集都不修改。
  2. 返回值 0 表示没有描述符准备好。若指定的描述符一个都没准备好,指定的时间就过了,那么就会发送这种情况。此时,所有描述符集都会置 0。
  3. 一个正返回值说明了已经准备好的描述符数。该值是 3 个描述符集中已准备好的描述符数之和,所以如果同一描述符已准备好读和写,那么在返回值中会对其计两次数。在这种情况下,3 个描述符集中仍旧打开的位对应于已准备好的描述符。

对于“准备好”的含义要作一些更具体的说明

  • 若对读集(readfds)中的一个描述符进行的 read 操作不会阻塞,则认为此描述符是准备好的。
  • 若对写集(writefds)中的一个描述符进行的 write 操作不会阻塞,则认为此描述符是准备好的。
  • 若对异常条件集(exceptfds)中的一个描述符有一个未决异常条件,则认为此描述符是准备好的。现在异常条件包括:在网络连接上到达带外的数据,或者在处于数据包模式的伪终端上发送了某些条件。
  • 对于读、写和异常条件,普通文件的文件描述符总是准备好的。

函数 pselect

#include 

int pselect(int maxfdp1, fd_set *restrict readfds, fd_set *restrict writefds, fd_set *restrict exceptfds, const struct timespec *restrict tsptr, const sigset_t *restrict sigmask);
//返回值:准备就绪的描述符数目;若超时,返回i0;若出错,返回 -1

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

  • select 的超时值用 timeval 结构指定,但 pselect 使用 timespec 结构。timespec 结构以秒和纳秒表示超时值,而非秒和微妙。
  • pselect 的超时值被声明位 const,这保证了调用 pselect 不会改变此值。
  • pselect 可使用可选信号屏蔽字。若 sigmask 为 NULL,那么在与信号有关的方面,pselect 的允许状况和 select 相同。否则,sigmask 指向一信号屏蔽字,在调用 pselect 时,以原子操作的方式安装该信号屏蔽字。在返回时,恢复以前的信号屏蔽字。

函数 poll

poll 函数类似于 select,但是程序员接口有所不同。poll 函数可用于任何类型的文件描述符。

#include 

int poll(struct pollfd fdarray[], nfds_t nfds, int timeout);
//返回值:准备就绪的描述符数目;若超时,返回0;若出错,返回-1

struct poofd{
    int fd;
    short events;
    short revents;
};

与 select 不同,poll 不是为每个条件(可读性、可写性和异常条件)构造一个描述符集,二十构造一个 pollfd 结构的数组,每个数组元素指定一个描述符编号以及我们对该描述符感兴趣的条件。

fdarray 数组中的元素数由 nfds 指定。

应将每个数组元素的 events 成员设置为下表中所示值得一个或几个,通过这些值告诉内核我们关系的是每个描述符的那些事件。返回时,revents 成员由内核设置,用于说明每个描述符发送了哪些事件。

标志名 说明
POLLIN                可以不阻塞地读高优先级数据以外的数据
POLLRDNORM         可以不阻塞地读普通数据
POLLRDBAND 可以不阻塞地读优先级数据
POLLPRI 可以不阻塞地读高优先级数据
POLLOUT 可以不阻塞地写普通数据
POLLWRNORM 与 POLLOUT 相同
POLLWRBAND 可以不阻塞地写优先级数据
POLLERR         已出错
POLLHUP 已挂断
POLLNVAL 描述符没有引用一个打开文件

上表中的前 4 行测试的是可读性,接下来的 3 行测试的是可写性,最后 3 行测试的是异常条件。最后 3 行是由内核在返回时设置的。即使在 events 字段中没有指定这 3 个值,如果相应条件发生,在 revents 中也会返回它们。

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

poll 的最后一个参数指定的是我们愿意等多长时间。有 3 种不同的情形:

  • timeout == -1        永远等待。当所指定的描述符中的一个已准备好,或捕捉到一个信号时返回。如果捕捉到一个信号,则 poll 返回 -1,errno 设置为 EINTR。
  • timeout == 0         不等待。测试所有描述符并立即返回。
  • timeout > 0            等待 timeout 毫秒。当指定的描述符之一已准备好,或 timeout 到期时立即返回。如果 timeout 到期时还没有一个描述符准备好,则返回值是 0。

你可能感兴趣的:(UNIX笔记专区,linux,unix)