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

writing to external program through pipe (C)

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

                                                     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[write{0}-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()s,这就是我认为在这里造成问题但不知道的原因。 你能帮忙解决这个看似简单的问题吗?任何帮助表示赞赏。

诊断

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

dup2(pipA[0], 0);

关闭文件描述符

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


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

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


处方

您的代码中也有一些未使用的定义和未使用的变量。除去您的所有评论(但有一些我的评论来解释正在发生的事情)和适当的修复,我最终得到:

#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 等待输入);在等待循环中,您将从 shell 提示中正确分离输出。您不必在循环体中打印任何内容,但这样做是令人放心的。

为了不重复,也为了在不熟悉的领域扮演专家我post作为答案。

我完成了任务

                 (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;
    }

注意不是很明显 - 我仍然没有在 child 中得到 close(pipA[0]);close(pipB[1]);。为什么他们不再使用了? 另外 dup2(pipB[1], 1);,我认为它会是另一种方式,但它没有用,所以由于试用结束错误,我想到了这个。