2022-04-22-Linux C 中select函数用法及注意事项

2022/4/22 7:12:43

本文主要是介绍2022-04-22-Linux C 中select函数用法及注意事项,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

Linux C 中select函数用法详细介绍及注意事项:
头文件:
/* According to POSIX.1-2001 */
#include <sys/select.h>

/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);

// 返回值:若有就绪描述符,则返回就绪描述符数目;成功时返回大于0的值,该值是发生事件(可读、可写、异常)的文件描述符数量,失败时返回-1,若超时则返回0。
第一个参数: nfds:是一个整数值,指集合中所有的文件描述符的范围,即所有的文件描述符的最大值加1。用来指定、表示监视的文件描述符数量,即注册在fd_set变量中文件描述符的数量。而每次新建文件描述符时,其值都会加1,(新建的文件描述符都要被监视)故只需将最大文件描述符值加1作为select函数的第一个参数,加1是因为文件描述符的值是从0开始的。
但要注意,待监听的文件描述符集总是从0, 1, 2,...开始的。所以,假如你要检测的描述符为11, 12, 13,那么系统实际也要监视 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10。此时真正待监视的描述符的个数为14个,也就是max(11, 12, 13) + 1
注意:
​ 1、如果你要监视描述符11, 12, 13, 但是你把select的第一个参数定为11,实际上只监视0到10, 所以select不会感知到11, 12, 13描述符的变化。
​ 2、如果你要监视描述符11, 12, 13, 且你把select的第一个参数定为14,实际上会监视0-13, 但是,如果你不把描述符 (比如)0 set到描述符集合fd_set中, 那么select也不会感知到0描述符的变化。
​ 所以, select感知到描述符变化的必要条件是,第一个参数要合理,比如定义为(fd_Max + 1), 且把需要监视的文件描述符 set 到文件描述符集合中来。

一个文件描述符集合保存在fd_set变量中,可读,可写,异常这三个文件描述符集合需要使用三个变量来保存,分别是 readfds,writefds,exceptfds。我们可以认为一个fd_set变量是由很多个二进制位构成的数组,每一位表示一个文件描述符是否需要被监视。

第二个参数: fd_set *readfds 是指向 fd_set 结构的指针,这个集合中包括文件描述符,是要监视这些文件描述符的读变化的,即关心是否可以从这些文件中读取数据。
如果这个集合中有一个文件可读,select就会返回一个大于0的值,表示有文件可读(前提没有超时);
如果没有可读的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。
可以传入NULL值,表示不关心任何文件的读变化。

第三个参数: fd_set *writefds 是指向 fd_set 结构的指针,这个集合中包括文件描述符,是要监视这些文件描述符的写变化的,即关心是否可以向这些文件中写入数据。
如果这个集合中有一个文件可写,select就会返回一个大于0的值,表示有文件可写;
如果没有可写的文件,则根据timeout再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。
可以传入NULL值,表示不关心任何文件的写变化。

第四个参数: fd_set *exceptfds 同上面两个参数的意图,用来监视文件异常。

struct timeval{
long tv_sec; //秒
long tv_usec; //微秒
}

第五个参数: timeout,参数timeout是select函数的超时时间,传入不同的参数值,将使得函数具有不同的行为(分以下三种情况):

1)若以 NULL 传入,则将函数置于阻塞状态,直到监视的文件描述符集合中某个文件描述符发生变化,函数才会被唤醒;(阻塞状态即无期限等待下去,这个等待可以被一个信号中断,只有当一个描述符准备好,或者捕获到一个信号时,函数才会返回。如果是捕获到信号,select返回-1,并将变量errno设置成EINTR。)
2)若以 0 秒 && 0 毫秒 即 0 传入,则将函数置为纯粹的非阻塞状态,不管文件描述符是否有变化,函数都立即返回执行。(不等待,而是直接返回,加入的描述符都会被监视,并且返回有事件发生的描述符个数,这种形式通过轮询方式,无阻塞地获得了多个文件描述符状态。)
3)若以大于 0 的值传入,则该值为超时时间值,函数在该时间值内阻塞,在超时时间内若集合中的文件描述符有变化,函数就返回,否则函数在超时后就立即返回。(等待指定的时间。当有描述符符合条件或者超过指定的超时时间的话,函数才会返回。等待总是会被信号中断。)

返回值
成功:> 0,返回三种文件描述符集合中“准备好了的”的文件描述符总数量
错误:返回 -1,并设置 errno
超时:返回 0

错误码:
EBADF:集合中包含无效的文件描述符。(文件描述符已经关闭了,或者文件描述符上已经有错误了)。
EINTR:捕获到一个信号,信号中断异常。
EINVAL:nfds是负的或者timeout中包含的值无效。
ENOMEM:无法为内部表分配内存。

int FD_ISSET(int fd, fd_set *set); //检测文件描述符集set中对应于文件描述符fd的位是否被设置
void FD_CLR(int fd, fd_set *set); //清除某一个被监视的文件描述符 fd, 即清除文件描述符集set中对应于文件描述符fd的位(设置为0)
void FD_ZERO(fd_set *set); //清空集合中的文件描述符,将每一位都设置为0,即清除文件描述符集set中的所有位(把所有位都设置为0)
void FD_SET(int fd, fd_set *set); //添加一个文件描述符,将set中的某一位设置成1,即设置文件描述符集set中对应于文件描述符fd的位(设置为1)

使用select的时候,需要注意:
1)我们应该总是设置timeout=0。因为依赖超时的代码通常是不可移植,并且很难调试。
2)nfds的值需要准备且适当。
3)read,recv,write,send和select都会有返回-1的情况,并set errno的值。这些errno必须被恰当的处理。如果你的程序不会接收到任何信号,那么errno永远都不会等于EINTR,如果你的程序并不会设置为非阻塞IO,那么errno就不会等于EAGAIN。
4)调用read,recv,write,send,不要使buffer的长度为0;
5)如果read,recv,write,send调用失败,并且返回的errno不是3)中说的那两种情况,或者返回0,意思是“end-of-file”, 这种情况下我们不应再将文件描述符传递给select。
6)每次调用select之前,timeout都需要重新设置。因为针对 设置超时时间的情况,timeout会被自动更新减少至0。
7)由于select()修改其文件描述符集,如果调用在循环中使用,则必须在每次调用之前重新初始化这些文件描述符集。

优点:
(1)select的可移植性较好,可以跨平台;可跨平台完成文件描述符监听。
(2)select可设置的监听时间 timeout 精度更好,可精确到微秒,而poll为毫秒,pselect为纳秒。

缺点:
(1)select支持的文件描述符数量上限为1024,不能根据用户需求进行更改;
(2)select每次调用时都要将文件描述符集合从用户态拷贝到内核态,开销较大;
(3)select返回的就绪文件描述符集合,需要用户循环遍历所监听的所有文件描述符是否在该集合中,当监听描述符数量很大时效率较低。

问题:
1)监听上限受文件描述符限制,最大1024
2)每次调用select函数时都需要向select函数传递监视对象信息
3)作为监视对象的fd_set变量会发生变化,所以每次调用select函数前要复制并保存原有监视对象的信息;循环中每次都要fd_zero, 及fd_set(xx)。
4)调用select函数后要遍历所有文件描述符,观察作为监视对象的fd_set变量的变化,查找发生变化的套接字文件描述符
5)每次调用select函数时需要向操作系统传递监视对象信息,而应用程序向操作系统传递数据将对程序造成很大的负担。而epoll只向操作系统传递1次监视对象,监视范围或内容发生变化时只通知发生变化的事项。

使用示例:
FD_ZERO(&rfds); // 初始化集合 如果不初始化,会导致不可预期的后果
FD_SET(0, &rfds); // 把文件描述符0加入到监测集合中



这篇关于2022-04-22-Linux C 中select函数用法及注意事项的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程