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

uyto3xhc  于 2023-05-16  发布在  Linux
关注(0)|答案(8)|浏览(140)

如何在Linux/OS X上用C实现非阻塞控制台IO?

pbgvytdp

pbgvytdp1#

我想补充一个例子:

#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
$
7jmck4yq

7jmck4yq2#

就像Pete Kirkham一样,我找到了cc.byexamples.com,它对我很有效。去那里获得对问题的很好的解释,以及ncurses版本。
我的代码需要从标准输入或文件中获取初始命令,然后在处理初始命令时监视cancel命令。我的代码是C++,但你应该能够使用scanf(),其余的我使用C++输入函数getline()
meat是一个函数,用于检查是否有任何可用的输入:

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

这必须在任何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;
}

在输入线程中,新命令被添加到队列中,该队列由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有一些我没有尝试过的建议,但其余的工作,所以它应该工作。

50few1ms

50few1ms3#

你真的不知道TTY(控制台)是一个非常有限的设备,你几乎不做非阻塞I/O。当你看到一些看起来像非阻塞I/O的东西时,比如在curses/ncurses应用程序中,你所做的就是所谓的 raw I/O。在原始I/O中,没有字符的解释,没有擦除处理等。相反,您需要编写自己的代码,在执行其他操作时检查数据。
在现代C程序中,您可以通过将控制台I/O放入一个 * 线程 * 或轻量级进程中来简化这一点。然后,I/O可以以通常的阻塞方式继续,但数据可以插入到队列中,以便在另一个线程上处理。

更新

这里有一个curses tutorial,它涵盖了更多。

uklbhaso

uklbhaso4#

本月早些时候,当我认为我可能需要非阻塞、非缓冲的控制台输入时,我将“Non-blocking user input in loop without ncurses”作为书签,但我没有,所以不能保证它是否有效。对于我的使用,我不在乎它直到用户按回车键才得到输入,所以只使用aio来读取stdin。

wlzqhblo

wlzqhblo6#

使用ncurses或线程的另一种替代方法是使用GNU Readline,特别是它允许您register callback functions的部分。模式则为:
1.在STDIN上使用select()(以及其他描述符)
1.当select()告诉您可以读取STDIN时,调用readline的rl_callback_read_char()
1.如果用户输入了完整的行,rl_callback_read_char将调用您的回调。否则,它将立即返回,您的其他代码可以继续。

pn9klfpd

pn9klfpd7#

让我们看看它是如何在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]键后,您将不会在每个键上接收事件,而是在整行上接收事件。这是因为terminal在canonical mode中运行(输入流被缓冲,当按下[RETURN]时缓冲区刷新):
在规范输入处理模式下,终端输入在以换行符('\n')、EOF或EOL字符终止的行中处理。在用户输入整行之前,不能读取任何输入,并且read函数(请参阅输入和输出原语)最多返回一行输入,无论请求多少字节。
如果你想立即读取字符,你可以使用noncanonical mode。使用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);
}

简单程序(link to playground):

#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 
  }
}
7gs2gvoe

7gs2gvoe8#

不完全确定您所说的“控制台IO”是什么意思--您是从STDIN阅读,还是这是一个从其他源读取的控制台应用程序?
如果从STDIN阅读,则需要跳过fread(),使用read()和write(),并使用poll()或select()来防止调用阻塞。您可以使用setbuf()禁用输入缓冲,这应该会导致fread返回EOF,但我从未尝试过。

相关问题