我正在创建一个TCP服务,每次客户端连接时都会分叉一个新进程。在fork之前,我设置了一个管道,这样孩子就可以将连接期间收集的统计信息发送回父级。父关闭写入结束,子关闭读取结束,父母维护一个读取结束文件描述符数组,每个孩子一个。
当孩子完成连接并退出时,我不确定如何处理这些文件描述符。孩子是否需要通过管道通知父母它将要退出,以便父母可以关闭管道?或者,在孩子退出并关闭后,父母可以自动检测到破裂的管道吗?
父程序中的代码正在运行一个循环,select()
检测侦听套接字和子管道读取端的活动。每个孩子在运行时都可以向父母发送多条消息。
通常,当子进程退出时,父进程应该使用管道文件描述符做什么?
答案 0 :(得分:3)
在您的情况下,父进程应在fork之后立即关闭写入管道的末尾。然后它可以读取其统计数据,直到EOF(文件结束),然后关闭管道的读取端。
答案 1 :(得分:3)
第一遍:在明确表示存在使用select()
的循环并且孩子们发送了多封邮件之前。
如果父进程维护文件描述符数组,则还需要将每个文件描述符与子进程相关联。如果孩子们在他们死之前发送一条小的统计信息,那么当主程序等待死孩子时,它会知道哪个孩子死了,所以它可以关闭它刚刚发现死亡的孩子的文件描述符(在确定之后)通过执行一个或多个最终读取来将管道清空。
另一种机制使用select()
或poll()
或相关函数来报告何时对文件描述符的读取操作不会挂起。当它从管道中检测到EOF(零字节读取)时,它知道孩子死了。但是,处理这个问题可能比较麻烦。
从你的问题来看,儿童过程中是否存在来自儿童过程的单一信息,或者当孩子正在工作时是否存在“意识流”统计报告,这一点并不完全清楚。如果有一条消息(小于管道缓冲区大小),那么生活很简单。如果有消息流或消息长度超过管道缓冲区大小,则必须更仔细地考虑协调 - 只有当孩子死亡时才能检测到消息。
第二遍:获得额外信息后。
如果您已经在循环中使用select()
,那么当一个孩子死亡时,您将从select()
获得“准备好读取”指示,并且您将从{{获得0个字节1}}表示该管道上的EOF。然后你应该关闭那个管道(并等待一个或多个waitpid()
的孩子,可能使用read()
- 应该至少有一个尸体被收集 - 所以你没有僵尸踢旷日持久)。
对上一个问题的严格回答是:当管道写入结束的唯一子节点死亡时,父节点应关闭该管道的读取端以释放资源以供以后重用。
答案 2 :(得分:0)
broken pipe
。所以它不适用于您的情况。在您的情况下,由于您的父级正在从管道读取,因此当子级退出时它应该读取EOF
(如果您已正确关闭父进程中的写入结束,否则它将阻止,因为它假定仍然存在将来要阅读的内容)。然后,您可以安全地关闭父进程中的读取fd。
一般
如果父写入和子读取,您需要担心broken pipe
这是孩子关闭读取fd的时间,而父母在写入管道时会获得SIGPIPE。 SIGPIPE默认终止进程,因此您可能需要设置信号处理程序以使其执行任何您想要的操作(如果您不希望它只是终止)。
答案 3 :(得分:0)
让我们看看父母有 1 个孩子和孩子的情况不同。
//fd[0] //read end
//fd[1] //write end
#include <unistd.h>
#include <stdio.h>
#include <errno.h> //For errno
#include <stdlib.h> //exit()
void DumpAndExit(char* str){
perror (str);
printf ("errno: %d", errono);
exit(0);
}
int main(){
int fd[2], pid = -1;
if (pipe(fd) < 0)
DumpAndExit ("pipe");
if (pid = fork() < 0) {
DumpAndExit ("fork");
}
else if (pid == 0) { //Child
close(fd[0]); //Close read end
printf("In Child \n");
sleep(2);
exit(0);
} else { //Parent
close(fd[1]); //close write
waitpid(-1); //Parent will wait for child
printf("Parent waiting\n");
char buf[4] = {};
read(fd[0], buf, sizeof(buf)); //reads from pipe
printf ("Read from child: %s", buf);
}
}
# ./a.out
In child
Parent waiting
Read from child:
#
用非常简单的话来说:
int fd[2]
是在父堆栈上创建的,然后复制到子堆栈。当子进程退出时,它的 PCB 被清除,父进程的 PCB 被更新,父进程知道管道的另一端没有连接。