Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Nonblocking Get Character

  • Platform: Linux 3.2.0 x86 (Debian 7)
  • Compiler: GCC 4.7.2 (Debian 4.7.2-5)

I am writing a function that reads a single character from stdin if a character is already present in stdin. If stdin is empty the function is suppose to do nothing and return -1. I googled nonblocking input and was pointed to poll() or select(). First I tried to use select() but I could not get it to work so I tried poll() and reached the same conclusion. I am not sure what these functions do exactly but from what I understand of poll()'s documentation if I call it like so:

struct pollfd pollfds;
pollfds = STDIN_FILENO;
pollfds.events = POLLIN;
poll(pollfds, 1, 0);

if(pollfds.revents & POLLIN) will be true if "Data other than high-priority data may be read without blocking.". But poll() always times out in my test situation. How I test the function could be the problem but the functionality I want is exactly what I am testing for. Here is the function currently and the test situation as well.

#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;
}
like image 464
John Vulconshinz Avatar asked Dec 14 '13 20:12

John Vulconshinz


1 Answers

You're doing IO incorrectly, the POSIX manual and all other related documentation explicitly says never to mix IO done on FILE *s and file descriptors. You have very blatantly broken this rule. This rule is in place because FILE *s use buffering an this means that after a call to fgets there will be nothing left for read to get because fgets already read all pending data into a buffer that is kept in the FILE * structure.

So since there's no way to check if an ISO C IO method will block, we have to use file descriptors only.

Since we know that STDIN_FILENO is just the number 0, we can use

fcntl (0, F_SETFL, O_NONBLOCK);

this will turn all reads on file descriptor 0 to non-blocking mode, if you want to use a different file descriptor so that you can leave 0 alone then just use dup to duplicate it.

This way, you can stay away from poll completely and implement ngetc as

ssize_t 
ngetc (char *c)
{
  return read (0, c, 1);
}

or better yet, a macro

#define ngetc(c) (read (0, (c), 1))

Thus you get a simple implementation for what you're looking for.

Edit: If you are still worried about the terminal buffering the input, you can always change the terminal's settings, see How to disable line buffering of input in xterm from program? for more information on how to do this.

Edit: The reason that one could not use fgetc instead of read is for the same reason that using fgets won't work. When one of the FILE * IO functions is run, it reads all the data from the associated file descriptor. But once that happens, poll will never return because it's waiting on a file descriptor that's always empty, and the same thing will happen with read. Thus, I suggest that you follow the advice of the documentation and never mix streams (IO using fgets, fgetc, etc.) and file descriptors (IO using read, write, etc.)

like image 78
randomusername Avatar answered Sep 27 '22 22:09

randomusername