【问题标题】:non-buffering stdin reading非缓冲标准输入读取
【发布时间】:2011-05-18 16:41:52
【问题描述】:

我的测试应用是

#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>

int main(int argc, char *argv[], char *envp[]) {
  int fd[2];

  if(pipe(fd) < 0) { 
    printf("Can\'t create pipe\n");
    exit(-1); 
  }

  pid_t fpid = fork();
  if (fpid == 0) {
    close(0);
    close(fd[1]);
    char *s = (char *) malloc(sizeof(char));
    while(1) if (read(fd[0], s, 1)) printf("%i\n", *s);
  }
  close(fd[0]);
  char *c = (char *) malloc(sizeof(char));
  while (1) {
    if (read(0, c, 1) > 0) write(fd[1], c, 1);
  }
  return 0;
}

我想在每个输入的字符后查看字符代码。但实际上 *s 仅在控制台中的 '\n' 之后打印。所以似乎stdin(desc 0的文件)被缓冲了。但是读取功能是无缓冲的,不是吗?我哪里错了。

UPD:我用的是 linux。

所以解决办法是

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

int main(int argc, char *argv[], char *envp[]) {
  int fd[2];

  if(pipe(fd) < 0) { 
    printf("Can\'t create pipe\n");
    exit(-1); 
  }

  struct termios term, term_orig;

  if(tcgetattr(0, &term_orig)) {
    printf("tcgetattr failed\n");
    exit(-1); 
  }

  term = term_orig;

  term.c_lflag &= ~ICANON;
  term.c_lflag |= ECHO;
  term.c_cc[VMIN] = 0;
  term.c_cc[VTIME] = 0;

  if (tcsetattr(0, TCSANOW, &term)) {
    printf("tcsetattr failed\n");
    exit(-1);
  }

  pid_t fpid = fork();
  if (fpid == 0) {
    close(0);
    close(fd[1]);
    char *s = (char *) malloc(sizeof(char));
    while(1) if (read(fd[0], s, 1)) printf("%i\n", *s);
  }
  close(fd[0]);
  char *c = (char *) malloc(sizeof(char));
  while (1) {
    if (read(0, c, 1) > 0) write(fd[1], c, 1);
  }
  return 0;
} 

【问题讨论】:

  • 请注意,这与缓冲无关。
  • 代码不应该在退出之前将父级中的终端属性重置为term_orig吗?您可能还应该让孩子在某个时候退出——在父母关门后,它会不断地从read() 得到 0。但是,父级也处于无限循环中;该过程仅在发出信号时结束。您确实需要一个信号处理程序来调用tcsetattr(),并使用您可能获得的主要信号的原始终端值(可以处理):HUP、INT、QUIT 也许,PIPE 和 TERM 是一个很好的集合。当然,你不能对 KILL 或 STOP 做任何事情。

标签: c linux unix posix


【解决方案1】:

不幸的是,您正在寻找的行为在标准 ANSI C 中是不可能的,并且 UNIX 终端 I/O 的默认模式是面向行的,这意味着您将始终需要输入一个 \n 字符来检索输入。您需要使用终端 I/O 设施,让您在 non-canonical 模式下进行编程,这样每次按键都会触发一个事件。在 Linux/UNIX 上,您可以查看 &lt;termios.h&gt; 标头或 ncurses 库。

【讨论】:

  • @Ximik,是的,他们没有使用标准的 ANSI C。大多数使用外部库,例如 ncurses 或 termcap。
【解决方案2】:

在我看来,您的解决方案有点复杂。还是不明白为什么需要管道和2进程。

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

int main(int argc, char *argv[], char *envp[]) {
  struct termios term, term_orig;

  if(tcgetattr(0, &term_orig)) {
    printf("tcgetattr failed\n");
    exit(-1);
  }

  term = term_orig;

  term.c_lflag &= ~ICANON;
  term.c_lflag |= ECHO;
  term.c_cc[VMIN] = 0;
  term.c_cc[VTIME] = 0;

  if (tcsetattr(0, TCSANOW, &term)) {
    printf("tcsetattr failed\n");
    exit(-1);
  }

  char ch;
  while (1) {
    if (read(0, &ch, 1) > 0) 
      printf(" %d\n", ch);
  }
  return 0;
}

【讨论】:

    【解决方案3】:

    Unix 将您的 tty 字符部分地缓冲在内核中,这样程序就不必单独处理行编辑,除非它们愿意。

    您可以指示 tty 驱动程序立即为您提供字节。有各种库使这比使用原始 ioctl 更容易一些。你可以从termios(3)开始。

    【讨论】:

    • 遗憾的是,没有人能够改进这个内核级的行编辑以使其真正可用...原则上它几乎可以和readline 一样好。
    猜你喜欢
    • 1970-01-01
    • 2011-05-03
    • 1970-01-01
    • 1970-01-01
    • 2020-10-05
    • 2011-07-01
    • 1970-01-01
    • 2011-01-23
    • 1970-01-01
    相关资源
    最近更新 更多