views:

3855

answers:

7

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

+1  A: 

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

Aziz
A: 

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.

Don Werve
+1  A: 

use ncurses

soulmerge
+10  A: 

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. The 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.

Charlie Martin
Of course you can do non-blocking IO on a tty. This is how most single-threaded text ui programs work.
MarkR
So was it that you didn't *read* the answer, or didn't *understand* the answer?
Charlie Martin
Answer is factually false. You can `fcntl(0, F_SETFL, fcntl(0, GETFL) | O_NONBLOCK)` even if fd 0 is attached to a tty -- this is usually not recommended because it confuses everybody else using stdin, but it works.
ephemient
@ephemient, read the answer again. It doesn't say "you can't", it says "you don't." As I say, is it that you didn't *read* the answer, or didn't *understand* the answer?
Charlie Martin
I read it three times before commenting and still read it as s/do/ca/ all along. While I don't completely agree with the answer, it is consistent...
ephemient
@eph, don'tcha hate it when that happens? NP. As you say, you *can* make it nonblocking. Given the question, though, I was pretty certain he questioner was asking for raw I/O.
Charlie Martin
+2  A: 

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.

Pete Kirkham
+3  A: 

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>

// 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.

jwhitlock
It looks like you reversed your links. cc.byexamples... links to Pete Kirkham's profile and vice-versa.
Nick Meyer
Thanks - fixed the links
jwhitlock
A: 

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.
Tyler McHenry