在Linux环境下,select函数如下
#include <sys/select.h>
#include <sys/time.h>
int select(int maxfdp1,fd_set *readset,fd_set *writeset,fd_set *exceptset,const struct timeval *timeout)
//返回值:就绪描述符的数目,超时返回0,出错返回-1
第一个参数是最大文件描述符+1
中间的三个参数readset、writeset和exceptset指定我们要让内核测试读、写和异常条件的描述字。如果对某一个的条件不感兴趣,就可以把它设为空指针。
struct fd_set可以理解为一个集合,这个集合中存放的是文件描述符,可通过以下四个宏进行设置:
void FD_ZERO(fd_set *fdset); //清空集合
void FD_SET(int fd, fd_set *fdset); //将一个给定的文件描述符加入集合之中
void FD_CLR(int fd, fd_set *fdset); //将一个给定的文件描述符从集合中删除
int FD_ISSET(int fd, fd_set *fdset); // 检查集合中指定的文件描述符是否可以读写
(3)timeout告知内核等待所指定描述字中的任何一个就绪可花多少时间。其timeval结构用于指定这段时间的秒数和微秒数。
struct timeval{
long tv_sec; //seconds
long tv_usec; //microseconds
};
这个参数有三种可能:
(1)永远等待下去:仅在有一个描述字准备好I/O时才返回。为此,把该参数设置为空指针NULL。
(2)等待一段固定时间:在有一个描述字准备好I/O时返回,但是不超过由该参数所指向的timeval结构中指定的秒数和微秒数。
(3)根本不等待:检查描述字后立即返回,这称为轮询。为此,该参数必须指向一个timeval结构,而且其中的定时器值必须为0。
select函数的本质是委托内核去监控是否有可读Socket,如果从fdRead中得到服务器socket,说明Accept请求,其他情况recv接受信息。
select实例函数
bool OnRun() {
if (isRun()) {
// 伯克利 socket 描述符
fd_set fdRead;
fd_set fdWrite;
fd_set fdExp;
FD_ZERO(&fdRead);
FD_ZERO(&fdWrite);
FD_ZERO(&fdExp);
FD_SET(_sock, &fdRead);
FD_SET(_sock, &fdWrite);
FD_SET(_sock, &fdExp);
SOCKET maxSock = _sock;
for (int n = (int)g_clients.size() - 1; n >= 0; n--) {
FD_SET(g_clients[n]->sockfd(), &fdRead); // 添加客户端到fdRead中。
if (maxSock < g_clients[n]->sockfd())
maxSock = g_clients[n]->sockfd();
}
timeval t = { 1,0 }; // 时间参数,非阻塞模式
int ret = select(maxSock + 1, &fdRead, &fdWrite, &fdExp, &t);
if (ret < 0) {
printf("select任务结束\n");
Close();
return false;
}
if (FD_ISSET(_sock, &fdRead)) { // 检查服务器socket的状态
FD_CLR(_sock, &fdRead);
Accept(); // accept
}
// 检查客户端socket的状态
for (int n = (int)g_clients.size() - 1; n >= 0; n--) {
if (FD_ISSET(g_clients[n]->sockfd(), &fdRead)) {
if (-1 == RecvData(g_clients[n])) {
auto iter = g_clients.begin() + n;
if (iter != g_clients.end()) {
g_clients.erase(iter);
}
}
}
}
}
return isRun();
}