大家好,我是涛哥。
中秋节刚刚过去,大家过得挺开心吧。关于中秋,有很多美好的诗词歌赋,也有很多形式的纪念,比如孔明灯。我自己没去放孔明灯,仅手绘一张,聊寄我思。
涛哥手绘(临摹)
欢乐时光,总是短暂。又是上班的节奏了,是该收收心咯。接下来,我们来看看重要的select函数,建议有兴趣的朋友亲自运行并调试一下程序,肯定会有收获。
一. 关于select函数
select函数是什么呢?为什么这么重要?熟悉网络编程的朋友,肯定听说过select函数,而且在笔试面试中经常和poll、epoll做比较,屡考不厌。
无论是Windows还是Linux中的网络编程,都会涉及select函数,大同小异,本文以Linux中的select为范例来介绍,先来看select函数的介绍:
DESCRIPTION
select() and pselect() allow a program to monitor multiple file descriptors, waiting until one or more of the file
descriptors become "ready" for some class of I/O operation (e.g., input possible). A file descriptor is considered ready
if it is possible to perform a corresponding I/O operation (e.g., read(2) without blocking, or a sufficiently small
write(2)).
可见,select函数可以用来监控多个文件描述符的状态。请注意,这里文件描述符可以是标准输入标准输出,也可以是网络套接字,它们都是广义的文件描述符。
二. 监控标准输入
接下来,我们看看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处,如果不输入任何东西,那么会在10秒+500微秒时,select返回0,这个特性可以用来做延时函数。
如果在超时之前输入,那么select函数立即返回1,因为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;
}
运行程序,然后什么也不要动,安静地等到超时,程序的结果为:
STDIN_FILENO is in fds 111
STDOUT_FILENO is in fds 222
timeout
iRet = 0
可见,刚开始时,标准输入输出描述符都在描述集中。在超时时间内,没有检测到输入输出,两个描述集被自动清除。
如果再次运行上述程序,并在10s+500微秒的超时时间内输入数据,此时不会有timeout超时现象,可以看到的结果为:
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前后的操作均不可少。因此,使用select时的建议编程范式如下:
-
FD_ZERO
-
FD_SET
-
select
-
FD_ISSET
上述iRet为1表示rdfds中,就绪的描述符总个数为1. 本文例子是使用select函数监控标准输入输出描述符,至于监控网络套接字,也是类似。后面会逐渐聊到网络编程的实战,敬请期待。