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
C non-blocking keyboard input
提问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 sigaction
to trap ctrl-c, or select
to 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 sigaction
call to register a signal handler for SIGINT
signal 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 系统上,您可以使用sigaction
call 为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 ncurses
library to put the terminal in cbreak mode and delay mode, then call getch()
, which will return ERR
if no character is ready:
select()
为了方便,有点太低级了。我建议您使用ncurses
库将终端置于 cbreak 模式和延迟模式,然后调用getch()
,ERR
如果没有字符准备好,它将返回:
WINDOW *w = initscr();
cbreak();
nodelay(w, TRUE);
At that point you can call getch
without 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 sfio
library. It's nice library patterned on C stdio
but 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.h
which comes with POSIX systems.
这是为您执行此操作的功能。您需要termios.h
POSIX 系统附带的。
#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: tcgetattr
gets the current terminal information and stores it in t
. If cmd
is 1, the local input flag in t
is set to non-blocking input. Otherwise it is reset. Then tcsetattr
changes 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 就会出现问题。