Неблокировать Получить персонаж

  • Платформа: 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; 
}