Search code examples
csocketsmkfifoposix-select

How to read() from clients and a FIFO using select() in C


I read() from clients connected to the server, and at the same time, I let select() be aware of data coming from a FIFO. Right now, when data is written to the FIFO, selects writes all data to the clients but keeps returning as if it was "ready-to-read". So the next read is set to -1 and errno == EAGAIN. It does this until it reaches fdmax. It works okay though.

But, why do I keep getting EAGAIN? and is there a better way to handle this? or is this the proper way?

Note: I'm passing O_RDWR|O_NONBLOCK so it can also keep read()ing data sent by clients and not just the FIFO.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <fcntl.h>
#include <errno.h>

#define PORT "9034"
int main(void) {
    fd_set master, read_fds;
    int fdmax, listener, newfd, sbytes, yes=1, i, j, rv; 
    struct sockaddr_storage remoteaddr; // client address
    socklen_t addrlen;
    char buf[256] = {0}, remoteIP[INET6_ADDRSTRLEN];
    struct addrinfo hints, *ai;

    FD_ZERO(&master);
    FD_ZERO(&read_fds);
    int fifo;
    if ((mkfifo("/tmp/fifo", 0666)) < 0)
        perror(strerror(errno));
    if ((fifo = open("/tmp/fifo", O_RDWR|O_NONBLOCK)) < 0)  
        perror(strerror(errno));

    // get us a socket and bind it
    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = AI_PASSIVE;
    rv = getaddrinfo(NULL, PORT, &hints, &ai);
    listener = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
    setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));
    bind(listener, ai->ai_addr, ai->ai_addrlen);
    freeaddrinfo(ai);
    listen(listener, 10);

    FD_SET (fifo, &master);
    FD_SET(listener, &master);
    fdmax = listener;
    for (;;) {
        read_fds = master;
        if (select(fdmax + 1, &read_fds, NULL, NULL, NULL) == -1) exit(4);

        for (i = 0; i <= fdmax; i++) {
            if (FD_ISSET(i, &read_fds)) {
                if (i == listener) {
                    addrlen = sizeof remoteaddr;
                    newfd = accept(listener, (struct sockaddr * ) & remoteaddr, & addrlen);
                    FD_SET(newfd, & master);
                    if (newfd > fdmax)
                        fdmax = newfd;
                } else if (i != fifo) {
                    recv(i, buf, sizeof buf, 0); 
                }   
            }
            if (FD_ISSET(fifo, &read_fds)) {
                sbytes = read (fifo, buf, sizeof (buf));
                if (sbytes == -1 && errno == EAGAIN)
                    continue;
                for(j = 0; j <= fdmax; j++) {
                    // send to everyone!
                    if (FD_ISSET(j, &master)) {
                        if (j != listener && j != i && j != fifo) {
                            if (send(j, buf, sbytes, 0) == -1) {
                                perror("send");
                            }   
                        }   
                    }   
                }   
            }   
        }   
    }   
    return 0;
}

Solution

  • for (i = 0; i <= fdmax; i++) {
    

    Here you are iterating the read_fds.

    if (FD_ISSET(fifo, &read_fds)) {
    

    Here you are testing whether fifo is readable. Every time around the loop. The first time, you read something, and send it; the next time around the loop, nothing has happened to the FIFO, but the condition above still holds, so you read again and get EAGAIN.

    This if block should be outside and below the loop.