不能在 C 中的管道中写入两次

Posted

技术标签:

【中文标题】不能在 C 中的管道中写入两次【英文标题】:Can't write twice into a pipe in C 【发布时间】:2014-11-27 18:05:33 【问题描述】:

我编写了一个简短的 C 程序来学习 IPC 基础。该程序由一个尝试向管道写入两次的子进程和一个尝试读取两条消息的父进程组成。消息在text1text2 中。执行后,父进程只读取第一条消息。

子进程的输出如下:

I write text1 in pipe
I write text2 in pipe

父级通过管道从子级接收到的文本是:Hi Dady, I'm your child and text1!

代码如下:

main(void)
    int     fd0[2], nbytes;
    pid_t   childpid;
    ssize_t errorfi;
    char    text1[] = "Hi Dady, I'm your child and text1!\n";
    char    text2[] = "Hi Dady, I'm your child and text2!\n";
    char    readbuffer[80],msg[80];
    pipe(fd0);        
    if((childpid = fork()) == -1)
    
            perror("fork");
            _exit(1);
    
    if(childpid == 0)
           //child process
            close(fd0[0]);
            fprintf (stderr, "I write text1 in pipe\n");
            errorfi = (write(fd0[1], text1, (strlen(text1)+1)));
            if (errorfi <0 )
            
               fprintf (stderr, "errorfi = %d\n", errorfi);
               fprintf (stderr, "error writting texto1 in fd0 pipe %s\n",strerror(errorfi));
               _exit (EXIT_FAILURE);
            
            fprintf (stderr, "I write text2 in pipe\n");
            errorfi = (write(fd0[1], text2, (strlen(text2)+1)));
            if (errorfi <0 )
            
               fprintf (stderr, "errorfi = %d\n", errorfi);
               fprintf (stderr, "error writting texto2 in fd0 pipe %s\n",strerror (errorfi));
               _exit (EXIT_FAILURE);
             
           _exit(0);
    else
           //parent process
            close(fd0[1]);
            nbytes = read(fd0[0], readbuffer, sizeof(readbuffer));
            printf("text received in parent from child trough pipe:%s\n", readbuffer);
    
    return(0);

为什么我只看text1

【问题讨论】:

【参考方案1】:

问题比 AB_ 的答案更糟糕。 (现已删除:建议您再阅读此答案)

父母和孩子之间可能存在竞争条件。如果孩子在父母阅读第一条消息之前只能写一次,一切都会好起来的(前提是你读了两次)。但是,如果孩子在父母阅读第一次(或唯一一次)阅读之前写了两次,将会得到 72 个字符......但你只会打印第一条消息!

为了证明这一点,我稍微修改了父部分,等待 0.5 秒并显示 nbytes :

       //parent process
        close(fd0[1]);
        usleep(500);
        nbytes = read(fd0[0], readbuffer, sizeof(readbuffer));
        printf("text received in parent from child trough pipe (%d) :%s\n",

nbytes,读取缓冲区);

我得到:

I write text1 in pipe
I write text2 in pipe
text received in parent from child trough pipe (72) :Hi Dady, I'm your child and text1!

为什么它得到 72 个字符并且只打印前半部分?因为readbuffer[36] == '\0'!并且所有 C 字符串函数都使用该 NULL 作为字符串终止符。

所以规则是当你通过管道或套接字传递文本时,永远不要发送终止空值,除非你对它们进行特殊处理 (*)

你应该在孩子中使用:

errorfi = write(fd0[1], text2, strlen(text1));

只发送非空字符并避免竞争条件的风险。

(*) 根据 Mat 的评论编辑:在串行通道(管道、套接字或 ... 串行线)中,您必须使用大小 + 数据或分隔符来清楚地识别数据块。我通常避免将 NULL 作为分隔符(在 C 或 C++ 程序中),因为它会阻止所有字符串函数的使用。但是如果你照顾它并继续处理第一个 null 之后,NULL 是一个很好的分隔符,因为它不在文本中使用(这就是它是 C 分隔符的原因)。

并且不要忘记:单个读取的大小(至少在串行线路和管道上)可以大于或小于单个写入的大小:例如,睡眠连接两个写入。

【讨论】:

对于 small 写入管道来说没问题。对 sokects 的一般建议是不好的。处理此问题的唯一可靠方法通常是在字符串之前发送大小或具有分隔符 - 而 null 是纯文本的良好分隔符。您可以完全从示例中的完整读取中恢复,只需进行一些处理。 @Mat :对于套接字中的文本,我只是想到了标准的文本协议,如 SMTP、POP、IMAP、FTP 等。 这些协议都有明确的分隔符(换行符,. 仅作为一行内容,等等)你给出的建议真的很糟糕。听起来好像一次读取将准确读取一次写入发送的内容。一般来说,这是绝对不能保证的(对管道的小写入是唯一可以保证的情况之一)。

以上是关于不能在 C 中的管道中写入两次的主要内容,如果未能解决你的问题,请参考以下文章

在C中读取和写入结构到管道

c中的命名管道

通过管道(C)写入外部程序

在多个进程写入时读取命名管道

管道中的执行过程

我的 C++ 程序如何等到对命名管道进行新的写入?