select() 在管道上使用时在标准输入上的行为

Behavior of select() on stdin when used on a pipe

我试图理解对 select() 在标准输入上使用时从管道接收数据时的行为观察。

基本上我有一个使用以下代码的简单 C 程序: hello.c:

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <termios.h>

int main(int argc, char *argv[])
{
  int flags, opt;
  int nsecs, tfnd;
  fd_set rfds;
  struct timeval tv;
  int retval;
  int stdin_fileno_p1 = STDIN_FILENO+1;
  char c;
  int n;

  /* Turn off canonical processing on stdin*/
  static struct termios oldt, newt;
  tcgetattr( STDIN_FILENO, &oldt);
  newt = oldt;
  newt.c_lflag &= ~(ICANON);
  tcsetattr( STDIN_FILENO, TCSANOW, &newt);

  while (1)
  {
    FD_ZERO(&rfds);
    FD_SET(STDIN_FILENO, &rfds);
    tv.tv_sec = 0;
    tv.tv_usec = 0;
    retval = select(stdin_fileno_p1, &rfds, NULL, NULL, &tv);

    if ( retval && (retval!=-1) )
    {
      n = read(STDIN_FILENO, &c, 1);
      write(STDOUT_FILENO, &c, 1);
    }
    else printf("No Data\n");
    usleep(100000);
  }
  tcsetattr( STDIN_FILENO, TCSANOW, &oldt);
}

如果我 运行 程序如下,当我在程序 运行 上键入按键时,我可以看到字符回显。当不按下键时,它会按预期显示 "No Data"。

./hello

但是,如果如下使用程序,程序永远不会进入显示"No Data"的状态。相反,重复显示最后一个字符 "c"。

echo -n abc | ./hello

我对这个观察有点困惑,如果你能帮助我理解观察到的行为,我将不胜感激。

问题是您的程序在读取 STDIN_FILENO 描述符时没有检测到文件结束条件。在 echo 写入 c 字符后,它将关闭管道的末端,这将导致程序中的 select 立即变为 return 并且 read到 return 0 表示该描述符不再提供更多数据。您的程序没有检测到该情况。相反,它只是用最后一个成功 read 留在缓冲区中的任何字符调用 write,然后重复循环。

要修复,请在 read 之后执行 if (n==0) break;