在之前的网络编程中, 我们经常提到select函数, Windows select和linux select大同小异, 下面, 我们来玩玩linux select, 直接上菜:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/time.h>
#include<sys/types.h>
int main()
{
struct timeval tv; // 超时时间
tv.tv_sec = 10;
tv.tv_usec = 500; // 注意单位是微秒
fd_set rdfds;
FD_ZERO(&rdfds); // 描述集初始化
FD_SET(STDIN_FILENO, &rdfds); // STDIN_FILENO是标准输入, 塞入描述集
int iRet = select(STDIN_FILENO + 1, &rdfds, NULL, NULL, &tv); // 第一个参数是监控句柄号+1
if(iRet < 0)
{
printf("selcet error, iRet %\n", iRet);
return -1;
}
if(0 == iRet)
{
printf("timeout \n");
return -2;
}
printf("iRet = %d \n", iRet); // 在终端中, 输入, 然后按enter, 会走到这里
char szBuf[10]= {0};
if(FD_ISSET(STDIN_FILENO, &rdfds) ) // 监控输入描述符已经发生了改变
{
printf("to read data\n");
read(STDIN_FILENO, szBuf, sizeof(szBuf) - 1); // 从键盘读取输入
}
write(STDOUT_FILENO, szBuf, strlen(szBuf)); // 在终端中回显
return 0;
}
执行程序后, 程序卡在select处, 如果用户不输入任何东西, 那么就会在10s + 500微秒的时候, select返回0. 如果用户在超时之前输入, 那么select立即返回1. 有点意思, 从这个程序可以体会到select的用途。
在linux中, select函数也可以用来做延时程序哦, 精确度为微秒。爽。
为了更好地理解上面的程序, 我们继续来看一个有趣的问题:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/time.h>
#include<sys/types.h>
int main()
{
struct timeval tv; // 超时时间
tv.tv_sec = 10;
tv.tv_usec = 500; // 注意单位是微秒
fd_set rdfds;
FD_ZERO(&rdfds); // 描述集初始化
FD_SET(STDIN_FILENO, &rdfds); // STDIN_FILENO是标准输入, 塞入描述集
FD_SET(STDOUT_FILENO, &rdfds); // STDOUT_FILENO是标准输入, 塞入描述集
if(FD_ISSET(STDIN_FILENO, &rdfds) )
{
printf("STDIN_FILENO is in fds 111\n");
}
if(FD_ISSET(STDOUT_FILENO, &rdfds) )
{
printf("STDOUT_FILENO is in fds 222\n");
}
int iRet = select(STDIN_FILENO + 1, &rdfds, NULL, NULL, &tv); // 第一个参数是监控句柄号+1
if(iRet < 0)
{
printf("selcet error, iRet %\n", iRet);
return -1;
}
if(0 == iRet)
{
printf("timeout \n");
}
printf("iRet = %d \n", iRet);
if(FD_ISSET(STDIN_FILENO, &rdfds) )
{
printf("STDIN_FILENO is in fds 333\n");
}
if(FD_ISSET(STDOUT_FILENO, &rdfds) )
{
printf("STDOUT_FILENO is in fds 444\n");
}
return 0;
}
运行一下, 然后什么也不要动(等10s中), 结果为:
STDIN_FILENO is in fds 111
STDOUT_FILENO is in fds 222
timeout
iRet = 0
可见, 刚开始时候, 标准输入、标准输出描述符都在描述集中。 select函数在超时时间内, 没有检测到有输入输出, 此时这两个描述集被自动清除。 我们再次运行程序, 并在10s内输入数据, 可以看到, 结果为:
STDIN_FILENO is in fds 111
STDOUT_FILENO is in fds 222
iRet = 1
STDIN_FILENO is in fds 333
可以看到, 检测到有输入后, select函数立即返回, 此时标准输入描述符仍然在描述集中, 而标准输出描述集则被清除。 有点意思。
所以, 在调用select之前, 我们通常需要把待监测的描述符号放下描述集中, 而在select后, 用FD_ISSET检查哪些描述符仍在描述集中, 那么, 这些描述集就处于就绪状态。 因此, select前后的操作均不可少。
因此, 如下试好习惯和范式:
FD_ZERO
FD_SET
select
FD_ISSET
最后要注意, 上述iRet为1表示rdfds中, 就绪的描述符总个数为1.