select函数用来检查套接字描述符(sockets descriptors)是否已准备好读/写,提供了一种同时检查多个套接字的方法。
Linux中select函数的声明在/usr/include/x86_64-linux-gnu/sys/select.h文件中,Windows下select函数的声明在WinSock2.h文件中,声明如下:
// Linux
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
// Windows
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, const struct timeval *timeout);
// macros
FD_SET(int fd, fd_set *set); // Add fd to the set
FD_CLR(int fd, fd_set *set); // Remove fd from the set
FD_ISSET(int fd, fd_set *set); // Return true if fd is in the set
FD_ZERO(fd_set *set); // Clear all entries from the set
不像socket中connect、accept、recv这几个函数属于阻塞方式,而select函数属于非阻塞方式。在使用select函数时,会经常用到四个宏FD_SET(将一个指定的文件描述符加入集合)、FD_CLR(将一个指定的文件描述符从集合中删除)、FD_ISSET(检查一个指定的文件描述符是否在集合中)、FD_ZERO(清空集合)。类型fd_set存放的是一组文件描述符的集合,在Linux系统中,如设备、管道、FIFO等都可通过文件描述符的形式来访问。文件描述符在形式上是一个非负整数,实际上,它是一个索引值。套接字也是文件描述符。
select函数参数介绍:
第一个参数nfds在Linux指的是highest-numbered的文件描述符+1,类型为int。在Windows下,这个参数可以忽略,可以是任意值。
第二个参数readfds是可选的,若不为null,select返回一个大于0的值,表示有文件可读;如果没有可读的文件,则根据timeout参数的值再判断是否超时,若超出timeout的时间,select返回0;若发生错误返回负值。
第三个参数writefds是可选的,若不为null,select返回一个大于0的值,表示有文件可写;如果没有可写的文件,则根据timeout参数的值再判断是否超时,若超出timeout的时间,select返回0;若发生错误返回负值。
第四个参数exceptfds是可选的,若不为null,select返回一个大于0的值,表示有异常发生在文件集合中;如果没有异常发生,则根据timeout参数的值再判断是否超时,若超出timeout的时间,select返回0;若发生错误返回负值。
第五个参数timeout是可选的,若不为null,则用来设置超时时间,则为等待的超时时间;若为null,则将select设置为阻塞状态,直到文件描述符集合中某个文件描述符发生变化时才会返回结果。
返回值:返回-1表示调用select函数时有错误发生,具体的错误在Linux可通过errno输出来查看,在Windows下可通过WSAGetLastError查看;返回0,表示select函数超时;返回正数即调用select函数成功,表示集合中文件描述符的数量,集合也会被修改以显示哪一个文件描述符已准备就绪。
以下为测试代码(funset_socket.cpp):
#include "funset.hpp"
#ifdef _MSC_VER
#include
#include
#else
#include
#include
#include
#include
#include
#include
#endif
#include
int test_select_1()
{
#ifdef _MSC_VER
fd_set fds;
FD_ZERO(&fds);
timeval tv;
tv.tv_sec = 2;
tv.tv_usec = 0;
int ret = select(0, &fds, nullptr, nullptr, &tv);
if (ret == SOCKET_ERROR) {
fprintf(stderr, "fail to select, error: %d\n", WSAGetLastError());
return -1;
} else if (ret == 0) {
fprintf(stderr, "select timeout\n");
return -1;
} else {
fprintf(stdout, "success to select\n");
}
#else
const char* path = "/dev/video0";
int fd = open(path, O_RDWR);
if (fd == -1) {
fprintf(stderr, "fail to open device: %s\n", path);
}
fd_set fds;
FD_ZERO(&fds);
FD_SET(fd, &fds);
struct timeval tv;
tv.tv_sec = 2;
tv.tv_usec = 0;
int ret = select(fd+1, &fds, nullptr, nullptr, &tv);
if (ret == -1) {
fprintf(stderr, "fail to select, error: %d, %s\n", errno, strerror(errno));
return -1;
} else if (ret == 0) {
fprintf(stderr, "select timeout\n");
return -1;
} else {
fprintf(stdout, "success to select\n");
}
close(fd);
#endif
return 0;
}
在Linux下执行结果如下:
GitHub:https://github.com/fengbingchun/OpenSSL_Test