如何在 Linux 上使用 C 实现非阻塞控制台 I/O?

问题描述 投票:0回答:8

如何在 C 语言的 Linux/OS X 上执行非阻塞控制台 IO?

c linux macos io nonblocking
8个回答
31
投票

我想补充一个例子:

#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>

int main(int argc, char const *argv[]) {
    char buf[20];
    fcntl(0, F_SETFL, fcntl(0, F_GETFL) | O_NONBLOCK);
    sleep(4);
    int numRead = read(0, buf, 4);
    if (numRead > 0) {
        printf("You said: %s", buf);
    }
}

运行此程序时,您有 4 秒的时间向标准输入提供输入。如果未找到输入,它将返回。

2 样本执行:

$ ./a.out
fda 
You said: fda
$ ./a.out
$ 

23
投票

Pete Kirkham,我找到了 cc.byexamples.com,它对我有用。去那里找到对问题的很好的解释,以及 ncurses 版本。

我的代码需要从标准输入或文件中获取初始命令,然后在处理初始命令时观察取消命令。我的代码是 C++,但你应该可以使用

scanf()
以及我使用 C++ 输入函数的其余部分
getline()
.

肉是检查是否有可用输入的函数:

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

// cc.byexamples.com calls this int kbhit(), to mirror the Windows console
//  function of the same name.  Otherwise, the code is the same.
bool inputAvailable()  
{
  struct timeval tv;
  fd_set fds;
  tv.tv_sec = 0;
  tv.tv_usec = 0;
  FD_ZERO(&fds);
  FD_SET(STDIN_FILENO, &fds);
  select(STDIN_FILENO+1, &fds, NULL, NULL, &tv);
  return (FD_ISSET(0, &fds));
}

这必须在任何标准输入函数之前调用当我在使用此函数之前使用

std::cin
时,它再也没有返回true。例如,
main()
有一个看起来像这样的循环:

int main(int argc, char* argv[])
{ 
   std::string initialCommand;
   if (argc > 1) {
      // Code to get the initial command from a file
   } else {
     while (!inputAvailable()) {
       std::cout << "Waiting for input (Ctrl-C to cancel)..." << std::endl;
       sleep(1);
     }
     std::getline(std::cin, initialCommand);
   }

   // Start a thread class instance 'jobThread' to run the command
   // Start a thread class instance 'inputThread' to look for further commands
   return 0;
}

在输入线程中,新命令被添加到队列中,由

jobThread
定期处理。
inputThread
看起来有点像这样:

THREAD_RETURN inputThread()
{
  while( !cancelled() ) {
    if (inputAvailable()) {
      std::string nextCommand;
      getline(std::cin, nextCommand);
      commandQueue.lock();
      commandQueue.add(nextCommand);
      commandQueue.unlock();
    } else {
        sleep(1);
    }
  }
  return 0;
}

这个功能可能在

main()
中,但我正在使用现有的代码库,而不是反对它。

对于我的系统,在发送换行符之前没有可用的输入,这正是我想要的。如果您想在键入时读取每个字符,则需要关闭标准输入上的“规范模式”。 cc.byexamples.com 有一些我没有尝试过的建议,但其余的都有效,所以它应该有效。


7
投票

你没有,真的。 TTY(控制台)是一个非常有限的设备,您几乎不执行非阻塞 I/O。当你看到一些看起来像非阻塞 I/O 的东西时,你会做什么,比如在 curses/ncurses 应用程序中,称为 raw I/O。在原始 I/O 中,没有字符的解释,没有擦除处理等。相反,您需要编写自己的代码来检查数据,同时做其他事情。

在现代 C 程序中,您可以通过将控制台 I/O 放入 thread 或轻量级进程中,以另一种方式简化它。然后 I/O 可以以通常的阻塞方式继续,但数据可以插入到队列中以在另一个线程上处理。

更新

这里有一个 curses 教程 涵盖了更多内容。



4
投票
这是一个使用 C++ 的相关问题——

Cross-platform (linux/Win32) nonblocking C++ IO on stdin/stdout/stderr


4
投票
使用 ncurses 或线程的另一种替代方法是使用

GNU Readline,特别是其中允许您注册回调函数的部分。那么模式就是:

    在 STDIN 上使用 select()(在任何其他描述符中)
  1. 当 select() 告诉您 STDIN 已准备好读取时,调用 readline 的 rl_callback_read_char()
  2. 如果用户输入了完整的一行,rl_callback_read_char 会调用你的回调。否则它会立即返回,你的其他代码可以继续。

3
投票
让我们看看它是如何在一个 Linux 实用程序中完成的。例如,

perf/builtin-top.c 来源(简体):

static void *display_thread(void *arg) { struct pollfd stdin_poll = { .fd = 0, .events = POLLIN }; struct termios save; set_term_quiet_input(&save); while (!done) { switch (poll(&stdin_poll, 1, delay_msecs)) { ... } } tcsetattr(0, TCSAFLUSH, &save); }
所以,如果你想检查是否有任何数据可用,你可以像这样使用 poll() 或 select() :

#include <sys/poll.h> ... struct pollfd pfd = { .fd = 0, .events = POLLIN }; while (...) { if (poll(&pfd, 1, 0)>0) { // data available, read it } ... }
在这种情况下,在按下 [RETURN] 键后,您将不会在每个键上收到事件,而是在整行上收到事件。这是因为终端在 

canonical 模式下运行(输入流被缓冲,当按下 [RETURN] 时缓冲区刷新):

在规范输入处理模式下,终端输入在 由换行符终止的行(' ')、EOF 或 EOL 字符。无输入 可以阅读,直到用户输入了整行,并且 读取函数(参见输入和输出原语)最多返回一个 单行输入,无论请求多少字节。

如果您想立即阅读字符,可以使用

非规范模式。使用tcsetattr()来切换:

#include <termios.h> void set_term_quiet_input() { struct termios tc; tcgetattr(0, &tc); tc.c_lflag &= ~(ICANON | ECHO); tc.c_cc[VMIN] = 0; tc.c_cc[VTIME] = 0; tcsetattr(0, TCSANOW, &tc); }
简单程序(

游乐场链接):

#include <stdio.h> #include <unistd.h> #include <sys/poll.h> #include <termios.h> void set_term_quiet_input() { struct termios tc; tcgetattr(0, &tc); tc.c_lflag &= ~(ICANON | ECHO); tc.c_cc[VMIN] = 0; tc.c_cc[VTIME] = 0; tcsetattr(0, TCSANOW, &tc); } int main() { struct pollfd pfd = { .fd = 0, .events = POLLIN }; set_term_quiet_input(); while (1) { if (poll(&pfd, 1, 0)>0) { int c = getchar(); printf("Key pressed: %c \n", c); if (c=='q') break; } usleep(1000); // Some work } }
    

0
投票
不完全确定“控制台 IO”是什么意思——您是从 STDIN 读取,还是从其他来源读取的控制台应用程序?

如果您正在从 STDIN 读取,则需要跳过 fread() 并使用 read() 和 write(),以及 poll() 或 select() 以防止调用阻塞。您可以使用 setbuf() 禁用输入缓冲,这会导致 fread 返回 EOF,但我从未尝试过。

© www.soinside.com 2019 - 2024. All rights reserved.