正确的 fork() 和 pipe() 用于具有多个孩子的单亲。我该怎么做?
Posted
技术标签:
【中文标题】正确的 fork() 和 pipe() 用于具有多个孩子的单亲。我该怎么做?【英文标题】:Proper fork() and pipe() use for a single parent having multiple children. How do I do this right? 【发布时间】:2014-10-13 02:04:44 【问题描述】:因此,我的PREVIOUS POST 被标记为离题、过于模糊,并要求对有用代码提出意见和建议。它做了这些事情,所以我重新发布一个关于我正在处理的代码的问题。感谢上一篇文章中的那些人,我能够通过回顾你们所说的来拼凑出我在这里的内容。
这里的重点是父/子关系以及使用 fork() 和 pipe() 来获得所需的效果。
该项目是一个 POSIX 纸牌游戏,其中父母(经销商)用他们自己的管道从父母那里分叉成 5 个孩子(玩家)。父母向玩家发牌(每人至少 5 张牌),直到一名玩家拥有 3 个同类(无弃牌)。获胜的孩子将获胜的手牌和 EOF (exit(0)) 发送给父母以宣布胜利。这会触发父级打印获胜玩家并将 EOF 传输给剩余的子级以关闭它们(退出(1))。然后父级关闭。如果父母在没有获胜者的情况下到达甲板的尽头,它将EOF传输给孩子,等待他们退出(1)......然后关闭。
我遇到的主要问题是子程序如何单独读取每张卡片,而不是一遍又一遍地从管道中读取相同的值。我想我缺少一种将父级写入管道和子级从管道读取同步的方法。
我对这些人完全陌生,因此非常感谢任何帮助。非常感谢。
代码已更新:(编译时有很多问题) 完成并编译,但我显然在管道和通过它们中继数据方面遇到了问题。这里有很多错误,从无法玩游戏,到无法读取或写入管道,再到离开僵尸进程。我很感激任何关于我的烂摊子的意见。哈哈
这是我运行程序 (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.
当前代码:
#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;
【问题讨论】:
每个孩子需要两个管道,每个方向一个。父级可以使用select()
来监控子级可以写入的所有管道,以确定是否可以读取。
@Paul Griffiths 所以像int toChild_pipe[PLAYERS][2], toParent_pipe[PLAYERS][2];
...然后以相同的方式创建两个管道?
是的,没错。例如,当每个孩子准备好一张新卡片时,每个孩子都可以在其写入管道中写入一个特定的字符,或者如果它宣布胜利,则可以写入一个不同的字符。然后,父母可以依次阅读它们并采取相应的行动。如果父母总是按顺序处理和读取每个孩子,当它准备好时,那么你甚至不需要select()
,只需循环并等待read()
。
一个实际的系统调用。
作为一般规则,如果您发现自己真的完全抓住了救命稻草,那么是时候做一些更简单的事情了。话虽如此,我写了一个答案,它不会为您制作游戏,但向您展示了一个示例机制,它将满足您的需求。对于您的问题,父级知道管道已被读取,因为它等待子级写入另一个管道来告诉它。
【参考方案1】:
您遇到的基本问题是您依赖 EOF 来检测事物,但是直到管道写入端的所有句柄都关闭后才会发生 EOF。所以你必须小心关闭所有进程中所有不需要的句柄。
在您的代码中,您有一个创建管道的循环,然后是分叉:
首先为子 0 创建两个管道 然后 fork child 0 child 0 关闭这些管道的父端,而 parent 关闭这些管道的子端(好) 循环 为子 1 创建两个管道 叉子 1 child 1 关闭其管道的父端,而 parent 关闭子端。此时,您遇到了一个问题——子 1 已将管道的父端继承给子 0,但并未关闭它们。这意味着子 0 在从父级读取时将无法检测到 EOF。孩子 2 和以后的孩子也会发生同样的事情。
【讨论】:
那么我该如何防止这种继承呢?这是否意味着孩子 2 从 0-1 继承所有父端,孩子 3 从 0-1-2 继承所有父端,......等等?此外,游戏现在似乎可以工作了(我在之前的帖子中使用了错误的迭代器来循环)。但是有一些大问题。 1)它为每个孩子输出块而不是顺序(奇怪)。 2)由于父母无法检测到获胜者的EOF,因此它会继续向其他玩家发牌。 3)它挂在wait()
。 (我想我用错了。我的意图是等待所有孩子结束,然后继续)
你不能阻止继承——fork 会自动继承所有打开的文件描述符。如果您不希望它们在子级中打开,则需要在分叉后在子级中关闭它们。【参考方案2】:
这种看起来像是纯代码的答案,但实际上并非如此,因为代码中的 cmets 解释了发生了什么。这与您正在编写的游戏不同,但显示了您正在寻找的机制。您可以根据自己的具体情况调整这种框架。
代码:
/* 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;
和输出:
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$
输出看起来有点奇怪,看起来进程在写入之前正在读取内容,但是当您使用异步进程并且不费心为演示目的同步输入/输出时会发生这种情况。对管道的实际读取和写入行为良好且同步,只是进入标准输出的调试消息看起来很奇怪。您应该仍然可以看到发生了什么。
【讨论】:
对理解这一点很有帮助。特别是在将其插入颜色编码编辑器之后。谢谢! 你应该在孩子中使用_exit(EXIT_SUCCESS)
而不是return EXIT_SUCCESS
@J.F.Sebastian:有时这就是您想要做的,但在这里没有任何区别,因为当孩子退出时没有退出处理程序和未刷新的 stdio 缓冲区(提供没有人采取将这个输出重定向到文件的稍微奇怪的步骤,即使在这种情况下,这里的 stdio 表现得很糟糕,开始时它不会产生任何实际影响)。
printf()
在fork()
之后可能会导致输出乱码。
父进程使用write()
的方式,read()
可以在写入超过PIPE_BUF字节的情况下序列化子进程。您可能应该将select
循环放入父级。以上是关于正确的 fork() 和 pipe() 用于具有多个孩子的单亲。我该怎么做?的主要内容,如果未能解决你的问题,请参考以下文章
C - 内部带有 fork() / pipe() 的 WHILE 循环
popen() 可以制作像 pipe() + fork() 这样的双向管道吗?