【问题标题】:The select() system call isn't working as expectedselect() 系统调用未按预期工作
【发布时间】:2015-03-15 07:34:05
【问题描述】:

我正在使用选择系统来等待输入。我也在循环中执行此操作。 这是代码。

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) 保持未设置,无论我是否是否提供任何输入。任何想法我如何解决这个问题。

【问题讨论】:

  • 那不是 c++(更像 c)
  • @Axalo 好吧,我的错。你能帮我解决这个问题吗?
  • @Axalo 顺便说一句,那是 c++
  • 你应该使用poll(2)而不是select(因为pollC10K problem更友好)
  • @Axalo 那是 c++,c 标准 (5.1.2.2.1) 需要 int main(void)int main(int argc, char *argv[])

标签: c unix network-programming system-calls


【解决方案1】:

您的代码有两个问题。

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

首先是select 修改了给定的文件描述符集——select 返回后,它们包含准备好进行 I/O 的文件描述符。这意味着,如果超时过去了,而在标准输入上没有任何输入,rfds 将是空的,并且对 select 的下一次调用将等待空文件描述符集的输入——它永远不会找到任何输入。

stdin 上的输入会将STDIN_FILENO(即0)保留在集合中,但是如果它出现,您会调用

FD_CLR(0,&rfds);

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

FD_SET(0, &rfds);

问题二:输入永远存在于标准输入中

第二个问题是,当您的程序在标准输入上等待输入时,它从不消耗任何内容。这意味着,如果您在每次使用 FD_SET(0, &rfds); 调用 select 之前修复文件描述符集,您的程序最终将在无限循环中一遍又一遍地打印“yes”。

这是因为一旦输入等待在标准输入中被使用,在该文件描述符上调用 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;
}

【讨论】:

  • 谢谢 :) 。我应该刷新输入缓冲区。
  • 您不能像冲洗水龙头一样冲洗输入缓冲区。 fflush 用于输出流。存在__fpurge,但它的语义不是很好定义的 wrt 缓冲。坚持基于行的输入,这是最可预测的。
【解决方案2】:

在某些实现中,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); };

您需要保留select 的结果ns,并且需要处理错误情况。您可能应该检查rfdstv 之后 你的select - 当它成功时(ns&gt;0)...

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

请注意,如果您的 stdintty 事情会非常复杂(因为通常 tty-s 是 kernel 缓冲,参见tty demystified 页面)。

【讨论】:

  • 除非确实需要处理数千个套接字,否则没有必要更喜欢 poll ovet select (除了可以说是更好的 poll API )。对于数千个套接字,两者都不是很好,更好地使用例如。 Linux 上的 epoll,或一些隐藏不可移植代码的多平台库。
  • 感谢您的帮助:)
【解决方案3】:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-11-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-02-13
    • 1970-01-01
    • 1970-01-01
    • 2014-08-01
    相关资源
    最近更新 更多