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

8

48

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

Essen answered 4/4, 2009 at 18:37 Comment(1)
This discussion ("C non-blocking keyboard input") solved the problem for me: #449444Worsen
S
7

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.

Superstructure answered 4/4, 2009 at 18:47 Comment(5)
Of course you can do non-blocking IO on a tty. This is how most single-threaded text ui programs work.Farthest
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.Margeret
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...Margeret
@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.Superstructure
@Margeret I think it should be F_GETFLBluegill
B
33

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

2 sample executions:

$ ./a.out
fda 
You said: fda
$ ./a.out
$ 
Bluegill answered 30/5, 2015 at 16:42 Comment(3)
This is the correct answer. It cover both operating systems requested (just tested on GNU/Linux). Keep in mind that this solution will still buffer input. (e.g. you will need to press enter to pass your input to the program.Hildie
To make it more clear, I would use STDIN_FILENO instead of 0 for the file descriptorPhotofluorography
Note that this can break the terminal since stdin can be shared with future processes that may not expect stdin to be non-blocking. Try to use cat after running your program. And please, use a consistent indentation style.Pinta
S
26

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.

Struve answered 4/4, 2009 at 18:37 Comment(2)
It looks like you reversed your links. cc.byexamples... links to Pete Kirkham's profile and vice-versa.Occam
Great example source, does what I need it to.Eward
S
7

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.

Superstructure answered 4/4, 2009 at 18:47 Comment(5)
Of course you can do non-blocking IO on a tty. This is how most single-threaded text ui programs work.Farthest
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.Margeret
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...Margeret
@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.Superstructure
@Margeret I think it should be F_GETFLBluegill
W
7

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.

Wallford answered 4/4, 2009 at 18:59 Comment(0)
N
4

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

Norry answered 4/4, 2009 at 18:40 Comment(0)
I
4

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.
Ingurgitate answered 14/8, 2009 at 23:37 Comment(0)
H
2

Let`s see how it done in one of Linux utilites. For example, perf/builtin-top.c sources (simplified):

static void *display_thread(void *arg)
{
    struct pollfd stdin_poll = { .fd = 0, .events = POLLIN };
    struct termios save;
    set_term_quiet_input(&save);
    while (!done) {
      switch (poll(&stdin_poll, 1, delay_msecs)) {
        ...
      }
    }
    tcsetattr(0, TCSAFLUSH, &save);
}

So, if you want to check if any data available, you can use poll() or select() like this:

#include <sys/poll.h>
...
struct pollfd pfd = { .fd = 0, .events = POLLIN };
while (...) {
  if (poll(&pfd, 1, 0)>0) {
    // data available, read it
  }
  ...
}

In this case you will receive events not on each key, but on whole line, after [RETURN] key is pressed. It's because terminal operates in canonical mode (input stream is buffered, and buffer flushes when [RETURN] pressed):

In canonical input processing mode, terminal input is processed in lines terminated by newline ('\n'), EOF, or EOL characters. No input can be read until an entire line has been typed by the user, and the read function (see Input and Output Primitives) returns at most a single line of input, no matter how many bytes are requested.

If you want to read characters immediately, you can use noncanonical mode. Use tcsetattr() to switch:

#include <termios.h>

void set_term_quiet_input()
{
  struct termios tc;
  tcgetattr(0, &tc);
  tc.c_lflag &= ~(ICANON | ECHO);
  tc.c_cc[VMIN] = 0;
  tc.c_cc[VTIME] = 0;
  tcsetattr(0, TCSANOW, &tc);
}

Simple programm (link to playground):

#include <stdio.h>
#include <unistd.h>
#include <sys/poll.h>
#include <termios.h>

void set_term_quiet_input()
{
  struct termios tc;
  tcgetattr(0, &tc);
  tc.c_lflag &= ~(ICANON | ECHO);
  tc.c_cc[VMIN] = 0;
  tc.c_cc[VTIME] = 0;
  tcsetattr(0, TCSANOW, &tc);
}

int main() { 
  struct pollfd pfd = { .fd = 0, .events = POLLIN };
  set_term_quiet_input();
  while (1) {
    if (poll(&pfd, 1, 0)>0) {
      int c = getchar();
      printf("Key pressed: %c \n", c);
      if (c=='q') break;
    }
    usleep(1000); // Some work 
  }
}
Haimes answered 2/8, 2022 at 8:20 Comment(1)
Nice answer containing async I/o with select() (mentioned) and poll() and also covering the mode issue in terminal. +1Sclerotomy
H
0

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.

Harrelson answered 4/4, 2009 at 18:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.