C非阻塞键盘输入

Zxa*_*aos 76 c linux asynchronous input nonblocking

我正在尝试用C语言编写一个程序(在Linux上)循环,直到用户按下一个键,但不应该要求按键继续每个循环.

有一个简单的方法吗?我想我可以做到这一点,select()但这似乎很多工作.

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

Aln*_*tak 61

如前所述,您可以使用sigaction陷阱ctrl-c或select陷阱任何标准输入.

但请注意,对于后一种方法,您还需要设置TTY,使其一次一个字符而不是一次一行模式.后者是默认值 - 如果您键入一行文本,则在按Enter键之前不会将其发送到正在运行的程序的stdin.

您需要使用该tcsetattr()功能关闭ICANON模式,也可能还禁用ECHO.从内存中,您还必须在程序退出时将终端设置回ICANON模式!

为了完整性,这里有一些代码我刚刚敲了(nb:没有错误检查!),它设置了一个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 */
}
Run Code Online (Sandbox Code Playgroud)


Nor*_*sey 15

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

WINDOW *w = initscr();
cbreak();
nodelay(w, TRUE);
Run Code Online (Sandbox Code Playgroud)

那时你可以getch不受阻塞地打电话.

  • 是的,诅咒是全有或全无. (5认同)

Meh*_*ari 11

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


Jon*_*egg 6

你可能想要 kbhit();

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

using namespace std;

int main()
{
    while(1)
    {
        if(kbhit())
        {
            break;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

  • 绝对不便携.kbhit()是DOS/Windows控制台的IO功能. (3认同)
  • Alnitak:我不知道它暗示了一个平台 - 什么是等效的Windows术语? (3认同)
  • @Alnitak:我的意思是我在接触任何*nix之前很久就熟悉"非阻塞呼叫"一词......所以就像Zxaos一样,我永远不会意识到它会暗示一个平台. (3认同)
  • @Alnitak为什么会将"非阻塞"作为一种编程概念,在Unix环境中更为熟悉? (2认同)

Jus*_*inB 6

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

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

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

  #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;
   }
Run Code Online (Sandbox Code Playgroud)