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

Posted

技术标签:

【中文标题】通过管道(C)写入外部程序【英文标题】:writing to external program through pipe (C) 【发布时间】:2020-01-05 16:34:15 【问题描述】:

我想使用外部程序来处理内存中的数据。像外部压缩器,编码器,任何处理我的数据并得到结果的东西。我读了很多关于管道的文章,但它仍然不起作用。所以我最终得到了一个简单的程序,它试图通过这样的管道写入外部程序,让它打印到标准输出:

                                                     stdout
             (w) pipeA (r)          $prog            +---+
             +-----------+       /~~~~~~~~~~~\       |1|
             |[1]     [0]| ----> |0     1| ----> |   |
        +~~> +-----------+       \~~~~~~~~~~~/       |   |
        |                                            +---+
        |
       +-+
 write() |
       +-+

我还是一无所获。 我的代码是这样的:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>


int main(void)

    int pipA[2];
    int pid;
    char buf_IN[32] = "Hello pipe!\n";
    ssize_t n_written;

    if ((pipe(pipA) == -1))    
        perror("pipe failed");
        exit(1);
    
    if ((pid = fork()) < 0)    
        perror("fork failed");
        exit(2);
    
    /*****************************/

    if (pid == 0)
         /* in child */
        dup2(0, pipA[0]);   // pipA[read(0)-end]->$prog[write0-end]
        close(pipA[1]);   // $prog won't write to this pipe(A)
        // external ``$prog''ram
        execlp("wc", "wc", (char *) 0);   // out should be: '      1       2      12'
        //execlp("base64", "base64", (char *) 0);   // out should be: 'SGVsbG8gcGlwZSEK'

        ;///if we're here something went wrong
        perror("execlp() @child failed");
        exit(3);
        

    else
         /* in parent */
        //dup2(pipA[1], 0);  // STDIN -> pipA // that supposed to connect STDIN->pipA; just in case I needed it
        close(pipA[0]);  // we won't read it, let $prog write to stdout
        //perror("execlp() @parent failed");
        //exit(4);
        n_written = write(pipA[1], buf_IN, strlen(buf_IN));
        close(pipA[1]);  // I guess this will close the pipe and send child EOF

        // base64: read error: Input/output error
        // wc: 'standard input': Input/output error
        //      0       0       0
        

return 0;

评论显示我在做什么。我不得不承认我没有在管道中得到这些 dup(),这就是我认为在这里造成问题但不知道的原因。 你能帮忙解决这个看似简单的问题吗?任何帮助表示赞赏。

【问题讨论】:

【参考方案1】:

诊断

你有 dup2() 的参数从后到前。你需要:

dup2(pipA[0], 0);

关闭文件描述符

您没有在子项中关闭足够多的文件描述符:


经验法则:如果您 dup2() 管道的一端到标准输入或标准输出,关闭两者 返回的原始文件描述符 pipe() 尽快地。 特别是,您应该在使用任何 exec*() 函数族。

如果您使用以下任一方式复制描述符,该规则也适用 dup() 或者 fcntl() F_DUPFD


处方

您的代码中也有一些未使用的定义和未使用的变量。去掉你所有的 cmets(但我有几个解释发生了什么),并进行了适当的修复,我最终得到:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main(void)

    int pipA[2];
    int pid;
    char buf_IN[32] = "Hello pipe!\n";
    ssize_t n_written;

    if ((pipe(pipA) == -1))
    
        perror("pipe failed");
        exit(1);
    
    if ((pid = fork()) < 0)
    
        perror("fork failed");
        exit(2);
    

    if (pid == 0)
    
        /* Child: Connect pipA[0] (read) to standard input 0 */
        dup2(pipA[0], 0);
        close(pipA[1]);  /* Close write end of pipe */
        close(pipA[0]);  /* Close read  end of pipe */
        execlp("wc", "wc", (char *)0);
        perror("execlp() @child failed");
        exit(3);
    
    else
    
        close(pipA[0]);  /* Close read end of pipe */
        n_written = write(pipA[1], buf_IN, strlen(buf_IN));
        if (n_written != (ssize_t)strlen(buf_IN))
        
            perror("short write");
            exit(4);
        
        close(pipA[1]);  /* Close write end of pipe — EOF for child */
    

    /* Optionally wait for child to die before exiting */
    // #include <sys/wait.h>  // With other #include lines
    // int corpse;
    // int status;
    // while ((corpse = wait(&status)) > 0)
    //     printf("Child %d exited with status 0x%.4X\n", corpse, status);

    return 0;

运行时,会产生:

       1       2      12

看起来不错。

如果没有 wait() 循环,您可能会在 shell 提示后看到来自 wc 的输出(因此看起来好像程序正在等待您的输入,但实际上,它将是等待输入的外壳);使用等待循环,您将正确分离 shell 提示符的输出。您不必在循环体中打印任何内容,但这样做会让人放心。

【讨论】:

谢谢,很快。我将尝试消化它并获得 dup2() 。 PS。我已经编辑了未使用的变量 PPS。刚刚测试了您的程序,它仍在等待 。究竟是什么意思?不应该返回终端而不是等待用户输入数据? 让我仔细检查一下,但我相信如果您在我的答案中复制并粘贴代码,它将为您提供wc 的输出,而无需任何用户交互。指出我写错了dup2() 的一件事是wc 没有终止。 它确实给出了答案,但程序仍在等待用户输入。 - 顺便提一句。你能评论那些 dup2()s 和 close()s 让人们知道他们在做什么吗?我问的原因是我显然不理解 dup2() 并且仍然有疑问。我是否正确关闭 pipA[] 以父母结尾?说 dup2(pipA[0], 0); 连接 pipA[0]--->0 对吗? pipA[0] 会变成 0 还是 0 会变成 pipA[0]? 我在答案中添加了一些注释(并注释掉了代码)。当孩子调用dup2(pipA[0], 0),然后在调用完成后,pipA[0]0 引用与调用之前pipA[0] 相同的打开文件描述(即使它们是两个不同的打开文件描述符——小心使用“描述”与“描述”)。当您随后关闭pipA[0] 时,只有标准输入指的是管道的读取端(这是您想要的)。请注意,父进程还有一个打开的文件描述符,它引用相同的打开文件描述——直到它关闭pipA[0] 谢谢,这有助于我更好地理解它。【参考方案2】:

为了不重复,也不在不熟悉的领域扮演专家,我将其发布为答案。

我完成了任务

                 (w) pipeA (r)            child            (w) pipeB (r)
                 +-----------+       /~~~~~~~~~~~~~\       +-----------+
            +~~~>|[1]     [0]| ----> |0 $prog 1| ----> |[1]     [0]| ~~~+
            |    +-----------+       \~~~~~~~~~~~~~/       +-----------+    |
            |                                                               \/
           +-+                                                             +--+
     write() |                                                             |  read()
           +-+                                                             +--+

                dup(pA[0],0)                                dup(pB[1],1)
                close(pA[1])                                close(pA[0])
                close(pA[0])                                close(pA[1])


带有可以读取的第二个管道。一切以此类推。如果有什么重大问题或者我应该注意的事情,请说出来。 (对不起python风格的缩进,希望你不介意)

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

ssize_t read_whole_file(int fildes, const void *buf, size_t nbyte);

int main(void)
    
    int pipA[2], pipB[2];
    int pid;
    char buf_IN[32] = "Hello pipe!\n";
    char buf_OUT[1024];
    char *bptr;
    ssize_t n_written, n_read = 0, a_read = 0, to_read = sizeof(buf_OUT);

    if ((pipe(pipA) == -1) || (pipe(pipB) == -1))
        
        perror("pipe failed");
        exit(1);
        
    if ((pid = fork()) < 0)
        
        perror("fork failed");
        exit(2);
        

    if (pid == 0)
        
        /* in child */
        dup2(pipA[0], 0);  // connect pipe A (read end/exit) to stdin (write end/input)
        close(pipA[1]);  // close unused pipe A end
        close(pipA[0]);  // close  - " - //
        ;
        dup2(pipB[1], 1);  // connect stdout (read end/output) to pipe B (write end/entry)
        close(pipB[0]);  // close unused pipe B ends
        close(pipB[1]);  // close  - " - //
        execlp("lzip", "lzip", "-c", (char *)0);
        ;
        perror("execlp() @child failed");
        exit(3);
        
    else
        
        /* in parent */
        close(pipA[0]);  // close pipe A read end - will only write to this one
        n_written = write(pipA[1], buf_IN, strlen(buf_IN));
        if (n_written < 0)
            perror("error: read_whole_file(pipA[1], ...) failed miserably\n");
        close(pipA[1]);  // close write end which subsequently signals EOF to child
        ;
        close(pipB[1]);  // close pipe B write end - will only read form this one
        a_read = read_whole_file(pipB[0], buf_OUT, sizeof(buf_OUT));
        if (a_read < 0)
            perror("error: read_whole_file(pipB[0], ...) failed miserably\n");
        close(pipB[0]);  // close read end after reading
        ;
        write(STDOUT_FILENO, buf_OUT, a_read);  // dump it to parent's stdout - equivalent of processing data received from external program/plugin
        
    return 0;
    

ssize_t read_whole_file(int fildes, const void *buf, size_t nbyte)  // read whole file
    
    ssize_t n_read, a_read = 0, to_read = nbyte;
    char *bptr = (char*)buf;
    size_t BUF_SIZE = 4096;
    do
        
        if(to_read < BUF_SIZE)
            BUF_SIZE = to_read;
        n_read = read(fildes, bptr, BUF_SIZE);
        if (n_read < 0)  // recover from temporarily failed read (got it from git wrapper)
            
            if ((errno == EINTR) || (errno == EAGAIN) || (errno == EWOULDBLOCK))
                continue;
            
        bptr += n_read;
        to_read -= n_read;
        a_read += n_read;
         while ((n_read>0) && (to_read>0));
    ;
    if (n_read < 0)
        a_read = n_read;
    return a_read;
    

要注意不是那么明显 - 我仍然没有得到 close(pipA[0]);close(pipB[1]); 的孩子。为什么它们不再使用了? 另外dup2(pipB[1], 1);,我认为它会是其他方式,但它没有工作,所以通过试用结束错误我来了。

【讨论】:

在这种情况下,您可能会在不关闭子节点的所有管道端的情况下逃脱,但在其他情况下(例如,3 个进程和 2 个管道,或更多,或多个管道在两个方向工作)关闭管道末端可能会导致进程在需要时无法获得 EOF,因为无意写入管道的进程仍将其打开以进行写入。或者一个进程可能会挂起,因为有一个进程打开了管道以供读取,因此系统假设它可能会从中读取,即使它无意这样做,所以它没有向写入者发出信号,而是阻止它。

以上是关于通过管道(C)写入外部程序的主要内容,如果未能解决你的问题,请参考以下文章

使用 Java 通过 Windows 上的外部应用程序管道数据

程序打开同一个命名管道并用 C 多次写入

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

如何确保已读取所有管道缓冲区?

QProcess与外部程序的调用(可以通过设置管道来交互)

乐鑫esp8266学习rtos3.0笔记:分享在 esp8266 C SDK如何通过外部写入参数,程序里面实现动态获取参数。