C非阻塞键盘输入
我试图编写一个C程序(在Linux上),循环直到用户按下一个键,但不应该需要按键来继续每个循环。
有一个简单的方法来做到这一点? 我想我可以用select()
来做,但是这似乎很多工作。
或者,有没有办法赶上一个ctrl – c按键清理之前,程序关闭,而不是非阻塞io?
如前所述,您可以使用sigaction
来捕获ctrl-c,或者select
捕获任何标准输入。
但是请注意,对于后一种方法,您还需要设置TTY,使其一次一个字符,而不是一次一行模式。 后者是默认的 – 如果你输入一行文本,它不会被发送到正在运行的程序的标准输入,直到你按下输入。
您需要使用tcsetattr()
函数关闭ICANON模式,并且也可能禁用ECHO。 从内存中,当程序退出时,还必须将终端重新设置为ICANON模式!
为了完整kbhit()
,下面是一些我刚刚敲起来的代码(nb:no error checking!),它设置了一个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 */ }
select()
有点太低级了。 我建议你使用ncurses
库将终端置于cbreak模式和延迟模式,然后调用getch()
,如果没有字符准备就会返回ERR
。
WINDOW *w = initscr(); cbreak(); nodelay(w, TRUE);
在这一点上,你可以调用getch
而不会阻塞。
在UNIX系统上,可以使用sigaction
调用为SIGINT
信号注册一个代表Control + C密钥序列的信号处理程序。 信号处理程序可以设置一个将在循环中检查的标志,使其适当地中断。
您可能需要kbhit();
//Example will loop until a key is pressed #include <conio.h> #include <iostream> using namespace std; int main() { while(1) { if(kbhit()) { break; } } }
这可能不适用于所有环境。 一个便携的方法是创建一个监视线程并在getch();
上设置一些标志getch();
curses库可以用于这个目的。 当然, select()
和信号处理程序也可以在一定程度上使用。
获得非阻塞键盘输入的另一种方法是打开设备文件并阅读它!
你必须知道你正在寻找的设备文件,/ 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; }
如果你感到高兴只是抓住Control-C,这是一个成功的交易。 如果你真的想要非阻塞的I / O,但你不想要curses库,另一种方法是将锁,股票和桶移动到AT&T sfio
库 。 C stdio
上的库很好,但更灵活,线程安全,性能更好。 (sfio代表安全,快速的I / O。)
没有可移植的方式来做到这一点,但select()可能是一个好方法。 有关更多可能的解决方案,请参阅http://c-faq.com/osdep/readavail.html 。
你可以使用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; } } }
没有太多的工作:D
这是一个为你做这个功能。 您需要POSIX系统附带的termios.h
。
#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); }
打破这个: tcgetattr
获取当前的终端信息并将其存储在t
。 如果cmd
为1,则t
的本地输入标志被设置为非阻塞输入。 否则它被重置。 然后tcsetattr
将标准输入更改为t
。
如果您在程序结束时没有重置标准输入,则在shell中将会出现问题。