Linux C 非阻塞键盘输入

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/448944/
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 16:53:15  来源:igfitidea点击:

C non-blocking keyboard input

clinuxasynchronousinputnonblocking

提问by Zxaos

I'm trying to write a program in C (on Linux) that loops until the user presses a key, but shouldn't require a keypress to continue each loop.

我正在尝试用 C(在 Linux 上)编写一个程序,该程序会循环直到用户按下某个键,但不需要按键来继续每个循环。

Is there a simple way to do this? I figure I could possibly do it with select()but that seems like a lot of work.

有没有一种简单的方法可以做到这一点?我想我可以做到,select()但这似乎需要做很多工作。

Alternatively, is there a way to catch a ctrl-ckeypress to do cleanup before the program closes instead of non-blocking io?

或者,有没有办法在程序关闭而不是非阻塞 io 之前捕获ctrl-c按键进行清理?

采纳答案by Alnitak

As already stated, you can use sigactionto trap ctrl-c, or selectto trap any standard input.

如前所述,您可以使用sigaction捕获 ctrl-c 或select捕获任何标准输入。

Note however that with the latter method you also need to set the TTY so that it's in character-at-a-time rather than line-at-a-time mode. The latter is the default - if you type in a line of text it doesn't get sent to the running program's stdin until you press enter.

但是请注意,使用后一种方法,您还需要设置 TTY,使其处于一次字符而不是一次行模式。后者是默认设置——如果你输入一行文本,它不会被发送到正在运行的程序的标准输入,直到你按下回车键。

You'd need to use the tcsetattr()function to turn off ICANON mode, and probably also disable ECHO too. From memory, you also have to set the terminal back into ICANON mode when the program exits!

您需要使用该tcsetattr()功能关闭 ICANON 模式,也可能还需要禁用 ECHO。根据记忆,您还必须在程序退出时将终端设置回 ICANON 模式!

Just for completeness, here's some code I've just knocked up (nb: no error checking!) which sets up a Unix TTY and emulates the DOS <conio.h>functions kbhit()and getch():

为了完整起见,这里有一些我刚刚敲出的代码(注意:没有错误检查!)它设置了一个 Unix TTY 并模拟了 DOS<conio.h>功能kbhit()getch()

#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/select.h>
#include <termios.h>

struct termios orig_termios;

void reset_terminal_mode()
{
    tcsetattr(0, TCSANOW, &orig_termios);
}

void set_conio_terminal_mode()
{
    struct termios new_termios;

    /* take two copies - one for now, one for later */
    tcgetattr(0, &orig_termios);
    memcpy(&new_termios, &orig_termios, sizeof(new_termios));

    /* register cleanup handler, and set the new terminal mode */
    atexit(reset_terminal_mode);
    cfmakeraw(&new_termios);
    tcsetattr(0, TCSANOW, &new_termios);
}

int kbhit()
{
    struct timeval tv = { 0L, 0L };
    fd_set fds;
    FD_ZERO(&fds);
    FD_SET(0, &fds);
    return select(1, &fds, NULL, NULL, &tv);
}

int getch()
{
    int r;
    unsigned char c;
    if ((r = read(0, &c, sizeof(c))) < 0) {
        return r;
    } else {
        return c;
    }
}

int main(int argc, char *argv[])
{
    set_conio_terminal_mode();

    while (!kbhit()) {
        /* do some work */
    }
    (void)getch(); /* consume the character */
}

回答by Mehrdad Afshari

On UNIX systems, you can use sigactioncall to register a signal handler for SIGINTsignal which represents the Control+C key sequence. The signal handler can set a flag which will be checked in the loop making it to break appropriately.

在 UNIX 系统上,您可以使用sigactioncall 为SIGINT表示 Control+C 键序列的信号注册一个信号处理程序。信号处理程序可以设置一个标志,该标志将在循环中检查以使其适当中断。

回答by Nate879

There is no portable way to do this, but select() might be a good way. See http://c-faq.com/osdep/readavail.htmlfor more possible solutions.

没有可移植的方法来做到这一点,但 select() 可能是一个好方法。有关更多可能的解决方案,请参阅http://c-faq.com/osdep/readavail.html

回答by PolyThinker

The curses library can be used for this purpose. Of course, select()and signal handlers can be used too to a certain extent.

诅咒库可用于此目的。当然,select()在一定程度上也可以使用信号处理程序。

回答by Jon Clegg

You probably want kbhit();

你可能想要 kbhit();

//Example will loop until a key is pressed
#include <conio.h>
#include <iostream>

using namespace std;

int main()
{
    while(1)
    {
        if(kbhit())
        {
            break;
        }
    }
}

this may not work on all environments. A portable way would be to create a monitoring thread and set some flag on getch();

这可能不适用于所有环境。一种可移植的方法是创建一个监视线程并在上设置一些标志getch();

回答by Norman Ramsey

select()is a bit too low-level for convenience. I suggest you use the ncurseslibrary to put the terminal in cbreak mode and delay mode, then call getch(), which will return ERRif no character is ready:

select()为了方便,有点太低级了。我建议您使用ncurses库将终端置于 cbreak 模式和延迟模式,然后调用getch()ERR如果没有字符准备好,它将返回:

WINDOW *w = initscr();
cbreak();
nodelay(w, TRUE);

At that point you can call getchwithout blocking.

那时你可以getch无阻塞地调用。

回答by Norman Ramsey

If you are happy just catching Control-C, it's a done deal. If you really want non-blocking I/O but you don't want the curses library, another alternative is to move lock, stock, and barrel to the AT&T sfiolibrary. It's nice library patterned on C stdiobut more flexible, thread-safe, and performs better. (sfio stands for safe, fast I/O.)

如果您对抓住 Control-C 感到高兴,那就大功告成了。如果你真的想要非阻塞 I/O 但你不想要 Curses 库,另一种选择是将锁、股票和桶移动到AT&Tsfio。这是一个很好的库,以 C 为模式,stdio但更灵活、线程安全,并且性能更好。(sfio 代表安全、快速的 I/O。)

回答by JustinB

Another way to get non-blocking keyboard input is to open the device file and read it!

另一种获得非阻塞键盘输入的方法是打开设备文件并读取它!

You have to know the device file you are looking for, one of /dev/input/event*. You can run cat /proc/bus/input/devices to find the device you want.

你必须知道你正在寻找的设备文件,/dev/input/event* 之一。你可以运行 cat /proc/bus/input/devices 来找到你想要的设备。

This code works for me (run as an administrator).

此代码适用于我(以管理员身份运行)。

  #include <stdlib.h>
  #include <stdio.h>
  #include <unistd.h>
  #include <fcntl.h>
  #include <errno.h>
  #include <linux/input.h>

  int main(int argc, char** argv)
  {
      int fd, bytes;
      struct input_event data;

      const char *pDevice = "/dev/input/event2";

      // Open Keyboard
      fd = open(pDevice, O_RDONLY | O_NONBLOCK);
      if(fd == -1)
      {
          printf("ERROR Opening %s\n", pDevice);
          return -1;
      }

      while(1)
      {
          // Read Keyboard Data
          bytes = read(fd, &data, sizeof(data));
          if(bytes > 0)
          {
              printf("Keypress value=%x, type=%x, code=%x\n", data.value, data.type, data.code);
          }
          else
          {
              // Nothing read
              sleep(1);
          }
      }

      return 0;
   }

回答by Rod

You can do that using select as follow:

您可以使用 select 来做到这一点,如下所示:

  int nfds = 0;
  fd_set readfds;
  FD_ZERO(&readfds);
  FD_SET(0, &readfds); /* set the stdin in the set of file descriptors to be selected */
  while(1)
  {
     /* Do what you want */
     int count = select(nfds, &readfds, NULL, NULL, NULL);
     if (count > 0) {
      if (FD_ISSET(0, &readfds)) {
          /* If a character was pressed then we get it and exit */
          getchar();
          break;
      }
     }
  }

Not too much work :D

没有太多的工作:D

回答by 112

Here's a function to do this for you. You need termios.hwhich comes with POSIX systems.

这是为您执行此操作的功能。您需要termios.hPOSIX 系统附带的。

#include <termios.h>
void stdin_set(int cmd)
{
    struct termios t;
    tcgetattr(1,&t);
    switch (cmd) {
    case 1:
            t.c_lflag &= ~ICANON;
            break;
    default:
            t.c_lflag |= ICANON;
            break;
    }
    tcsetattr(1,0,&t);
}

Breaking this down: tcgetattrgets the current terminal information and stores it in t. If cmdis 1, the local input flag in tis set to non-blocking input. Otherwise it is reset. Then tcsetattrchanges standard input to t.

分解:tcgetattr获取当前终端信息并将其存储在t. 如果cmd为 1,则本地输入标志 int设置为非阻塞输入。否则它被重置。然后tcsetattr将标准输入更改为t.

If you don't reset standard input at the end of your program you will have problems in your shell.

如果您没有在程序结束时重置标准输入,您的 shell 就会出现问题。