Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I use getline without blocking for input?

Is there any method to call getline() and, if there is no input given, not to block and waiting?

I have the following code:

 while(true){
    if(recv(sd, tBuffer, sizeof(tBuffer), MSG_PEEK | MSG_DONTWAIT) > 0) break;
    getline(cin,send);
 }

I want to wait for an input, but if I receive some data at sd socket, I want stop waiting for the data and exit the while. My code right now, just stucks on first iteration at getline(). I want to evaluate getline(), and if is no input available, go back at if.

Is this possible?

PS: I tried with cin.peek(), but that blocks for input too.

like image 431
Darius Avatar asked Mar 10 '23 04:03

Darius


2 Answers

You should be able to do this by setting non-blocking mode on standard input, file descriptor 0:

int flags = fcntl(0, F_GETFL, 0);
fcntl(0, F_SETFL, flags | O_NONBLOCK);

Now, if there's no input available, the underlying read() system call will return 0, and std::cin will think that this is end of file, and set eof() on std::cin.

When you wish to read from standard input again, just clear() the stream's state.

The only complicating factor here is that this makes it difficult to detect a real end-of-file condition on std::cin. Not much a problem when standard input is an interactive terminal; but if standard input can be a file this is going to be an issue.

In that case, your only realistic option is to forego std::cin completely, put non-blocking mode on file descriptor 0, poll() or select() it, to determine when there's something to read, then read() it.

Although you could also use poll() or select() with std::cin, this is going to get complicated, because you will need to explicitly check if there's anything already buffered in std::cin's streambuf, because that would, obviously, preempt any kind of poll() or select() checking; but by attempting to read something from std::cin, you still run the risk of reading the buffered data, then attempting to read() from the underlying file descriptor that's now in non-blocking mode, this resulting in a fake end-of-file condition.

To summarize: you need invest some additional time reading and understanding how file streams, and stream buffers work; and how file descriptors actually work, and how non-blocking mode works; in order to figure out the correct logic you will need to use.

Oh, and if you insist on going the non-blocking route with std::cin, and getline(), you will have no easy way to determine if the string returned by getline() ends because getline() actually read a newline from standard input, or it reached a premature fake-end of file condition and not the entire line of input has actually been read.

So, with non-blocking mode, and std::cin, you'll be pretty much forced to use read(), instead of getline().

like image 111
Sam Varshavchik Avatar answered Mar 12 '23 17:03

Sam Varshavchik


The istream::getsome() method can be used to perform non-blocking reads. You can use it to build a non-blocking equivalent of std::getline.

bool getline_async(std::istream& is, std::string& str, char delim = '\n') {

    static std::string lineSoFar;
    char inChar;
    int charsRead = 0;
    bool lineRead = false;
    str = "";

    do {
        charsRead = is.readsome(&inChar, 1);
        if (charsRead == 1) {
            // if the delimiter is read then return the string so far
            if (inChar == delim) {
                str = lineSoFar;
                lineSoFar = "";
                lineRead = true;
            } else {  // otherwise add it to the string so far
                lineSoFar.append(1, inChar);
            }
        }
    } while (charsRead != 0 && !lineRead);

    return lineRead;
}

This functions works identically to the original std::getline() function except it always returns instantly. I've got it on my gists because it comes in handy occasionally.

like image 24
PeteBlackerThe3rd Avatar answered Mar 12 '23 17:03

PeteBlackerThe3rd