Home » Linux » How do you do non-blocking console I/O on Linux in C?

How do you do non-blocking console I/O on Linux in C?

Posted by: admin November 29, 2017 Leave a comment

Questions:

How do you do nonblocking console IO on Linux/OS X in C?

Answers:

You don’t, really. The TTY (console) is a pretty limited device, and you pretty much don’t do non-blocking I/O. What you do when you see something that looks like non-blocking I/O, say in a curses/ncurses application, is called raw I/O. In raw I/O, there’s no interpretation of the characters, no erase processing etc. Instead, you need to write your own code that checks for data while doing other things.

In modern C programs, you can simplify this another way, by putting the console I/O into a thread or lightweight process. Then the I/O can go on in the usual blocking fashion, but the data can be inserted into a queue to be processed on another thread.

Update

Here’s a curses tutorial that covers it more.

Questions:
Answers:

Like Pete Kirkham, I found cc.byexamples.com, and it worked for me. Go there for a good explanation of the problem, as well as the ncurses version.

My code needed to take an initial command from standard input or a file, then watch for a cancel command while the initial command was processed. My code is C++, but you should be able to use scanf() and the rest where I use the C++ input function getline().

The meat is a function that checks if there is any input available:

#include <unistd.h>
#include <stdio.h>
#include <sys/select.h>

// cc.byexamples.com calls this int kbhit(), to mirror the Windows console
//  function of the same name.  Otherwise, the code is the same.
bool inputAvailable()  
{
  struct timeval tv;
  fd_set fds;
  tv.tv_sec = 0;
  tv.tv_usec = 0;
  FD_ZERO(&fds);
  FD_SET(STDIN_FILENO, &fds);
  select(STDIN_FILENO+1, &fds, NULL, NULL, &tv);
  return (FD_ISSET(0, &fds));
}

This has to be called before any stdin input function. When I used std::cin before using this function, it never returned true again. For example, main() has a loop that looks like this:

int main(int argc, char* argv[])
{ 
   std::string initialCommand;
   if (argc > 1) {
      // Code to get the initial command from a file
   } else {
     while (!inputAvailable()) {
       std::cout << "Waiting for input (Ctrl-C to cancel)..." << std::endl;
       sleep(1);
     }
     std::getline(std::cin, initialCommand);
   }

   // Start a thread class instance 'jobThread' to run the command
   // Start a thread class instance 'inputThread' to look for further commands
   return 0;
}

In the input thread, new commands were added to a queue, which was periodically processed by the jobThread. The inputThread looked a little like this:

THREAD_RETURN inputThread()
{
  while( !cancelled() ) {
    if (inputAvailable()) {
      std::string nextCommand;
      getline(std::cin, nextCommand);
      commandQueue.lock();
      commandQueue.add(nextCommand);
      commandQueue.unlock();
    } else {
        sleep(1);
    }
  }
  return 0;
}

This function probably could have been in main(), but I’m working with an existing codebase, not against it.

For my system, there was no input available until a newline was sent, which was just what I wanted. If you want to read every character when typed, you need to turn off “canonical mode” on stdin. cc.byexamples.com has some suggestions which I haven’t tried, but the rest worked, so it should work.

Questions:
Answers:

I want to add an example:

#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>

int main(int argc, char const *argv[])

{
    char buf[20];
    fcntl(0, F_SETFL, fcntl(0, F_GETFL) | O_NONBLOCK);
    sleep(4);
    int numRead = read(0,buf,4);
    if(numRead > 0){
        printf("You said: %s", buf);
    }
}

When you run this program you have 4 seconds to provide input to standard in. If no input found, it will not block and will simply return.

2 sample executions:

Korays-MacBook-Pro:~ koraytugay$ ./a.out
fda 
You said: fda
Korays-MacBook-Pro:~ koraytugay$ ./a.out
Korays-MacBook-Pro:~ koraytugay$ 

Questions:
Answers:

I bookmarked “Non-blocking user input in loop without ncurses” earlier this month when I thought I might need non-blocking, non-buffered console input, but I didn’t, so can’t vouch for whether it works or not. For my use, I didn’t care that it didn’t get input until the user hit enter, so just used aio to read stdin.

Questions:
Answers:

Here’s a related question using C++ — Cross-platform (linux/Win32) nonblocking C++ IO on stdin/stdout/stderr

Questions:
Answers:

use ncurses

Questions:
Answers:

Another alternative to using ncurses or threads is to use GNU Readline, specifically the part of it that allows you to register callback functions. The pattern is then:

  1. Use select() on STDIN (among any other descriptors)
  2. When select() tells you that STDIN is ready to read from, call readline’s rl_callback_read_char()
  3. If the user has entered a complete line, rl_callback_read_char will call your callback. Otherwise it will return immediately and your other code can continue.
Questions:
Answers:

Not entirely sure what you mean by ‘console IO’ — are you reading from STDIN, or is this a console application that reads from some other source?

If you’re reading from STDIN, you’ll need to skip fread() and use read() and write(), with poll() or select() to keep the calls from blocking. You may be able to disable input buffering, which should cause fread to return an EOF, with setbuf(), but I’ve never tried it.

Leave a Reply

Your email address will not be published. Required fields are marked *