UNIX网络编程读书笔记:select函数

灯泡select函数概况:

select函数允许进程指示内核等待多个事件中的任何一个发生,并仅在有一个或多个事件发生或经历一段指定的时间后才唤醒它。

作为一个例子,我们可以调用select,告知内核仅在下列情况发生时才返回:

(1)集合{1,4,5}中的任何描述字准备好读,或

(2)集合{2,7}中的任何描述字准备好写,或

(3)集合{1,4}中的任何描述字有异常条件待处理,或

(4)已经历了10.2秒。

也就是说,我们调用select告知内核对哪些描述字(就读、写或异常条件)感兴趣以及等待多长时间。我们感兴趣的描述字不局限于套接口;任何描述字都可以使用select来测试。

#include <sys/select.h>
#include <sys/time.h>

int select(int maxfdpl, fd_set *readset, fd_set *writeset,
    fd_set *exceptset, const struct timeval *timeout);
返回值:就绪描述字的总数目,0——超时,-1——出错

灯泡select函数参数介绍:

关于此函数的参数的详细介绍可参考http://www.cnblogs.com/nufangrensheng/p/3557584.html。重复内容不再赘述,这里只给出一些补充内容。

timeout参数的const限定词表示它在函数返回时不会被select修改。

但是有些Linux版本会修改这个timeval结构。因此从移植性考虑,我们应该假设该timeval结构在select返回时未被定义,因此每次调用select之前都得对它进行初始化。POSIX规定对该结构使用const限定词。

中间的三个参数readset、writeset和exceptset指定我们要让内核测试读、写和异常条件的描述字。目前支持的异常条件只有两个:

(1)某个套接口的带外数据的到达。

(2)某个已置为分组方式的伪终端存在可从其主端读取的控制状态信息。

如何给这三个参数的每一个指定一个或多个描述字值是一个设计上的问题。select使用描述字集,典型地是一个整数数组,其中每个整数中的每一位对应一个描述字。举例来说,假设使用32位整数,那么该数组的第一个元素对应描述字0-31,第二个元素对应描述字32-63,以此类推。所有这些实现细节都与应用程序无关,它们隐藏在名为fd_set的数据类型和以下四个宏中:

/* fd_set macro */
void FD_ZERO(fd_set *fdset);           /* clear all bits in fdset */
void FD_SET(int fd, fd_set *fdset);    /* turn on the bit for fd in fdset */
void FD_CLR(int fd, fd_set *fdset);    /* turn off the bit for fd in fdset */
int FD_ISSET(int fd, fd_set *fdset);   /* is the bit for fd on in fdset?*/

描述字集的初始化非常重要,因为作为自动变量分配的一个描述符字集如果没有初始化,那么可能发生不可预期的后果。因此,声明了一个描述符集后,必须用FD_ZERO清除其所有位,然后在其中设置我们关心的各个位。举个例子,以下代码用于定义一个fd_set类型的变量,然后打开描述字1、4和5的对应位:

/* example */
fd_set rset;

FD_ZERO(&rset);      /* initialize the set: all bits off */
FD_SET(1, &rset);    /* turn on bit for fd 1 */
FD_SET(4, &rset);    /* turn on bit for fd 4 */
FD_SET(5, &rset);    /* turn on bit for fd 5 */

select函数的中间三个参数readset、writeset和exceptset中,如果我们对某一个的条件不感兴趣,就可以把它设为空指针。事实上,如果这三个指针均为空,我们就有了一个比UNIX的sleep函数更为精确的定时器(sleep睡眠以秒为最小单位)。poll函数提供类似的功能。

maxfdpl参数指定待测试的描述符字个数,它的值是待测试的最大描述符字加1(因此我们把该参数命名为maxfdpl),描述字0、1、2……一直到maxfdpl-1均被测试。

头文件<sys/select.h>中定义的FD_SETSIZE常值是数据类型fd_set中的描述字总数,其值通常是1024,不过很少有程序用到那么多描述字。

select函数修改由指针readset、writeset和exceptset所指向的描述字集,因而这三个参数都是值-结果参数。调用该函数时,我们指定所关心的描述字的值,该函数返回时,结果指示哪些描述字已就绪。该函数返回后,我们使用FD_ISSET宏来测试fd_set数据类型中的描述字。描述字集中任何与未就绪的描述字对应的位返回时均清成0。为此,每次重新调用select函数时,我们都得再次把所有描述字集中所关心的位均置为1。

使用select时最常见的两个错误:(1)忘了对最大描述字加1;(2)忘了描述字集是值-结果参数。

灯泡select函数返回值:

该函数的返回值表示跨所有描述字集的已就绪的总位数。如果在任何描述字就绪之前定时器时间到,那么返回0。返回-1表示出错。

灯泡描述字就绪条件:

1、下列四个条件中的任何一个满足时,一个套接口准备好读:

(1)该套接口接收缓冲区中的数据字节数大于等于套接口接收缓冲区低潮标记的当前大小。对这样的套接口的读操作将不阻塞并返回一个大于0的值(也就是返回准备好读入的数据)。我们可以使用SO_RCVLOWAT套接口选项设置该套接口的低潮标记。对于TCP和UDP套接口而言,其缺省值为1。

(2)该连接的读这一半关闭(也就是接收了FIN的TCP连接)。对这样的套接口的读操作将不阻塞并返回0(也就是返回EOF)。

(3)该套接口是一个监听套接口且已完成的连接数不为0。对这样的套接口的accept通常不会阻塞。

(4)其上有一个套接口错误待处理。对这样的套接口的读操作将不阻塞并返回-1(也就是返回一个错误),同时把errno设置成确切的错误条件。这些待处理的错误(pending error)也可以通过指定SO_ERROR套接口选项调用getsockopt获取并清除。

2、下列四个条件中的任何一个满足时,一个套接口准备好写:

(1)该套接口发送缓冲区中的可用空间字节数大于等于套接口发送缓冲区低潮标记的当前大小,并且或者(I)该套接口已连接,或者(II)该套接口不需要连接(例如UDP套接口)。这意味着如果我们把这样的套接口设置为非阻塞,写操作将不阻塞并返回一个正值(例如由传输层接受的字节数)。我们可以使用SO_SNDLOWAT套接口选项来设置该套接口的低潮标记。对于TCP和UDP而言,其缺省值通常为2048。

(2)该连接的写这一半关闭。对这样的套接口的写操作将产生SIGPIPE信号。

(3)该套接字早先使用非阻塞式connect已建立连接,并且连接已经异步建立或者connect已经以失败告终。

(4)其上有一个套接口错误待处理。对这样的套接口的写操作将不阻塞并返回-1(也就是返回一个错误),同时把errno设置成确切的错误条件。这些待处理的错误(pending error)也可以通过指定SO_ERROR套接口选项调用getsockopt获取并清除。

3、如果一个套接口存在带外数据或者仍处于带外标记,那么它有异常条件待处理。

注意:当某个套接口上发生错误时,它将由select标记为既可读又可写。

接收和发送低潮标记的目的在于:允许应用程序控制在select返回可读或可写条件之前,有多少数据可读或有多大空间可用于写。举例来说,如果我们知道除非至少存在64字节的数据,否则我们的应用程序没有任何工作可做,那么可以把接收低潮标记设置为64,以防少于64字节的数据准备好读时,select就唤醒我们。

任何UDP套接口只要其发送低潮标记小于等于发送缓冲区大小(缺省应该总是这种关系)就总是可写的,这是因为UDP套接口不需要连接。

image

原文地址:https://www.cnblogs.com/nufangrensheng/p/3589621.html