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

2024-10-11 08:36:00
admin
原创
231
摘要:问题描述:如何使用 C 在 Linux/OS X 上执行非阻塞控制台 IO?解决方案 1:我想添加一个例子:#include <unistd.h> #include <fcntl.h> #include <stdio.h> int main(int...

问题描述:

如何使用 C 在 Linux/OS X 上执行非阻塞控制台 IO?


解决方案 1:

我想添加一个例子:

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

解决方案 2:

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

我的代码需要从标准输入或文件中获取初始命令,然后在处理初始命令时监视取消命令。我的代码是 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;
}

在输入线程中,新命令被添加到队列中,并由 定期处理jobThreadinputThread看起来有点像这样:

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(),但是我正在使用现有的代码库,而不是反对它。

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

解决方案 3:

实际上,你不需要。TTY(控制台)是一种功能非常有限的设备,而且你几乎不执行非阻塞 I/O。当你看到看起来像非阻塞 I/O 的东西时,比如在 curses/ncurses 应用程序中,你所做的就是原始 I/O。在原始 I/O 中,没有字符解释,没有擦除处理等。相反,你需要编写自己的代码来检查数据,同时执行其他操作。

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

更新

这里有一个curses 教程,其中详细介绍了这一点。

解决方案 4:

本月初,我收藏了“不使用 ncurses 实现循环中的非阻塞用户输入”,当时我以为我可能需要非阻塞、非缓冲的控制台输入,但我并没有这样做,因此无法保证它是否有效。对于我的使用,我并不关心它在用户按下 Enter 键之前是否没有输入,因此只使用aio来读取 stdin。

解决方案 5:

这是一个使用 C++ 的相关问题——跨平台(linux/Win32)非阻塞 C++ IO 在 stdin/stdout/stderr 上

解决方案 6:

除了使用 ncurses 或线程之外,另一种选择是使用GNU Readline ,特别是它允许您注册回调函数的部分。模式如下:

  1. 在 STDIN 上使用 select()(以及任何其他描述符)

  2. 当 select() 告诉您 STDIN 已准备好读取时,调用 readline 的 rl_callback_read_char()

  3. 如果用户输入了整行,rl_callback_read_char 将调用您的回调。否则它将立即返回,其他代码可以继续。

解决方案 7:

让我们看看如何在 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] 键后,您将收到整行事件,而不是每个键的事件。这是因为终端以规范模式运行(输入流被缓冲,按下 [RETURN] 时缓冲区刷新):

在规范输入处理模式下,终端输入以换行符 ('n')、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 
", c);
      if (c=='q') break;
    }
    usleep(1000); // Some work 
  }
}

解决方案 8:

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

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

相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   2329  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1502  
  在当今快速变化的商业环境中,项目管理工具已成为企业提升效率、优化资源分配和确保项目成功的关键。无论是初创公司还是大型企业,选择合适的项目管理软件都能显著提高团队协作和项目交付的质量。本文将为您推荐2025年不同规模企业的10个专业项目管理工具,帮助您找到最适合的解决方案。信创国产项目管理软件 - 禅道禅道是一款国产开源...
项目管理平台   1  
  工程项目管理涉及众多环节与复杂流程,从项目规划、资源分配到进度跟踪、成本控制等,每一步都对项目的成功交付起着关键作用。在数字化时代,借助专业的工程项目管理软件能极大提升管理效率与质量。然而,面对市场上琳琅满目的软件产品,如何选择适合的软件并熟练运用,成为众多工程项目管理者亟待解决的问题。明确自身需求在选择工程项目管理软...
项目风险管理   1  
  PLM(产品生命周期管理)项目管理旨在确保产品从概念到退役的整个生命周期内的有效规划、执行和控制。然而,在项目推进过程中,黑天鹅事件的出现往往会打乱原有的计划和节奏。黑天鹅事件具有意外性、重大影响和事后可解释性的特点,给PLM项目管理带来巨大挑战。如何有效应对这些难以预测的事件,制定合理的应急计划,成为项目管理者必须面...
plm研发管理系统   20  
热门文章
项目管理软件有哪些?
曾咪二维码

扫码咨询,免费领取项目管理大礼包!

云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用