(c/c++) 尝试强制从父进程发送输入到子进程的 EOF

6
我有一个非常简单的C/C++程序,它fork一个子进程来执行另一个程序,并向该子程序发送一些数据,然后等待响应。
子程序从stdin读取并在继续之前等待EOF。
我的问题是,子程序接收到了管道写入的初始输入,但它从未看到EOF(即使我关闭了管道),因此它会一直等待。
我不确定为什么关闭管道不意味着子进程的stdin已经到达了EOF?
以下是代码:

http://gist.github.com/621210


我更新了我的答案。当我思考这个问题时,它应该有“作业”标签吗?它似乎非常基础。 - Zan Lynx
我向您保证这与作业无关。我已经毕业多年了。虽然我不是C/C++编码人员,但我正在构建一个概念验证系统,用于我正在进行的演示,并且我回到了我十年前没有写过的代码。 - Kyle Simpson
3个回答

8
最常见的原因是您没有关闭管道的写入端口,因此EOF永远不会被发送。常见的示例是当您的代码看起来像这样时:
int fds[2];
pipe(fds);  // open a pipe
if (fork()) {
    // parent process
    write(fds[1], ...  // write data
    close(fds[1]); // close it
} else {
    // child process
    while (read(fds[0], ....) > 0) {
        // read until EOF

这里的问题是管道的写入端从未关闭——父进程关闭了它,但子进程仍然保持着写入描述符的开启。所以子进程在读取描述符上永远不会看到EOF。
在fork出子进程后,第一件事就是要关闭它的写描述符(close(fds[1]);),关闭其写入描述符的副本。这样,当父进程关闭对管道写入端的最后一个引用时,子进程将在读取端看到EOF。
编辑:
查看您添加的链接,这正是问题所在——子进程仍然在其stdout上保持着管道的写入端。在子进程中不要复制写入端到stdout,只需将其关闭即可。将stdout发送到其他地方(日志文件或/dev/null)。
编辑:
对于双向通信,您需要两个管道:
int tochild[2], fromchild[2];
pipe(tochild); pipe(fromchild);
if (fork()) {
    close(tochild[0]);
    close(fromchild[1]);
    //write to tochild[1] and read from fromchild[0]
} else {
    dup2(tochild[0], 0);
    dup2(fromchild[1], 1);
    close(tochild[0]); close(tochild[1]);
    close(fromchild[0]); close(fromchild[1]);
    exec(...
}

在向子进程发送大量数据时,您需要非常小心地编写父进程的数据。否则,如果在读取子进程的输出之前发送了所有数据,则可能会发生死锁(两个管道都被填满,父进程阻塞尝试为子进程写入更多数据,而子进程则阻塞尝试输出)。您需要使用poll或select来告诉何时有数据可读或空间可写,并且您可能希望将管道(至少是父端)设置为非阻塞模式。


请查看链接代码片段中的32-37行。我正在关闭它们。 - Kyle Simpson
管道是一种单向通信通道 - 现在的方式是,子进程的输出将被反馈到子进程的输入。如果您想要双向通信,则需要两个管道。 - Chris Dodd
@Chris-ok,关于你最后的注释,需要注意死锁的问题:也就是说,子进程在从父进程读取数据之前,不会输出任何内容。换句话说,在管道中只有写入或读取操作,而不是同时进行。那么我是否仍有可能无法让子进程读取父进程想要发送给它的所有输入?反之亦然,由于所有输入都将在子进程开始输出之前完成,所以子进程能够输出多少量数据是有限制的吗? - Kyle Simpson
只要子进程在写入任何内容之前读取了所有输入,那么就不会出现问题。如果子进程在完全读取其输入之前开始写入,则可能会出现问题。 - Chris Dodd
1
@eonil:是的,确切地说。只有当引用计数降至0时,底层对象才会关闭(并将EOF传播到管道的另一端)。 - Chris Dodd
显示剩余5条评论

3

更新,我认为问题出在这里:

你正在读取一个字符并检查该字符是否为EOF。这不是read()系统调用的工作方式。当读到EOF时,它将返回0。它不会将EOF写入缓冲区。

另外,我看到你一次只读取一个字符。这是一种可怕的数据读取方式。它比读取大缓冲区,例如4或8kB慢数千倍。

我认为你在这里也犯了一个常见的错误。你没有检查write()的返回值。

不能保证write系统调用在返回之前写入所有数据。它可能会写入4000字节并返回。它将返回写入的字节数。然后你需要负责更新缓冲区指针并再次调用write。

或者它可能返回一个错误代码,你需要检查这个错误。


好的,这只是一个简单的概念验证代码。在我的测试中,我发送了5个字符作为输入。所以如果write()只发送了其中一半,我会感到惊讶的。 - Kyle Simpson
此外,如果我将子程序更改为仅读取确切的5个字符(而不是等待EOF),则子程序可以很好地获取所有5个字符,并按预期工作。因此,似乎唯一的问题是我需要子程序等待可变长度输入,而EOF似乎是正确的信号方式。 - Kyle Simpson
你关注的问题不对。我没有问题从子进程获取输出到父进程。我的问题是当父进程向连接到子进程标准输入的管道写入一些数据后关闭管道,子进程永远无法获得EOF。 - Kyle Simpson
看一下L44和L46... L44将数据写入管道,应该发送到子进程的stdin。L44运行良好。L46在write()完成后关闭管道。根据我之前所知,关闭管道应该将EOF发送到子进程的stdin。但实际上并没有。 - Kyle Simpson
@Kyle:我更新了我的答案。第一部分现在解决了你的read()调用,我认为这是错误的。 - Zan Lynx
现在,由于另一个答案帮助我修复了双向 I/O,我将修复 write() 和 read(),使它们具有缓冲功能,从而更加健壮。感谢你的建议。 - Kyle Simpson

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接