首页 > 解决方案 > 在 C 中使用两个管道或一个管道进行 2 次以上的读/写?如何?

问题描述

我有以下简化的代码模板:

pid_t pid;
int pipe1[2], pipe2[2];
pid = fork();
pipe(pipe1); pipe(pipe2)
if(pid == 0)  //child
{
    read(pipe1[0],...);
    write(pipe1[1],...);
    close(pipe1[0]);
    close(pipe1[1]);

    close(pipe2[1]);
    read(pipe2[0]...);
}
else //parent
{
    write(pipe1[1],...);
    wait(NULL);
    read(pipe1[0]...);
    close(pipe1[0]);
    close(pipe1[1]);

    close(pipe2[0]);
    write(pipe2[1]...);
}

如果我不在父母和孩子中使用 pipe2,则代码可以完美运行,但如果我这样做,孩子似乎没有什么可读的(程序什么都不做,直到我插入它)。另外,有没有办法只使用一个管道进行 2 次以上的读/写?我尝试多次使用 wait(NULL) ,但没有奏效。

标签: clinuxpipefork

解决方案


简而言之,您的代码模板是垃圾。让我解释一下为什么。

  1. 每个管道都是单向的。

    如果您使用管道将数据从子级发送到父级,请关闭子级中的读取端和父级中的写入端。这允许父级查看子级(写入端)何时关闭管道或退出,然后read()返回-1.errno == EPIPE

    如果您使用管道将数据从父级发送到子级,请关闭父级中的读取端和子级中的写入端。这允许父母检测孩子是否过早退出,write()然后将返回 with-1并且errno == EPIPE在父母中引发 SIGPIPE 信号。
     

  2. 如果您需要父子之间的双向“管道”,请使用 Unix 域流套接字对通过socketpair(AF_UNIX, SOCK_STREAM, 0, fdpair).

    这样的套接字对的工作方式与管道非常相似,只是套接字对是双向的。您也可以使用send(descriptor, buffer, length, MSG_NOSIGNAL)代替write(descriptor, buffer, length); 在前一种情况下,如果套接字的另一端已经关闭,则不会发出 SIGPIPE 信号。

    在父级中使用一个描述符,在子级中使用另一个。父母和孩子都应该关闭另一个描述符。否则,一端无法检测到另一端何时关闭其描述符。

    在某些情况下,Unix 域数据报套接字对可能更可取。每个send()生成一个单独的数据报,使用单个recv(). (也就是说,保留了消息边界。)如果接收端知道发送端可能发送的数据报的最大大小,这是实现父进程和子进程之间双向通信的一种非常健壮和简单的方法;我个人经常使用它。
     

  3. read()并且write()管道和插座可能很

    (但是,POSIX 声明您应该始终能够将至少 512 个字节填充到管道中;如果我没记错的话,Linux 至少支持一整页。)

    这意味着您需要执行循环,而不是单个调用,直到您拥有所需的数据量为止。

    使用套接字,send()要么发送所有数据,要么失败-1(带有errno == EMSGSIZE,或其他错误代码)。

    对于数据报套接字(Unix 域数据报套接字、UDP 套接字),如果缓冲区足够大,则recv()要么接收整个数据报,要么失败-1(带有errno设置)。接收零长度数据报是不确定的,所以不要尝试这样做。

    对于流套接字,recv()可能只返回部分数据(即短或部分接收)。
     

  4. 当两个进程相互发送和接收或读取和写入数据时,死锁是一个严重的常见问题。

    简而言之,两端可能最终都在等待另一端同时读/写,什么都没有发生。

    在这种情况下,有三种典型的解决方案可以避免死锁:

    1. 使用查询-响应协议,使一个端点始终发起通信,然后等待另一个端点响应。在任何给定时间最多有一个端点正在传输数据。

    2. 使用非阻塞/异步 I/O。也就是说,在尝试write()/之前send(),每个端点都会执行read()/recv()以查看另一端是否已经发送了一些东西。这支持全双工通信(信息可以同时双向流动)。

    3. 使用单独的线程连续read()/ recv(),而另一个线程write()/ send()。这基本上将每个套接字分成两个单向“通道”,一个线程仅处理它们的方向。这对于一端产生大量数据而另一端偶尔发送命令的协议很有用。
       

结合以上所有,我们会发现没有一个模板应该使用。存在显着差异的变体,使它们在某些用例中更好,但在其他用例中更难/更差。应该根据手头的情况选择一种。如果 OP 想看到一个更好的例子(“模板”),他们应该描述一个实际的问题案例,包括所需的行为。


推荐阅读