C неблокирующий ввод с клавиатуры
Я пытаюсь написать программу на C (в Linux), которая зацикливается до тех пор, пока пользователь не нажимает клавишу, но не требует нажатия клавиши для продолжения каждого цикла.
Есть ли простой способ сделать это? Я полагаю, что я мог бы сделать это с select()
но это похоже на большую работу.
В качестве альтернативы, есть ли способ поймать нажатие ctrl-c для очистки перед закрытием программы вместо неблокирующего ввода-вывода?
11 ответов
Как уже говорилось, вы можете использовать sigaction
поймать в ловушку Ctrl-C, или select
отловить любой стандартный ввод.
Тем не менее, обратите внимание, что с последним методом вам также нужно установить TTY так, чтобы он был в символьном режиме, а не в линейном режиме. Последнее является значением по умолчанию - если вы введете строку текста, она не будет отправлена на стандартный экран запущенной программы, пока вы не нажмете ввод.
Вам нужно будет использовать tcsetattr()
функция для отключения режима ICANON, и, возможно, также отключить ECHO. Из памяти вы также должны вернуть терминал в режим ICANON, когда программа выйдет!
Просто для полноты, вот код, который я только что добавил (nb: без проверки ошибок!), Который устанавливает TTY Unix и эмулирует 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 и delay, затем вызвать getch()
, который вернется ERR
если ни один персонаж не готов:
WINDOW *w = initscr();
cbreak();
nodelay(w, TRUE);
В этот момент вы можете позвонить getch
без блокировки.
В системах UNIX вы можете использовать sigaction
вызов, чтобы зарегистрировать обработчик сигнала для SIGINT
сигнал, который представляет последовательность клавиш Control+C. Обработчик сигнала может установить флаг, который будет проверяться в цикле, чтобы он соответствующим образом прерывался.
Другой способ получить неблокирующий ввод с клавиатуры - открыть файл устройства и прочитать его!
Вы должны знать файл устройства, который вы ищете, один из /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;
}
Вы, вероятно, хотите 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();
Если вы счастливы, просто поймав Control-C, это уже готово. Если вам действительно нужен неблокирующий ввод / вывод, но вам не нужна библиотека curses, другой альтернативой является перемещение блокировки, запаса и ствола в AT&T sfio
библиотека Это хорошая библиотека по образцу C stdio
но более гибкий, поточно-ориентированный и работает лучше. (Sfio означает безопасный, быстрый ввод / вывод.)
Библиотека curses может быть использована для этой цели. Конечно, select()
и обработчики сигналов могут быть использованы в определенной степени.
Вот функция, чтобы сделать это для вас. Тебе нужно 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, флаг локального ввода в t
установлен на неблокирующий вход. В противном случае он сбрасывается. затем tcsetattr
изменяет стандартный ввод на t
,
Если вы не сбросите стандартный ввод в конце вашей программы, у вас будут проблемы в вашей оболочке.
Вы можете сделать это, используя команду 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
Нет переносимого способа сделать это, но select() может быть хорошим способом. Смотрите http://c-faq.com/osdep/readavail.html для более возможных решений.
На С++ я сделал так:
#include <chrono>
#include <thread>
using namespace std::chrono_literals;
void OnEnter()
{
while (true)
{
getchar();
// do something when enter is pressed
}
}
int main()
{
std::thread keyBoardCommands(OnEnter);
while(true)
{
// code for main loop
std::this_thread::sleep_for(16ms);
}
}
Этот код будет независимым от платформы.