yat0
yat0

Reputation: 967

C FIFO's - How to read server's stdin while waiting for client requests

I'm implementing a simple ipc system using linux named pipes in C.

I have this server code:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include "external/paths.h"
#include "external/sv.h"
#include "external/tags.h"

int main(int argc, char *argv[])
{
    int fd, bytes_read;
    char request[200];

    // create fifo
    mknod(FIFO_SERVER, S_IFIFO | 0666, 0);

    puts("Servidor initialized.\nWaiting for client requests.");

    // open created fifo
    fd = open(FIFO_SERVER, O_RDONLY);

    while(1)
    {
        if( (bytes_read = read(fd, request, LEN_CL_REQUEST)) == -1 )
            perror("error read()");

        if(bytes_read == 0)
            continue;

        if(bytes_read > 0)
        {
            printf("Request read: %s\n", request);
            // answer back
        }
    } 

    close(fd);
    unlink(FIFO_SERVER);

    return 0;
}

I'm ommiting the client because my question is only related with the server. The communication is working fine, I can read requests from the client and I can answer them. Now, lets say I want to, at anytime, be able to quit the server when the key 'Q' is pressed.. I can't do this because my code blocks on the read statement waiting for another client request, so I have no way to read the stdin..

Is something like this possible to do? I'm thinking in something like non-blocking the read statement and try to read stdin for a few seconds, then check again for incoming requests.. I've been searching but I haven't found anything similar.

UPDATE:

I followed Jean-Baptiste Yunès approach but it turns out that select is only detecting the fifo events, I don't really know why.

This is the code I'm testing:

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main()
{
    int result, fd, maxDescriptor;
    char input[20], texto[100];

    mknod("fifo", S_IFIFO | 0666, 0);
    fd = open("fifo", O_RDWR); // RDWR to avoid EOF return to select

    fd_set readset;
    FD_ZERO(&readset);
    FD_SET(fileno(stdin), &readset);
    FD_SET(fd, &readset);

    maxDescriptor = fileno(stdin) > fd ? fileno(stdin) : fd;

    while(1)
    {
        result = select(maxDescriptor+1, &readset, NULL, NULL, NULL);

        if(result == -1)
            perror("select()");
        else if(result)
        {
            puts("data available.");

            if( FD_ISSET(fileno(stdin), &readset) )
            {
                scanf("%s", input);
                printf("%s\n", input);

                if( strcmp(input, "Q") == 0 )
                    break;
            }

            if( FD_ISSET(fd, &readset) )
            {
                read(fd, texto, 100);
                printf("lido:\n%s\n", texto);
            }
        }
        else
            puts("no data.");
    }

    unlink("fifo");
    return 0;
}

UPDATE 2:

As Jean-Baptiste Yunès pointed out, there's need to reset fd_set since it doesn't reset automatically.

Here's the final working code:

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main()
{
    int result, fd, maxDescriptor;
    char input[20], texto[100];

    mknod("fifo", S_IFIFO | 0666, 0);
    fd = open("fifo", O_RDWR); // RDWR to avoid EOF return to select

    fd_set readset;
    FD_ZERO(&readset);
    FD_SET(fileno(stdin), &readset);
    FD_SET(fd, &readset);

    maxDescriptor = fileno(stdin) > fd ? fileno(stdin) : fd;

    while(1)
    {
        result = select(maxDescriptor+1, &readset, NULL, NULL, NULL);

        if(result == -1)
            perror("select()");
        else if(result)
        {
            puts("data available.");

            if( FD_ISSET(fileno(stdin), &readset) )
            {
                scanf("%s", input);
                printf("%s\n", input);

                if( strcmp(input, "Q") == 0 )
                    break;
                }

            if( FD_ISSET(fd, &readset) )
            {
                read(fd, texto, 100);
                printf("lido:\n%s\n", texto);
            }

            FD_SET(fileno(stdin), &readset);
            FD_SET(fd, &readset);
        }
        else
            puts("no data.");
    }

    unlink("fifo");
    return 0;
}

Upvotes: 1

Views: 1230

Answers (1)

Jean-Baptiste Yun&#232;s
Jean-Baptiste Yun&#232;s

Reputation: 36391

You have to use select. You need to wait on both channels: something from the pipe or something from stdin but you never know which one to read. The purpose of select is to make your process wait on any channel for read or write.

fd_set readset;
FD_ZERO(&readset);        // empty set of descriptor to select on
FD_SET(fd, &readset);     // add the pipe
FD_SET(stdin, &readset);  // add stdin
result = select(fd + 1, &readset, NULL, NULL, NULL); // now wait for something to read on at least one channel (pipe or stdin)
if (result>0) {
    if (FD_ISSET(fd, &readset)) { // test for pipe availability
        // now read the pipe
    }
    if (FD_ISSET(stdin, &readset)) { // test for stdin availability
        // now read stdin
    }
}

Upvotes: 4

Related Questions