Неблокировать Получить персонаж
- Платформа: Linux 3.2.0 x86 (Debian 7)
- Компилятор: GCC 4.7.2 (Debian 4.7.2-5)
Я пишу функцию, которая читает один символ из stdin, если символ уже присутствует в stdin. Если stdin пуст, функция не должна ничего делать и возвращает -1. Я искал неблокирующий вход в Google и был отмечен poll() или select(). Сначала я попытался использовать select(), но я не мог заставить его работать, поэтому попробовал опрос() и пришел к такому же выводу. Я не уверен, что эти функции делают точно, но из того, что я понимаю в документации poll(), если я так называю:
struct pollfd pollfds;
pollfds = STDIN_FILENO;
pollfds.events = POLLIN;
poll(pollfds, 1, 0);
if (pollfds.revents и POLLIN) будет истинным, если "Данные, отличные от высокоприоритетных данных, могут быть прочитаны без блокировки". Но опрос() всегда бывает в моей тестовой ситуации. Как я могу проверить функцию, может быть проблема, но функциональность, которую я хочу, именно то, что я тестирую. Вот и текущая функция и тестовая ситуация.
#include <poll.h>
#include <stdio.h>
#include <unistd.h>
int ngetc(char *c)
{
struct pollfd pollfds;
pollfds.fd = STDIN_FILENO;
pollfds.events = POLLIN;
poll(&pollfds, 1, 0);
if(pollfds.revents & POLLIN)
{
//Bonus points to the persons that can tell me if
//read() will change the value of '*c' if an error
//occurs during the read
read(STDIN_FILENO, c, 1);
return 0;
}
else return -1;
}
//Test Situation:
//Try to read a character left in stdin by an fgets() call
int main()
{
int ret = 0;
char c = 0;
char str[256];
//Make sure to enter more than 2 characters so that the excess
//is left in stdin by fgets()
fgets(str, 2, stdin);
ret = ngetc(&c);
printf("ret = %i\nc = %c\n", ret, c);
return 0;
}
Ответы
Ответ 1
Вы неправильно выполняете IO, руководство POSIX и вся другая соответствующая документация явно указывают, что никогда не следует смешивать IO в FILE *
и дескрипторах файлов. Вы очень грубо нарушили это правило. Это правило существует, потому что FILE *
использует буферизацию, это означает, что после вызова fgets
там для read
ничего не останется, потому что fgets
уже считывает все ожидающие данные в буфер, который хранится в структуре FILE *
.
Так как нет способа проверить, будет ли использоваться метод ISO C IO, мы должны использовать только дескрипторы файлов.
Так как мы знаем, что STDIN_FILENO
- это просто число 0, мы можем использовать
fcntl (0, F_SETFL, O_NONBLOCK);
это превратит все read
в дескриптор файла 0 в неблокирующий режим, если вы хотите использовать другой файловый дескриптор, чтобы что вы можете оставить 0 в покое, просто используйте dup
, чтобы дублировать его.
Таким образом, вы можете полностью отказаться от poll
и реализовать ngetc
как
ssize_t
ngetc (char *c)
{
return read (0, c, 1);
}
или еще лучше, макрос
#define ngetc(c) (read (0, (c), 1))
Таким образом, вы получаете простую реализацию для того, что вы ищете.
Изменить: Если вы все еще беспокоитесь о буферизации ввода терминала, вы всегда можете изменить настройки терминала, см. Как отключить буферизацию строк ввода в xterm из программы? для получения дополнительной информации о том, как это сделать.
Изменить: Причина, по которой нельзя использовать fgetc
вместо read
, по той же причине, что использование fgets
не будет работать. Когда запускается одна из функций FILE *
IO, она считывает все данные из соответствующего файлового дескриптора. Но как только это произойдет, poll
никогда не вернется, потому что он ожидает файлового дескриптора, который всегда пуст, и то же самое произойдет с read
. Таким образом, я предлагаю вам следовать рекомендациям документации и потокам смешивания никогда (IO с использованием fgets
, fgetc
и т.д.) И файловыми дескрипторами (IO с использованием read
, write
, и т.д.)
Ответ 2
В коде есть две проблемы.
-
В соответствии с manual poll
, присваивание 0 таймауту вернет немедленно
Если значение таймаута равно 0, poll() должен немедленно вернуться. Если значение таймаута равно -1, poll() блокируется до тех пор, пока не будет выполнено требуемое событие или до тех пор, пока вызов не будет прерван.
-
fgets
не делает то, что вы ожидаете, это из библиотеки stdio и буферизует чтение. Предположим, вы ввели 3 буквы и нажмите enter, после fgets
третья буква не будет доступна для poll
.
Итак, закомментируйте строку fgets и назначьте -1 таймауту в poll
и запустите его снова, чтобы увидеть, что вы хотите.
Ответ 3
Я не получил ожидаемого поведения с ответом выше, и мне действительно пришлось принять во внимание этот ответ
которые устанавливают TTY в неканоническом режиме.
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <termios.h>
int main(int argc, char *argv[])
{
struct termios t;
tcgetattr(0, &t);
t.c_lflag &= ~ICANON;
tcsetattr(0, TCSANOW, &t);
fcntl(0, F_SETFL, fcntl(0, F_GETFL) | O_NONBLOCK);
printf("Starting loop (press i or q)...\n");
for (int i = 0; ; i++) {
char c = 0;
read (0, &c, 1);
switch (c) {
case 'i':
printf("\niteration: %d\n", i);
break;
case 'q':
printf("\n");
exit(0);
}
}
return 0;
}