你如何在 Linux 上用 C 进行非阻塞控制台 I/O?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/717572/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-03 17:11:09  来源:igfitidea点击:

How do you do non-blocking console I/O on Linux in C?

clinuxmacosiononblocking

提问by nonpolynomial237

How do you do nonblocking console IO on Linux/OS X in C?

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

采纳答案by Charlie Martin

You don't, really. The TTY (console) is a pretty limited device, and you pretty much don't do non-blocking I/O. What you do when you see something that looks like non-blocking I/O, say in a curses/ncurses application, is called raw I/O. In raw I/O, there's no interpretation of the characters, no erase processing etc. Instead, you need to write your own code that checks for data while doing other things.

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

In modern C programs, you can simplify this another way, by putting the console I/O into a threador lightweight process. Then the I/O can go on in the usual blocking fashion, but the data can be inserted into a queue to be processed on another thread.

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

Update

更新

Here's a curses tutorialthat covers it more.

这是一个诅咒教程,涵盖了更多内容。

回答by Aziz

回答by Don Werve

Not entirely sure what you mean by 'console IO' -- are you reading from STDIN, or is this a console application that reads from some other source?

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

If you're reading from STDIN, you'll need to skip fread() and use read() and write(), with poll() or select() to keep the calls from blocking. You may be able to disable input buffering, which should cause fread to return an EOF, with setbuf(), but I've never tried it.

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

回答by Pete Kirkham

I bookmarked "Non-blocking user input in loop without ncurses" earlier this month when I thought I might need non-blocking, non-buffered console input, but I didn't, so can't vouch for whether it works or not. For my use, I didn't care that it didn't get input until the user hit enter, so just used aioto read stdin.

本月早些时候,我将“无 ncurses 循环中的非阻塞用户输入”添加书签,当时我认为我可能需要非阻塞、非缓冲的控制台输入,但我没有,因此无法保证它是否有效。对于我的使用,我并不关心它直到用户按回车键才获得输入,所以只是使用aio读取标准输入。

回答by jwhitlock

Like Pete Kirkham, I found cc.byexamples.com, and it worked for me. Go there for a good explanation of the problem, as well as the ncurses version.

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

My code needed to take an initial command from standard input or a file, then watch for a cancel command while the initial command was processed. My code is C++, but you should be able to use scanf() and the rest where I use the C++ input function getline().

我的代码需要从标准输入或文件中获取初始命令,然后在处理初始命令时观察取消命令。我的代码是 C++,但你应该能够使用 scanf() 和我使用 C++ 输入函数 getline() 的其余部分。

The meat is a function that checks if there is any input available:

肉是一个检查是否有任何可用输入的函数:

#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));
}

This has to be called before any stdin input function. When I used std::cin before using this function, it never returned true again. For example, main() has a loop that looks like this:

这必须在任何 stdin 输入函数之前调用。当我在使用此函数之前使用 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;
}

In the input thread, new commands were added to a queue, which was periodically processed by the jobThread. The inputThread looked a little like this:

在输入线程中,新的命令被添加到一个队列中,该队列由 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;
}

This function probably could have been in main(), but I'm working with an existing codebase, not against it.

这个函数可能已经在 main() 中,但我正在使用现有的代码库,而不是反对它。

For my system, there was no input available until a newline was sent, which was just what I wanted. If you want to read every character when typed, you need to turn off "canonical mode" on stdin. cc.byexamples.comhas some suggestions which I haven't tried, but the rest worked, so it should work.

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

回答by Tyler McHenry

Another alternative to using ncurses or threads is to use GNU Readline, specifically the part of it that allows you to register callback functions. The pattern is then:

使用 ncurses 或线程的另一种替代方法是使用GNU Readline,特别是它允许您注册回调函数的部分。然后模式是:

  1. Use select() on STDIN (among any other descriptors)
  2. When select() tells you that STDIN is ready to read from, call readline's rl_callback_read_char()
  3. If the user has entered a complete line, rl_callback_read_char will call your callback. Otherwise it will return immediately and your other code can continue.
  1. 在 STDIN 上使用 select()(在任何其他描述符中)
  2. 当 select() 告诉你 STDIN 准备好读取时,调用 readline 的 rl_callback_read_char()
  3. 如果用户输入了完整的一行,rl_callback_read_char 将调用您的回调。否则它将立即返回并且您的其他代码可以继续。

回答by Koray Tugay

I want to add an example:

我想添加一个例子:

#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);
    }
}

When you run this program you have 4 seconds to provide input to standard in. If no input found, it will not block and will simply return.

当你运行这个程序时,你有 4 秒的时间来向标准输入提供输入。如果没有找到输入,它不会阻塞并且会简单地返回。

2 sample executions:

2个示例执行:

Korays-MacBook-Pro:~ koraytugay$ ./a.out
fda 
You said: fda
Korays-MacBook-Pro:~ koraytugay$ ./a.out
Korays-MacBook-Pro:~ koraytugay$