我正在尝试使用C语言编写一个程序(在Linux上),直到用户按下某个键为止,该程序才会循环运行,但不要求按键即可继续每个循环。
有没有简单的方法可以做到这一点?我认为我可以做到,select()
但这似乎需要大量工作。
另外,有没有办法在程序关闭之前捕获ctrl-c按键进行清理,而不是非阻塞io?
Answers:
如前所述,您可以使用sigaction
ctrl-cselect
陷阱或任何标准输入。
但是请注意,使用后一种方法时,您还需要设置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 */
}
(void)
是获得该角色然后将其丢弃的位。
在UNIX系统上,可以使用sigaction
call为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();
获得非阻塞键盘输入的另一种方法是打开设备文件并读取它!
您必须知道要查找的设备文件,其中一个是/ 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&Tsfio
库。这是一个很好的基于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
nfds
应设置为1
这是为您执行此操作的功能。您需要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);
}
分解:tcgetattr
获取当前的终端信息并将其存储在中t
。如果cmd
为1,则本地输入标志int
设置为非阻塞输入。否则将被重置。然后tcsetattr
将标准输入更改为t
。
如果您在程序结束时不重置标准输入,则您的Shell中将出现问题。