You are here

Non-blocking Reading of Stdin in C++

[i]Updated with Windows version 5/26/2009[/i]

I recently added the ability for the Coldest dedicated server to accept commands from its stdin. During the process I discovered that getline(cin, s) is a blocking call, which had two important impacts in my case. First, it meant that I couldn't include the call in my main server thread or it would only allow the server to run one loop per command. Obviously a bad thing. Second, and more difficult to resolve, the thread containing the getline would not end properly until the getline call returned, which meant that either I had to find a way to write to my own stdin (a method for doing so did not immediately present itself), or find a way to make sure the getline would not block.

It turns out that non-blocking reading of stdin is not as easy as you might think. I lost count of the number of message board and mailing list posts I found that were either unanswered or had answers that boiled down to "You can't." Fortunately that's not strictly true. It's correct that there is no way (that I found) to unblock the getline call in a fashion that couldn't fail on some systems, but there is a way to use getline without blocking its entire thread. Since I had so much trouble finding a method on Google I figured I would document mine here for anyone else in a similar situation.

The trick is to treat stdin as a socket. This allows you to use poll and select to determine whether the getline call (or any other blocking cin call for that matter) would return immediately. You simply need to poll the stdin file descriptor before calling it and if poll does not indicate that it is ready for reading then you don't make the getline call. This would actually allow you to put the getline back into the main thread if you were so inclined, but since I had already split it off as a separate one I left it that way.

Here's my code for doing just that:

[b]Notes on Windows:[/b] Credit to [url=http://www.velocityreviews.com/forums/t336612-using-selectsysstdin-on-wi... thread[/url] for the ideas implemented here. The only problem is that WaitForSingleObject returns even if a getline may block, and trying to use cin.get fails as well because for some reason after the first enter keypress it hangs, even though WaitForSingleObject returns that it is ready. I was not able to find a solution so for my purposes I am strongly suggesting the server be run from my PyQt4 frontend, which only sends full lines of input making the getline safe as long as there is any input. The code below is fine in the regular Windows command-line too as long as you know you will never try to end the input thread when it might be blocking on the getline call. In my case that can happen if someone types some characters but does not press enter to execute what they've typed, and the server is then ended remotely from a client using a remote console command. This will hang until enter is pressed in the local command window. If the local command line is the only input method this probably won't be an issue, but for more complex programs that can't use my frontend kludge I'm afraid I don't have a good answer for you.

[code]
int ServerInput(void* dummy)
{
#ifdef DEDICATED
setsighandler();
// Check for commands being input from stdin
string command;
#ifndef WIN32
pollfd cinfd[1];
// Theoretically this should always be 0, but one fileno call isn't going to hurt, and if
// we try to run somewhere that stdin isn't fd 0 then it will still just work
cinfd[0].fd = fileno(stdin);
cinfd[0].events = POLLIN;
#else
HANDLE h = GetStdHandle(STD_INPUT_HANDLE);
#endif
while (running)
{
#ifndef WIN32
if (poll(cinfd, 1, 1000))
#else
// This is problematic because input on Windows is not line-buffered so this will return
// even if getline may block. I haven't found a good way to fix it, so for the moment
// I just strongly suggest only running the server from the Python frontend, which does
// line buffer input. This does work okay as long as the user doesn't enter characters
// without pressing enter, and then try to end the server another way (say a remote
// console command), in which case we'll still be waiting for the stdin EOL and hang.
if (WaitForSingleObject(h, 0) == WAIT_OBJECT_0)
#endif
{
getline(cin, command);
console.Parse(command, false);
}
}
#endif
return 0;
}
[/code]

Note that if all you want is keyboard input you're probably better off using the SDL input functions as they'll wrap all of this OS-specific nastiness for you. However, I couldn't do that because I wanted to be able to pipe input into the server from a GUI console as well and as far as I know SDL doesn't have a method for handling that.