选择子例程
用途
检查多个文件描述符和消息队列的 I/O 状态。
库
标准 C 库 (libc.a)
语法
描述
select 子例程检查指定的文件描述符和消息队列,以查看它们是否已准备好进行读取 (接收) 或写入 (发送) ,或者它们是否具有异常的暂挂状态。
在未连接的流套接字上进行选择时,选择将在建立连接时返回。 如果在已连接的流套接字上进行选择,那么就绪消息指示可以发送或接收数据。 对于读,写和异常情况,常规文件的文件描述符始终选择 true。 For more information on sockets, refer to "了解套接字连接" and the related "检查待处理的连接 示例程序" dealing with pending connections 在 "AIX®版本 "6.1通信编程概念中 .
为了与此操作系统的前发行版和 BSD 系统兼容,还支持 select 子例程。
在共享内存描述符上, select 子例程返回 true。
参数
| 项 | 描述 |
|---|---|
| Nfdsmsgs | 指定要检查的文件描述符数和消息队列数。 低阶 16 位给出了指定要检查哪些文件描述符的位掩码的长度; 高阶 16 位给出了包含消息队列标识的数组的大小。 如果 Nfdsmsgs 参数的一半等于值 0 ,那么假定相应的位掩码或数组不存在。 |
| TimeOut | 指定一个空指针或一个指向 timeval 结构的指针,该结构指定等待至少一个要满足的选择标准的最大时间长度。 timeval 结构在 /usr/include/sys/time.h 文件中定义,它包含以下成员:TimeOut中指定的微秒数。tv_usec,值从 0 到 999999 ,如果进程没有 root 用户权限并且值小于 1 毫秒,那么设置为 1 毫秒。 如果 TimeOut 参数是空指针,那么 select 子例程将无限期等待,直到至少满足其中一个选择标准为止。 如果 TimeOut 参数指向包含零的 timeval 结构,那么将轮询文件和消息队列状态,并且 select 子例程将立即返回。 |
| 读列表、写列表、ExceptList | 分别指定要检查读,写和异常的内容。 它们一起指定选择标准。 其中每个参数都指向一个 sellist 结构,该结构可以同时指定文件描述符和消息队列。 您的程序必须定义以下格式的 sellist 结构:该fdsmask数组被视为每个位对应于文件描述符的位字符串。 文件描述符 N 由数组元素中的位(1 << (N 修改 位)) 表示 fdsmask[n /BITS (int)]。 ( BITS 宏在 values.h 文件中定义。) 设置为 1 的每个位指示要检查相应文件描述符的状态。 注: Nfdsmsgs 参数的低阶 16 位指定 bits (非元素) 在fdsmask组成文件描述符掩码的数组。 如果掩码中仅包含最后一个整数的一部分,那么将使用适当数量的低阶位,而忽略剩余的高阶位。 如果将 Nfdsmsgs 参数的低阶 16 位设置为 0 ,那么 不得 定义fdsmask sellist 结构中的数组。
每个 intmsgidsarray 指定要检查其状态的消息队列标识。 值为-1的元素将被忽略。 Nfdsmsgs 参数的高位 16 位指定msgids数组。 如果将 Nfdsmsgs 参数的高阶 16 位设置为 0 ,那么 不得 定义msgids sellist 结构中的数组。 注意: ReadList、WriteList 和 ExceptList参数指定的数组大小相同,因为每个参数都指向相同的sellist结构类型。 但是,您不需要在每个文件描述符或消息队列中指定相同数目的文件描述符或消息队列。 设置 0 不感兴趣的文件描述符位,并设置msgids数组为-1。
您可以使用 sys/select.h 文件中定义的 SELLIST 宏来定义 sellist 结构。 此宏的格式为: 其中 f 指定fdsmask数组, m 指定msgids数组,并且每个 声明符 是要声明为具有此类型的变量的名称。 |
返回值
成功完成时, select 子例程返回一个值,该值指示满足选择标准的文件描述符和消息队列的总数。 该fdsmask将修改位掩码,以便设置为 1 的位指示符合条件的文件描述符。 该msgids数组进行修改,使不符合标准的消息队列标识符被替换为-1。
返回值类似于 Nfdsmsgs 参数,因为低阶 16 位提供文件描述符的数量,高阶 16 位提供消息队列标识的数量。 这些值指示满足每个读,写和异常条件的总和。 因此,同一文件描述符或消息队列最多可计数 3 次。 您可以使用在 sys/select.h 文件中找到的 NFDS 和 NMSGS 宏将这两个值与返回值分开。 例如,如果 rc 包含从 select 子例程返回的值,那么 NFDS(rc) 是选择的文件数, 而 NMSGS(rc) 是选择的消息队列数。
如果 TimeOut 参数指定的时间限制到期,那么 select 子例程将返回值 0。
如果在 Readlist 参数中指定了基于连接的套接字,并且连接断开,那么 select 子例程将成功返回, 但是套接字上的 recv 子例程将返回值 0 以指示套接字连接已关闭。
对于基于连接的非 bloking 套接字,成功和不成功的连接都将导致 select 子例程成功返回而没有任何错误。
当连接成功完成时,套接字将变为可写,如果连接迂到错误,那么套接字将变为可读且可写。
使用 select 子例程时,不能检查套接字上的任何暂挂错误。 您需要使用 SOL_SOCKET 和 SOL_ERROR 调用 getsockopt 子例程以检查暂挂错误。
如果选择子程序不成功,则返回值为-1并设置全局变量errno以指示错误。 在这种情况下, ReadList、WriteList 和ExceptList参数所指向结构的内容是不可预测的。
错误代码
如果下列其中一项为 true ,那么 select 子例程将失败:
| 项 | 描述 |
|---|---|
| EBADF | 指定了无效的文件描述符或消息队列标识。 |
| 再次 | 内部数据结构分配失败。 |
| EINTR | 在 select 子例程期间捕获到信号,并且安装了信号处理程序并指示不重新启动子例程。 |
| EINVAL | 为 TimeOut 参数或 Nfdsmsgs 参数指定了无效值。 |
| EINVAL | 由其中一个文件描述符引用的 STREAM 或复用器在多路复用器下游 (直接或间接) 链接。 |
| Efault | ReadList、WriteList、ExceptList 或TimeOut参数指向进程地址空间之外的位置。 |
示例
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <fcntl.h>
#include <sys/time.h>
#include <errno.h>
#include <stdio.h>
int main()
{
int sockfd, cnt, i = 1;
struct sockaddr_in serv_addr;
bzero((char *)&serv_addr, sizeof (serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = inet_addr("172.16.55.25");
serv_addr.sin_port = htons(102);
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
exit(1);
if (fcntl(sockfd, F_SETFL, FNONBLOCK) < 0)
exit(1);
if (connect(sockfd, (struct sockaddr *)&serv_addr, sizeof
(serv_addr)) < 0 && errno != EINPROGRESS)
exit(1);
for (cnt=0; cnt<2; cnt++) {
fd_set readfds, writefds;
FD_ZERO(&readfds);
FD_SET(sockfd, &readfds);
FD_ZERO(&writefds);
FD_SET(sockfd, &writefds);
if (select(sockfd + 1, &readfds, &writefds, NULL,
NULL) < 0)
exit(1);
printf("Iteration %d ==============\n", i);
printf("FD_ISSET(sockfd, &readfds) == %d\n",
FD_ISSET(sockfd, &readfds));
printf("FD_ISSET(sockfd, &writefds) == %d\n",
FD_ISSET(sockfd, &writefds));
i++;
}
return 0;
}
Iteration 1 ==============
FD_ISSET(sockfd, &readfds) == 0
FD_ISSET(sockfd, &writefds) == 1
Iteration 2 ==============
FD_ISSET(sockfd, &readfds) == 1
FD_ISSET(sockfd, &writefds) == 1
在第一次迭代中, select 仅通知写事件。 在第二次迭代中, select 将同时通知读和写事件。
注
FD_SETSIZE 是 #define 变量,用于定义各种 FD 宏将使用的文件描述符数。 FD_SETSIZE 的缺省值为 65534 个打开文件描述符。 此值不能设置为大于 OPEN_MAX。
有关更多信息,请参阅 /usr/include/sys/time.h 文件。
用户可以覆盖 FD_SETSIZE 以在包含系统头文件之前选择较小的值。 由于 FD_ZERO 中的开销为零 65534 位,因此出于性能原因,需要执行此操作。
性能问题和建议的编码实践
select(FD_SETSIZE, ....)如果程序使用 FD_ZERO 和缺省 FD_SETSIZE,那么性能将很差。 不应在任何循环中或在每个 select 调用之前使用 FD_ZERO 。 但是,使用它一次将位字符串归零不会导致问题。 如果计划使用此简单编程方法,那么应覆盖 FD_SETSIZE 以定义较少数量的 FDs。 例如,如果您的进程将只打开您将选择的两个 FDs ,并且在该进程中打开的其他 FDs 永远不会超过几百个,那么您应该将 FD_SETSIZE 降低到大约 1024 个。
请勿将 FD_SETSIZE 作为第一个参数传递给 select。 这指定系统应该检查的最大文件描述符数。 程序应该跟踪已分配的最高 FD ,或者使用 getdtablesize 子例程来确定此值。 这将节省在内核中和内核外传递过长的位图,并减少 select 必须检查的 FDs 数。
使用 poll 系统调用而不是 select。 poll 系统调用具有与 select相同的功能,但它使用 FDs 列表而不是位图。 因此,如果仅在单个 FD 上进行选择,那么只会将一个 FD 传递到 poll。 通过 select,您必须传递一个位图,只要为该 FD 分配的 FD 编号。 例如,如果 AIX 分配了 FD 4000 ,那么您必须传递长度为 4001 位的位图。