【问题标题】:writing to external program through pipe (C)通过管道(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[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(),这就是我认为在这里造成问题但不知道的原因。 你能帮忙解决这个看似简单的问题吗?任何帮助表示赞赏。

【问题讨论】:

    标签: c pipe ipc


    【解决方案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,因为无意写入管道的进程仍将其打开以进行写入。或者一个进程可能会挂起,因为有一个进程打开了管道以供读取,因此系统假设它可能会从中读取,即使它无意这样做,所以它没有向写入者发出信号,而是阻止它。
    猜你喜欢
    • 1970-01-01
    • 2019-01-30
    • 2013-01-29
    • 1970-01-01
    • 2020-02-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多