3
votes

I wrote this piece of code to show the basic working of how I would like to send some data (Strings) from the parent process to the child process. But I seem to have some problems. (I removed all error checking to make the code more readable)

When I run this piece of code I expect to see the two test strings to be displayed on the terminal, but I only see the first one.
When I uncomment the first “sleep(1)”, then I see both strings displayed.
But when I uncomment only the second “sleep(1)”, then I again only see the first string.

I suspect this problem has something to do with synchronization. That the strings get written to fast and the fifo write end closes before everything is read by the child process. That’s why we see the correct output when we introduce a sleep between the two write() commands.

But what I don’t understand is that we still get a faulty output when we only introduce a sleep after both write commands. Why can’t the child read both strings even if they are both written before it can read one?
How can I solve this problem? Do I need some synchronization code, and if so how should I implement this. Because I won’t write a “sleep(1)” after every write command.
And is the solution also viable for multiple processes that want to write to the same fifo? (but with still only one process that reads from the fifo)

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

int main(int argc, char const *argv[]) {
    mkfifo("test_fifo", 0666);
    pid_t pid = fork();

    if (pid == 0) {
        int fd = open("test_fifo", O_RDONLY);
        char data[400];
        int rc;
        do {
            rc = read(fd, data, 400);
            if (rc > 0) printf("Received data: %s\n", data);
        } while(rc > 0);
    }
    else {
        int fd = open("test_fifo", O_WRONLY);
        char * string1 = "This is the first test string";
        write(fd, string1, strlen(string1) + 1);
        //sleep(1);
        char * string2 = "This is the second test string";
        write(fd, string2, strlen(string2) + 1);
        //sleep(1);
        close(fd);
        wait(NULL);
    }
    return 0;
}

1
fd is non blocking and read returns 0. Or you read both strings at the same time at the first read call, but because %s prints up until first NUL, the second is not displayed. - KamilCuk
Have you tried attaching a debugger? The fact that you have no error checking is worrisome. Are you sure there's no fails in there? - jwdonahue
So as @KamilCuk points out, your read loop can exit before you're done writing all the bytes. Understanding the exact mechanics of the different behaviors due to the various combinations of sleep() statements, would require real-time instruction tracing of the entire OS and application. - jwdonahue

1 Answers

3
votes

You are receiving both strings at the same time at the first call to read. Because %s prints up until a zero byte, the second string is just not displayed. The poor mans synchronization with sleep(1) allows child to "catch" the messages in two distinct read call.

read returns the count of bytes read. Use that number. Change the parent code to:

    ssize_t rc;
    do {
        rc = read(fd, data, 400);
        if (rc > 0) {
            printf("Received data: ");
            for (size_t i = 0; i < rc; ++i) {
                if (data[i] == '\0') {
                    printf("\\x00");
                    continue;
                }
                printf("%c", data[i]);
            }
            printf("\n");
        }
    } while(rc >= 0);

and it shows on my pc:

Received data: This is the first test string\x00This is the second test string\x00

Why can’t the child read both strings even if they are both written before it can read one?

Well, the problem is not in reading, it's how you are displaying the data you read. (Still, reading could be improved, one should handle that pesky EAGAIN errno code).

How can I solve this problem?

If you want 1:1 relationship between read/write use a constant size packets or generally you have to know in advance how many bytes you want to read. Bytes written are "concatenated" together and lose structure. Or use pipe(3p) on which messages with size smaller then PIPE_BUF are guaranteed to be atomic. Or you could use POSIX message queue mq_receive/mq_send.

Or write a proper "deserializer", something that will buffer data and keep internal state and notify higher level only when a whole "message" was received, ie. detect when a zero byte was received in the stream of bytes and restore structure the the stream of bytes.