3

So, my PREVIOUS POST was flagged for being off topic, too vague, and asking for opinions and recommendations for useful code. It did those things, so I am re-posting as a question on code I am working on. Thanks to those in the previous post, I was able to piece what I have here from reviewing what you guys said.

The focus here is the Parent/Child relationship and the use of fork() and pipe() to get the desired affect.

The project is a POSIX card game where the parent (dealer) forks into 5 children (players) with their own pipe from the parent. The parent deals cards to the players (at least 5 cards each) until one player has a 3 of a kind(no discard). The winning child transmits the winning hand and EOF (exit(0)) to the parent to declare victory. That triggers the parent to print the winning player and transmit EOF to the remaining children to close them out (exit(1)). The parent then closes. If the parent reaches the end of the deck without a winner, it transmits EOF to the children, waits for them to exit(1)...then closes.

The main question I had is how the child programs read each card individually rather than reading the same value from the pipe over and over. I think I am missing a method to sync the Parent writing to the pipe and the Children reading from the pipe.

I am completely new to this guys, so any help is greatly appreciated. Many thanks.

CODE UPDATED: (compiles with a lot of issues) Complete and compiles but I clearly have trouble with the pipes and relaying data through them. There's alot wrong here from failing to play the game, to failing to read from or write to the pipes, to leaving zombie processes. I'd appreciate any input on my mess. lol

This is my output when I run the program (play.c):

os@debian:~/Documents/cpsc351/projects/assn2$ gcc -o play play.c
os@debian:~/Documents/cpsc351/projects/assn2$ ./play
Pipe Success...toChild 1 created.
Pipe Success...toParent 1 created.
Pipe Success...toChild 2 created.
Pipe Success...toParent 2 created.
Pipe Success...toChild 3 created.
Pipe Success...toParent 3 created.
Pipe Success...toChild 4 created.
Pipe Success...toParent 4 created.
Pipe Success...toChild 5 created.
Pipe Success...toParent 5 created.
Parent: All players are at the table.  Dealing cards... 
 30  5C to player 1
 51  KS to player 2
 9  10H to player 3
 25  KD to player 4
 6  7H to player 5
 18  6D to player 1
 45  7S to player 2
 29  4C to player 3
 37  QC to player 4
 12  KH to player 5
 7  8H to player 1
 19  7D to player 2
 20  8D to player 3
 49  JS to player 4
 35  10C to player 5
 15  3D to player 1
 5  6H to player 2
 36  JC to player 3
 0  AH to player 4
 22  10D to player 5
 48  10S to player 1
 27  2C to player 2
 42  4S to player 3
 16  4D to player 4
 32  7C to player 5
 4  5H to player 1
 14  2D to player 2
 41  3S to player 3
 39  AS to player 4
 1  2H to player 5
 26  AC to player 1
 46  8S to player 2
 34  9C to player 3
 11  QH to player 4
 24  QD to player 5
 17  5D to player 1
 31  6C to player 2
 44  6S to player 3
 40  2S to player 4
 3  4H to player 5
 21  9D to player 1
 50  QS to player 2
 13  AD to player 3
 33  8C to player 4
 23  JD to player 5
 43  5S to player 1
 2  3H to player 2
 28  3C to player 3
 47  9S to player 4
 38  KC to player 5
 10  JH to player 1
 8  9H to player 2
Child: Fork Success...Player 4 is sitting at the table.
Child: Player 4 is dealt a KD. Hand Total = 1 cards.
Child: Player 4 is dealt a QC. Hand Total = 2 cards.
Child: Player 4 is dealt a JS. Hand Total = 3 cards.
Child: Player 4 is dealt a AH. Hand Total = 4 cards.
Child: Player 4 is dealt a 4D. Hand Total = 5 cards.
Child: Player 4 is dealt a AS. Hand Total = 6 cards.
Child: Player 4 is dealt a QH. Hand Total = 7 cards.
Child: Player 4 is dealt a 2S. Hand Total = 8 cards.
Child: Player 4 is dealt a 8C. Hand Total = 9 cards.
Child: Player 4 is dealt a 9S. Hand Total = 10 cards.
Child: Fork Success...Player 5 is sitting at the table.
Child: Player 5 is dealt a 7H. Hand Total = 1 cards.
Child: Player 5 is dealt a KH. Hand Total = 2 cards.
Child: Player 5 is dealt a 10C. Hand Total = 3 cards.
Child: Player 5 is dealt a 10D. Hand Total = 4 cards.
Child: Player 5 is dealt a 7C. Hand Total = 5 cards.
Child: Player 5 is dealt a 2H. Hand Total = 6 cards.
Child: Player 5 is dealt a QD. Hand Total = 7 cards.
Child: Player 5 is dealt a 4H. Hand Total = 8 cards.
Child: Player 5 is dealt a JD. Hand Total = 9 cards.
Child: Player 5 is dealt a KC. Hand Total = 10 cards.
Child: Player 5 has left the table.
os@debian:~/Documents/cpsc351/projects/assn2$ Child: Player 4 has left the table.
Child: Fork Success...Player 3 is sitting at the table.
Child: Player 3 is dealt a 10H. Hand Total = 1 cards.
Child: Player 3 is dealt a 4C. Hand Total = 2 cards.
Child: Player 3 is dealt a 8D. Hand Total = 3 cards.
Child: Player 3 is dealt a JC. Hand Total = 4 cards.
Child: Player 3 is dealt a 4S. Hand Total = 5 cards.
Child: Player 3 is dealt a 3S. Hand Total = 6 cards.
Child: Player 3 is dealt a 9C. Hand Total = 7 cards.
Child: Player 3 is dealt a 6S. Hand Total = 8 cards.
Child: Player 3 is dealt a AD. Hand Total = 9 cards.
Child: Player 3 is dealt a 3C. Hand Total = 10 cards.
Child: Player 3 has left the table.
Child: Fork Success...Player 2 is sitting at the table.
Child: Player 2 is dealt a KS. Hand Total = 1 cards.
Child: Player 2 is dealt a 7S. Hand Total = 2 cards.
Child: Player 2 is dealt a 7D. Hand Total = 3 cards.
Child: Player 2 is dealt a 6H. Hand Total = 4 cards.
Child: Player 2 is dealt a 2C. Hand Total = 5 cards.
Child: Player 2 is dealt a 2D. Hand Total = 6 cards.
Child: Player 2 is dealt a 8S. Hand Total = 7 cards.
Child: Player 2 is dealt a 6C. Hand Total = 8 cards.
Child: Player 2 is dealt a QS. Hand Total = 9 cards.
Child: Player 2 is dealt a 3H. Hand Total = 10 cards.
Child: Player 2 is dealt a 9H. Hand Total = 11 cards.
Child: Player 2 has left the table.
Child: Fork Success...Player 1 is sitting at the table.
Child: Player 1 is dealt a 5C. Hand Total = 1 cards.
Child: Player 1 is dealt a 6D. Hand Total = 2 cards.
Child: Player 1 is dealt a 8H. Hand Total = 3 cards.
Child: Player 1 is dealt a 3D. Hand Total = 4 cards.
Child: Player 1 is dealt a 10S. Hand Total = 5 cards.
Child: Player 1 is dealt a 5H. Hand Total = 6 cards.
Child: Player 1 is dealt a AC. Hand Total = 7 cards.
Child: Player 1 is dealt a 5D. Hand Total = 8 cards.
Child: Player 1 has at least "3 of a Kind". Hand Total = 8 cards.

Current Code:

#define _POSIX_C_SOURCE 200809L

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

#include "cards.h"
#include "cards.c"

#define READ 0
#define WRITE 1

#define PLAYERS 5

int main(int argc, char *argv[])
{
    //loop declarations ***Would not let me initialize within a for-loop...c99 error.***
    int i;
    int j;  


    pid_t player[PLAYERS];
    int toChild_pipe[PLAYERS][2];
    int toParent_pipe[PLAYERS][2];
    int dealt_card;
    int card_count = 1;
    int player_count = 0;
    int status_forChild;
    int status_forParent;
    int player_card;

    for(i = 0; i < PLAYERS; i++)
    {
        // Create the pipes
        if (pipe(toChild_pipe[i]) < 0) 
        {
            perror("'To-Child' Pipe Error\n");
            exit(1);
        }

        if (pipe(toParent_pipe[i]) < 0) 
        {
            perror("'To-Parent' Pipe Error\n");
            exit(1);
        }

        // Fork the child (new player)
        player[i] = fork();
        if (player[i] < 0)
        {
            perror("Fork Error:");
            printf(" Player %d cloud not sit at table.\n", i+1);
            exit(1);
        } 

        else if (player[i] > 0) //Parent Process
        {     
            // Close unsed pipe ends in Parent
            close(toChild_pipe[i][READ]);
            close(toParent_pipe[i][WRITE]);
        } 

        else //(player[i] == 0)-- Child Process
        {     
            int player_num = (i+1);
            int player_card;
            int hand[13] = {0};         
            int player_card_count = 0;
            bool game_over = false;


            printf("Child: Fork Success...Player %d is sitting at the table.\n", player_num);

            // Close unsed pipe ends in Parent
            close(toParent_pipe[i][READ]);
            close(toChild_pipe[i][WRITE]);

            while(!game_over)
            {   

                if ((status_forChild = read(toChild_pipe[i][READ], &player_card, sizeof(player_card))) == 0)
                {
                    //EOF from parent.  Player lost.
                    game_over = true;
                    close(toParent_pipe[i][WRITE]);
                    close(toChild_pipe[i][READ]);
                    printf("Child: Player %d has left the table.\n", player_num);
                    exit(1);

                }

                else if (status_forChild == -1)
                {
                    perror("");
                    printf("Child %d: ERROR: Could not read from pipe.\n", i+1);
                    exit(1);
                }

                else
                {
                    //Players have 5 cards, loop through hand to check for winner. If yes, WIN.                 
                    if (player_card_count == 5)
                    {
                        for (j = 0; j < 13; j++)
                        {
                            if(hand[j] >=3)
                            {
                                //WINNER! Close process (status = 0) 
                                printf("Child: Player %d has at least. Hand Total = %d cards.\n"
                                    , player_num, rank(player_card));
                                close(toParent_pipe[i][WRITE]);
                                close(toChild_pipe[i][READ]);
                                exit(0);
                            }
                        }
                    }

                    //Read the current card value dealt, increment card value in hand array                 
                    int card_index = value_index(rank(player_card));
                    hand[card_index]++;
                    player_card_count++;


                    printf("Child: Player %d is dealt a %s%s. Hand Total = %d cards.\n", player_num, rank(player_card), 
                        suit(player_card), player_card_count);  

                    if ((hand[card_index] >= 3)&&(player_card_count > 5)) //at least (3 of a kind) and (> 5 card hand)
                    {
                        //WINNER! Close process (status = 0) 
                        printf("Child: Player %d has at least. Hand Total = %d cards.\n", player_num, rank(player_card));
                        close(toParent_pipe[i][WRITE]);
                        close(toChild_pipe[i][READ]);
                        exit(0);
                    } 
                }
            }
        }
    }

    shuffle();
    printf("Parent: All players are at the table.  Dealing cards... \n");

    while ((dealt_card = deal()) != EOF) 
    {

        //Card is written to the pipe for current player        
        if ((status_forParent = write(toChild_pipe[i][WRITE], &dealt_card, sizeof(dealt_card))) == -1)
        {           
            perror("");
            printf("Parent: ERROR: Could not read from pipe for Child %d.\n", i+1);
            exit(1);
        }

        //If child process exited with status = 0, child had 3 of a kind and wins game.
        else if (status_forParent == 0)
        {           
            printf("Parent: Player %d has WON!!!\n", player_count+1, rank(player_card));
            break;
        }   

        else    
        {

            printf(" %d  %s%s to player %d\n", dealt_card, rank(dealt_card), suit(dealt_card), player_count+1);

            if (player_count >= PLAYERS-1)
                player_count = 0;           
            else
                player_count++;


        }
    }
    // Close pipe ends
    close(toParent_pipe[i][READ]);
    close(toChild_pipe[i][WRITE]);
    wait(NULL);
    return 0;
}
Community
  • 1
  • 1
LazyBear
  • 343
  • 1
  • 7
  • 23
  • 3
    You'll need two pipes per child, one for each direction. The parent can use `select()` to monitor all the pipes the children can write to, to determine whether one is ready for reading. – Crowman Oct 13 '14 at 02:19
  • @Paul Griffiths So something like `int toChild_pipe[PLAYERS][2], toParent_pipe[PLAYERS][2];` ...then create both pipes in the same way? – LazyBear Oct 13 '14 at 02:25
  • 1
    Yep, exactly. Each child could write a particular character to it's write pipe when it's ready for a new card, for instance, or a different character if it's declaring victory. The parent could then read them in turn and act accordingly. If the parent always deals to and reads from each child in order, when it's ready, then you wouldn't even need `select()`, just loop and wait on `read()`. – Crowman Oct 13 '14 at 02:26
  • @Paul Griffiths Okay, I've added the extra pipe. Now, when you say wait on `read()`, do you mean the loop keeps checking the pipe for a certain value or an actual sys call like `wait()` ? – LazyBear Oct 13 '14 at 02:53
  • 1
    An actual system call. – Crowman Oct 13 '14 at 03:23
  • @Paul Griffiths Okay, that makes sense. But how does the child let the Parent know that the pipe has been read from? From your comment I initially thought it was looking for a "I'm ready" value from the READ pipe. I changed the code to reflect this where after reading the Child would write 111 to let the Parent know it can be dealt another card. A while loop stalls until that 111 can be read from the Parent's read pipe. How would a sys call work for here? More specifically, how does the Parent know the pipe's been read and it can proceed? Thanks so far, btw. – LazyBear Oct 13 '14 at 04:03
  • 1
    As a general rule, if you find yourself really grasping totally at straws, it's time to work on something simpler. That being said, I wrote an answer that doesn't make your game for you, but shows you an example mechanism which will do what you're looking for. For your question, the parent knows the pipe has been read because it waits for the child to write to its other pipe to tell it as much. – Crowman Oct 13 '14 at 04:05
  • Okay, I updated the code and it's compiling, but going bonkers. I re-read the project and realized that that I don't have to transmit the winning hand to the parent, but the Parent has to recognize that the winning hand exited with satus 0 [ exit(0) ]. The paent then sends EOF to the rest of the children which exit(1). – LazyBear Oct 13 '14 at 17:37

2 Answers2

2

The basic problem you have is that you depend on EOFs to detect things, but an EOF won't occur until ALL handles on the write end of the pipe are closed. So you have to be careful about closing ALL unneeded handles in all processes.

In your code you have a loop that creates pipes, then forks:

  • first you create two pipes for child 0
  • then fork child 0
  • child 0 closes parent ends of those pipes, while parent closes child ends of those pipes (good)
  • loop
  • create two pipes for child 1
  • fork child 1
  • child 1 closes parent ends of its pipes, while parent closes child ends.

At this point, you have a problem -- child 1 has inherited the parent ends of the pipe to child 0, but isn't closing them. Which means that child 0 won't be able to detect an EOF when it reads from parent. The same thing happens with child 2 and later children.

Chris Dodd
  • 119,907
  • 13
  • 134
  • 226
  • So how do I prevent that inheritance? Does that mean that child 2 inherits all parent ends from 0-1, child 3 inherits all parent ends from 0-1-2, ....etc? Also, the game kinda seems to work now (I used the wrong iterator in my main to loop in previous post). But there some big issues. 1) It outputs in blocks for each child rather than sequentially (weird). 2) Since the parent couldn't detect EOF from the winner, it keeps dealing cards out to the rest of the players. 3)It hangs at `wait()`. (I think I'm using that wrong. my intent for it is to wait for all Children to end, then proceed) – LazyBear Oct 13 '14 at 20:07
  • You can't prevent the inheritance -- fork inherits all open file descriptors automatically. You need to close them in the child after the fork if you don't want them open in the child. – Chris Dodd Oct 16 '14 at 04:24
0

This kind of looks like a code-only answer, but it's not really, since the comments in the code explain what's going on. It isn't the same game you're writing, but shows the mechanism you're looking for. You can adapt this kind of framework to your specific situation.

Code:

/*  Demonstration of multiplayer "game" with processes.
 *
 *  The parent sets up a number of processes equal to NUM_KIDS.
 *  It loops through each one in turn, and writes a character to
 *  each child, beginning with one. The child reads it, and if
 *  that character is the winning number, it writes back to the
 *  parent to notify it, and exits. If it's not the winning
 *  character, it writes a different character to the parent (which
 *  is ignored) and waits for another character to read. If it
 *  reads the game over character, it exits. 
 *
 *  It's not a very fun game, but demonstrates how a number of
 *  child processes can act as different players, how they can
 *  receive input from the parent and, based on that input, how
 *  they can determine a win situation and notify the parent of
 *  such.
 */


#define _POSIX_C_SOURCE 200809L

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>


#define NUM_KIDS 5

static const int CHILD_NO_WIN = 0;  /*  Child sends this if it doesnt win    */
static const int CHILD_WIN = 1;     /*  Child sends this if it wins          */
static const int GAME_OVER = 0;     /*  Child loses if it receives this      */
static const int WINNER = 13;       /*  Child wins if it receives this       */


/*  Convenience function to make a pair of pipes  */

void make_pipe_pair(int * pair1, int * pair2)
{
    if ( pipe(pair1) == -1 || pipe(pair2) == -1 ) {
        perror("couldn't create pipe");
        exit(EXIT_FAILURE);
    }
}


/*  Convenience function to close a pair of file descriptors  */

void close_pair(const int rfd, const int wfd)
{
    if ( close(rfd) == -1 || close(wfd) == -1 ) {
        perror("couldn't close file");
        exit(EXIT_FAILURE);
    }
}


/*  Main child process function  */

void child_func(const int rpipe, const int wpipe, const size_t child_id)
{
    char out_c = CHILD_NO_WIN;      /*  Character to write  */
    char in_c;                      /*  Character to read   */
    bool keep_reading = true;

    while ( keep_reading ) {

        /*  Read a single character from the parent  */

        ssize_t num_read;
        if ( (num_read = read(rpipe, &in_c, 1)) == -1 ) {
            perror("error reading from pipe in child");
            exit(EXIT_FAILURE);
        }
        else if ( num_read == 0 ) {
            printf("Pipe from parent closed to child %zu.\n", child_id);
            keep_reading = false;
        }
        else {
            printf("Child %zu read %d from parent.\n", child_id, in_c);

            if ( in_c == GAME_OVER ) {

                /*  We lost, so tell loop to end. No need to write()
                 *  to parent, since it already knows a previous
                 *  child won.                                        */

                printf("Child %zu got game over signal.\n", child_id);
                keep_reading = false;
            }
            else {
                if ( in_c == WINNER ) {

                    /*  We won, so send won signal to parent  */

                    out_c = 1;
                }

                /*  Write won signal to parent if we won, or
                 *  other character if we didn't.             */

                if ( write(wpipe, &out_c, 1) == -1 ) {
                    perror("error writing to pipe in child");
                    exit(EXIT_FAILURE);
                }
                else {
                    printf("Child %zu wrote %d to parent.\n", child_id, out_c);
                }
            }
        }
    }


    /*  Close file descriptors and exit  */

    close_pair(rpipe, wpipe);
}


/*  Main function  */

int main(void)
{
    int ptoc_fd[NUM_KIDS][2];   /*  Parent to child pipes    */
    int ctop_fd[NUM_KIDS][2];   /*  Child to parent pipes    */
    pid_t children[NUM_KIDS];   /*  Process IDs of children  */
    int winning_child;          /*  Holds number of winner   */


    /*  Create pipe pairs and fork children  */

    for ( size_t i = 0; i < NUM_KIDS; ++i ) {
        make_pipe_pair(ptoc_fd[i], ctop_fd[i]);

        if ( (children[i] = fork()) == -1 ) {
            perror("error calling fork()");
            return EXIT_FAILURE;
        }
        else if ( children[i] == 0 ) {
            printf("Child %zu created.\n", i + 1);
            close_pair(ctop_fd[i][0], ptoc_fd[i][1]);
            child_func(ptoc_fd[i][0], ctop_fd[i][1], i + 1);
            printf("Child %zu terminating.\n", i + 1);
            return EXIT_SUCCESS;
        }
        else {
            close_pair(ptoc_fd[i][0], ctop_fd[i][1]);
        }
    }


    /*  Set up game variables and enter main loop  */

    char out_c = 1;
    char in_c = 0;
    bool won = false;

    while ( !won ) {

        /*  Loop through each child  */

        for ( size_t i = 0; !won && i < NUM_KIDS; ++i ) {

            /*  Write next number to child  */

            if ( write(ptoc_fd[i][1], &out_c, 1) == -1 ) {
                perror("error writing to pipe");
                exit(EXIT_FAILURE);
            }
            else {
                printf("Parent wrote %d to child %zu.\n", out_c, i+1);
            }

            ++out_c;


            /*  Read status from child if game not over  */

            if ( !won ) {
                ssize_t num_read;
                if ( (num_read = read(ctop_fd[i][0], &in_c, 1)) == -1 ) {
                    perror("error reading from pipe");
                    return EXIT_FAILURE;
                }
                else if ( num_read == 0 ) {
                    printf("Pipe from child %zu closed.\n", i+1);
                }
                else {
                    printf("Parent read %d from child %zu.\n", in_c, i+1);
                    if ( in_c == CHILD_WIN ) {
                        printf("Parent got won signal from child %zu.\n", i+1);
                        won = true;
                        winning_child = i+1;
                    }
                }
            }
        }
    }


    /*  Clean up and harvest dead children  */

    out_c = 0;
    for ( size_t i = 0; i < NUM_KIDS; ++i ) {
        if ( write(ptoc_fd[i][1], &out_c, 1) == -1 ) {
            perror("error writing to pipe");
            exit(EXIT_FAILURE);
        }
        else {
            printf("Parent wrote %d to child %zu.\n", out_c, i + 1);
        }

        if ( waitpid(children[i], NULL, 0) == -1 ) {
            perror("error calling waitpid()");
            return EXIT_FAILURE;
        }
        else {
            printf("Successfully waited for child %zu.\n", i + 1);
        }

        close_pair(ptoc_fd[i][1], ctop_fd[i][0]);
    }


    /*  Show who won, and then quit.  */

    printf("Parent terminating. Child %d won.\n", winning_child);

    return EXIT_SUCCESS;
}

and output:

paul@thoth:~/src/sandbox/multipipe$ ./multipipe
Child 1 created.
Child 1 read 1 from parent.
Parent wrote 1 to child 1.
Child 1 wrote 0 to parent.
Child 3 created.
Parent read 0 from child 1.
Parent wrote 2 to child 2.
Child 2 created.
Child 2 read 2 from parent.
Parent read 0 from child 2.
Parent wrote 3 to child 3.
Child 3 read 3 from parent.
Parent read 0 from child 3.
Child 4 created.
Parent wrote 4 to child 4.
Child 3 wrote 0 to parent.
Child 2 wrote 0 to parent.
Child 4 read 4 from parent.
Child 5 created.
Parent read 0 from child 4.
Parent wrote 5 to child 5.
Child 4 wrote 0 to parent.
Child 5 read 5 from parent.
Parent read 0 from child 5.
Parent wrote 6 to child 1.
Child 5 wrote 0 to parent.
Child 1 read 6 from parent.
Parent read 0 from child 1.
Parent wrote 7 to child 2.
Child 1 wrote 0 to parent.
Child 2 read 7 from parent.
Parent read 0 from child 2.
Parent wrote 8 to child 3.
Child 3 read 8 from parent.
Parent read 0 from child 3.
Child 2 wrote 0 to parent.
Parent wrote 9 to child 4.
Child 4 read 9 from parent.
Parent read 0 from child 4.
Parent wrote 10 to child 5.
Child 3 wrote 0 to parent.
Child 4 wrote 0 to parent.
Child 5 read 10 from parent.
Child 5 wrote 0 to parent.
Parent read 0 from child 5.
Parent wrote 11 to child 1.
Child 1 read 11 from parent.
Parent read 0 from child 1.
Parent wrote 12 to child 2.
Child 2 read 12 from parent.
Child 1 wrote 0 to parent.
Parent read 0 from child 2.
Parent wrote 13 to child 3.
Child 3 read 13 from parent.
Parent read 1 from child 3.
Parent got won signal from child 3.
Parent wrote 0 to child 1.
Child 2 wrote 0 to parent.
Child 1 read 0 from parent.
Child 1 got game over signal.
Child 1 terminating.
Child 3 wrote 1 to parent.
Successfully waited for child 1.
Parent wrote 0 to child 2.
Child 2 read 0 from parent.
Child 2 got game over signal.
Child 2 terminating.
Successfully waited for child 2.
Parent wrote 0 to child 3.
Child 3 read 0 from parent.
Child 3 got game over signal.
Child 3 terminating.
Successfully waited for child 3.
Parent wrote 0 to child 4.
Child 4 read 0 from parent.
Child 4 got game over signal.
Child 4 terminating.
Successfully waited for child 4.
Parent wrote 0 to child 5.
Child 5 read 0 from parent.
Child 5 got game over signal.
Child 5 terminating.
Successfully waited for child 5.
Parent terminating. Child 3 won.
paul@thoth:~/src/sandbox/multipipe$

The output looks a little weird in place, and looks like processes are reading stuff before it's been written, but that's what happens when you work with asynchronous processes and don't bother to synchronize your input/output for demo purposes. The actual reading and writing to the pipes is well behaved and synchronized, it's just the debug messages going to standard output that look screwy. You should still be able to see what's going on.

Crowman
  • 25,242
  • 5
  • 48
  • 56
  • Very helpful for comprehending this. Especially after plugging it into a color coded editor. Thanks! – LazyBear Oct 13 '14 at 04:22
  • you should use `_exit(EXIT_SUCCESS)` in the child instead of `return EXIT_SUCCESS` – jfs Oct 13 '14 at 05:19
  • @J.F.Sebastian: Sometimes that'll be what you want to do, but it doesn't make any difference here, since there are no exit handlers and no unflushed stdio buffers when the children exit (provided nobody takes the slightly bizarre step of redirecting this output to a file, and even in that case, the stdio here is sufficiently badly behaved to begin with that it wouldn't make any practical difference). – Crowman Oct 13 '14 at 05:25
  • `printf()` after `fork()` may lead to garbled output. – jfs Oct 13 '14 at 05:25
  • the way the parent uses `write()`, `read()` can serialize the child processes if more than PIPE_BUF bytes are written. You should probably put a `select` loop into the parent. – jfs Oct 13 '14 at 05:28
  • @J.F.Sebastian: ? It'll only do that here if `PIPE_BUF` is zero, and it's required to be at least 512. A `select()` loop is completely unnecessary, since the whole point is to visit each child in succession. Letting them do their thing concurrently would break the turn-based logic of the game. I'm answering the OP's question, not presenting this as an a priori good example of where to use separate processes. – Crowman Oct 13 '14 at 05:30
  • incorrect. What do you think happens if any child stops reading and the parent calls `write()` for the corresponding pipe `PIPE_BUF` times? – jfs Oct 13 '14 at 05:50
  • @J.F.Sebastian: It won't. The parent won't call `write()` a second time until the child has read from the pipe and written back to the parent to tell it so. If a child stops reading, then it'll stop writing, and the parent's `read()` will block forever. There'll never be more than one character in any of the pipes, here. – Crowman Oct 13 '14 at 05:58
  • I said child may stop reading. It doesn't mean it can't write. If we assume that the child behaves exactly as written then the behavior is already serialized. – jfs Oct 13 '14 at 06:07
  • In this case, if it stops reading it absolutely does mean that it can't write, because it's written to work that way. Assuming "that the child behaves exactly as written" is absolutely what we ought to do, because it can't behave in any other way. Other than for debugging purposes while it's being written, guarding against conditions that a program just cannot generate adds unnecessary complexity, but adds no value. – Crowman Oct 13 '14 at 06:35