Spook
Spook

Reputation: 373

C++ - function select shows there are data in stdin after they were read

I have following code in my chat client:

fd_set fds;
struct timeval t;
t.tv_sec = 0;
t.tv_usec = 100;
int ret;

string message;

while(run)
{
    FD_ZERO(&fds);
    FD_SET(STDIN_FILENO, &fds);

    if((ret = select(STDIN_FILENO + 1, &fds, NULL, NULL, &t)) < 0)
    {
        exit(ERR_SELECT);
    }
    else if(ret > 0 && FD_ISSET(STDIN_FILENO, &fds))
    {
        message.clear();
        getline(cin, message);

        if(strlen(message.c_str()) > 0)
        {
            send_message(&message, client_socket);
        }
    }
}

It works fine when I type something while program is running. It reads message in stdin, sends it and then wait in loop for next input. But when I pass input to program on start like this:

echo "message" | ./chat_client

Or this:

printf "message\n" | ./chat_client

It reads and sends "message" but then in next iterations of loop it behaves like there is something in stdin but getline reads only empty string. And then when I write something, getline does not read it.

Does anyone know what causes it?

Thanks for any advice

Upvotes: 1

Views: 1254

Answers (1)

ilkkachu
ilkkachu

Reputation: 6517

getline() returns if it reaches end-of-file, which happens when everything coming from the pipe has been read. You need to explicitly test if cin.eof() returns true.

As for select() it's by design that it shows the file as "readable" if at end-of-file. That way the program can detect the connection was closed. The Linux manual for select() states that:

A file descriptor is considered ready if it is possible to perform a corresponding I/O operation (e.g., read(2) without blocking...

and a read() on a file descriptor at EOF returns a zero without blocking.


I'm not sure if you can get problems by selecting on an fd read through an istream, but this works for me (Linux/gcc):

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

int main()
{
  std::string message;
  while(1) {
    fd_set fds;
    FD_ZERO(&fds);
    FD_SET(STDIN_FILENO, &fds);
    int ret = select(1, &fds, NULL, NULL, NULL);
    if (ret == -1) break;
    getline(std::cin, message);
    if (std::cin.eof()) {
      std::cout << "eof." << std::endl;
      break;
    }
    std::cout << "read: " << message << std::endl;
  }
  return 0; 
}

Upvotes: 2

Related Questions