select()系统调用没有按预期工作

时间:2015-01-16 16:23:33

标签: c unix network-programming system-calls

我正在使用select系统等待输入。我也是在循环中这样做。 这是代码。

int main()
{
    fd_set rfds;
    struct timeval tv;  

    FD_ZERO(&rfds);
    FD_SET(0,&rfds);

    tv.tv_sec = 5;
        tv.tv_usec = 0;

    while(1)
    {
        select(1,&rfds,NULL,NULL,&tv);
        if(FD_ISSET(0,&rfds))
        {
            write(STDOUT_FILENO,"yes",3);
            FD_CLR(0,&rfds);    
        }

        tv.tv_sec = 5;
            tv.tv_usec = 0; 
    }   
    return 0;

}

现在的问题是select调用仅在第一次正常工作。如果我在前5秒内输入,我得到yes作为输出但是在随后的迭代中fd(0)仍然未设置,无论我是否提供任何输入或不。任何想法如何解决这个问题。

3 个答案:

答案 0 :(得分:3)

您的代码存在两个问题。

问题一:空文件描述符集上的select

第一个是select修改它给出的文件描述符集 - 在select返回后,它们包含为I / O做好准备的文件描述符。这意味着如果超时在stdin上没有任何输入的情况下传递,rfds将为空,并且对select的下一次调用将等待空文件描述符集的输入 - 它将永远不会找到任何输入

stdin上的输入会将STDIN_FILENO0)保留在集合中,但因为如果它出现了,你可以调用

FD_CLR(0,&rfds);

要从集合中删除stdin,select也会在这种情况下等待空fd集。我知道为什么你把它放在那里,它与第二个问题有关(见下文)。在任何情况下,解决第一个问题的方法是在再次调用select之前将stdin放回fd集:

FD_SET(0, &rfds);

问题二:输入坐在stdin永远

第二个问题是,当你的程序在stdin上等待输入时,它永远不会消耗任何东西。这意味着如果您在每次使用select调用FD_SET(0, &rfds);之前修复文件描述符集,您的程序将在无限循环中反复打印“是”。

这是因为一旦输入等待在stdin中消耗,在该文件描述符上调用select将导致select检查是否有输入等待,认识到是,有,告诉你那个事实,你不用它做任何事情,只要求select检查它是否仍在那里。无论你多久检查一次,它都是。

我不知道你究竟想要消耗什么输入,所以接下来就是猜测。如果用户输入内容,我假设您希望程序写“是”。当用户输入的东西并不总是清楚地定义时,但是通常的解释是说用户输入通常是基于行的 - 用户期望一旦他按下返回就会发生事情。然后,一种理智的方法是在数据出现时丢弃到下一个换行符,这样每次按下返回按钮都会产生“是”。这可能是这样的:

while ((c = getchar()) != EOF) && c != '\n'); // discard until end-of-line

int c;

把它放在一起

总之,这可能会做你想要的:

#include <sys/select.h>
#include <unistd.h>
#include <stdio.h>

int main()
{
  fd_set rfds;
  struct timeval tv;
  int c;

  FD_ZERO(&rfds);
  FD_SET(0,&rfds);

  tv.tv_sec = 5;
  tv.tv_usec = 0;

  while(1)
  {
    select(1,&rfds,NULL,NULL,&tv);
    if(FD_ISSET(0, &rfds))
    {
      puts("yes");
      while((c = getchar()) != EOF && c != '\n'); // consume input
    } else {
      puts("no");
      FD_SET(0, &rfds); // place stdin back in the fd set
    }

    tv.tv_sec = 5;
    tv.tv_usec = 0; 
  }

  return 0;
}

答案 1 :(得分:1)

在某些实现中,select(2)允许修改文件描述符集和超时。所以你应该在select

之前的循环中设置它们
while(1) {
  FD_ZERO(&rfds);
  FD_SET(0,&rfds);
  tv.tv_sec = 5;
  tv.tv_usec = 0;
  int ns = select(1,&rfds,NULL,NULL,&tv);
  if (ns < 0 && errno == EINTR) continue;
  else if (ns < 0) { perror("select"); exit(EXIT_FAILURE); };

您需要保留ns的结果select,并且需要处理错误案例。 rfds后,您可能应该在 tv之后检查selectns>0 ...(<{1}})

正如我所评论的那样,您应该使用poll(2)代替select(因为poll更加友好C10K problem,并且因为系统的大小fd_set编译 - 限制最高文件描述符的时间)

请注意,如果 stdin tty ,事情就相当复杂(因为通常 tty -s是内核缓冲,请参阅tty demystified页面。)

答案 2 :(得分:0)

从stdin获取数据后,代码使用FD_CLR(0,&amp; rfds)命令清除读取文件描述符集(rfds),因此在下一个循环中,您的读取文件描述符为空