select函数到底该怎么用?

大家好,我是涛哥。

中秋节刚刚过去,大家过得挺开心吧。关于中秋,有很多美好的诗词歌赋,也有很多形式的纪念,比如孔明灯。我自己没去放孔明灯,仅手绘一张,聊寄我思。

                                                             涛哥手绘(临摹)  

欢乐时光,总是短暂。又是上班的节奏了,是该收收心咯。接下来,我们来看看重要的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函数监控标准输入输出描述符,至于监控网络套接字,也是类似。后面会逐渐聊到网络编程的实战,敬请期待。

猜你喜欢

转载自blog.csdn.net/stpeace/article/details/120498206