【问题标题】:Does bidirectional popen() work on Mac OS X in C?双向 popen() 是否可以在 C 中的 Mac OS X 上工作?
【发布时间】:2017-10-17 22:51:54
【问题描述】:

我的问题 在哪里可以找到一个清晰的 C 代码示例,该示例在 MacOS 上设置和使用带有 popen() 的双向管道?

我正在尝试使用 popen() 在我的 C 程序和 Mac OS X 上的外部程序之间打开双向管道。

我的 C 程序不得不重复:

  • 从自己的标准输入读取输入行
  • 将其重新格式化为外部程序的输入
  • 调用外部程序,将格式化的输入传递到外部程序的标准输入中
  • 从外部程序的标准输出中读取结果
  • 处理这些结果并在其自己的标准输出中生成一些诊断输出

如果我使用实际文件来存储输入/输出,然后使用 system() 或类似方法,我可以轻松做到这一点,但这太慢了,因为我有数十亿个输入。所以我想完全在内存中完成。

相关的手册页,以及本组和其他人的互联网讨论表明,我可以打开一个到外部程序的管道,然后用 fprintf 写入它,就像写入文件一样。

pout = popen(external_program, "w")
fprintf(pout,input_to_external_program);

这可以作为单向管道正常工作,将格式化的输入馈送到外部程序中。

显然,在 Mac OS 上可以通过指定“r+”而不是“r”或“w”作为 popen 的相关参数来打开双向管道,从而使用同一管道读取和写入程序。

pout = popen(external_program, "r+")
fprintf(pout,"%s", input_to_external_program);
fscanf(fpout,"%s", &output_from_external_program);

我不需要在读取、写入、读取、写入等之间交替,因为我的程序将在开始读取外部程序的输出之前完成将输入写入外部程序。

外部程序应该只读取一个独立的输入序列(即,它以一个特殊的行结束,表示“输入结束”),然后运行到完成,同时写入输出,然后终止。

但是当我尝试使用这个功能时,它就不起作用了,因为有些东西阻塞了 - 外部程序启动了,但甚至没有完成一个输入。

即使我只是忘记阅读任何内容,而只是将“w”替换为“r+”,那么之前工作的功能也会停止工作。

我今天早上的大部分时间都在搜索可以修改的工作 popen() 示例,但我发现的所有示例都只使用单向管道。

所以我们最终回到

我的问题 *我在哪里可以找到一个清晰的 C 代码工作示例,该示例在 MacOS 上设置并使用带有 popen() 的双向管道?**

如果它更容易,我也许可以用 Perl 替换 C 部分。

更新

感谢您的回答,但我仍然无法使其正常工作,尽管我可以使一些小规模版本工作。例如,我编写了一个小程序,它会在输入的任何数字上加一,如果输入为 -99,则退出/我称之为“复制”。

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

int 
main(int argc, char **argv) {

  int k;

  setbuf(stdout,NULL);

  while (scanf("%d",&k) == 1) {
    if (k == -99) break;
    printf("%d\n",k+1);
  }

}

然后我编写了一个父程序,它将创建管道,向其中写入一些东西,然后等待输出(不做 NOHANG,因为我希望父级继续吸取子级的输出,直到子级完成)。

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>

int main(void) {

    FILE *p;
    int  k;

    p = popen("copy", "r+");
    if (!p) {
        puts("Can't connect to copy");
        return 1;
    }

    fprintf(p,"%d\n",100);
    fprintf(p,"%d\n",200);
    fprintf(p,"%d\n",300);
    fprintf(p,"%d\n",-99);

    fflush(p);

    while (fscanf(p,"%d",&k) == 1)  {
      printf("%d\n", k);
    }

    pclose(p);

    return 0;
 }

这很好用,但是一旦我用我的实际子进程替换虚拟程序“副本”,它就会挂起。

一切都应该是一样的——父进程为子进程创建一个完整的输入序列,子进程读取它,做一些工作,输出任意数量的输出行,然后终止。

但有些不同 - 要么子进程没有接收到它的所有输入,要么正在缓冲它的输出或其他东西。神秘。

【问题讨论】:

  • 也许检查一下,它可能会给双向管道一些提示:stackoverflow.com/questions/8390799/…
  • popen() 手册页的摘录。 “popen() 函数通过创建管道、分叉和​​调用 shell 来打开一个进程。由于管道根据定义是单向的,因此类型参数可能只指定读取或写入,而不是两者;生成的流相应地是只读的或只写。”
  • 如果你想要双向通信,而不是popen(),请使用fork(),在子进程中,在'执行'bc进程之前,修改stdin和@987654331 @
  • 在贴出的代码中,fpout来自哪里?
  • @user3629249:在 macOS 上不是这样。相关man page的摘录:“由于popen()现在使用双向管道实现,类型参数可能请求双向数据流”

标签: c pipe popen


【解决方案1】:

确保您在父进程中fflush(pout),并且子进程在每次输出后执行fflush(stdout)

【讨论】:

    【解决方案2】:

    这是一个与bc 交互以执行整数计算的程序。在最简单的情况下,您需要做的就是使用fgets()fputs() 来读写管道。

    但是,如果您输入的内容不会产生输出,例如设置变量(例如,x=1)或运行时错误(例如,1/0),那么fgets() 将继续无限期地等待输入。为避免这种情况,您必须创建管道 non-blocking 并反复检查管道进程的输出。

    #include <stdio.h>
    #include <fcntl.h>
    #include <unistd.h>
    
    int main(void) {
        FILE *p;
        char buf[1025];
        int i, fd, delay;
    
        p = popen("bc", "r+");
        if (!p) {
            puts("Can't connect to bc");
            return 1;
        }
        /* Make the pipe non-blocking so we don't freeze if bc produces no output */
        fd = fileno(p);
        fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK);
    
        puts("Give me a sum to do (e.g., 2+2):");
        if (fgets(buf, 1024, stdin)) {
            fputs(buf, p);
            fflush(p);
            for (delay=1,i=0; i<20; delay*=2,i++) {
                usleep(delay);
                if (fgets(buf, 1024, p)) {
                    printf("The answer is %s", buf);
                    break;
                }
            }
        }
        pclose(p);
    
        return 0;
    }
    

    另外,不要忘记管道通常是行缓冲的。这不是问题,因为fgets() 在输入的末尾包含一个换行符。 更正:管道是 block 缓冲的,所以在将数据发送到管道。

    【讨论】:

    • 发布的答案(几乎)编译。但是,当实际运行对popen() 的调用时会失败。如果代码使用perror() 而不是puts(),那么操作系统认为对popen() 的调用也会输出,结果如下:can't connect to bc: Invalid argument `
    • 当使用popen() 打开管道时,不应使用fclose() 关闭,而是使用:pclose()
    • 顺便说一句:管道是“块缓冲”,而不是“行缓冲”
    • 注意:pipe() 是一个众所周知的 C 库函数。使用与函数相同的名称作为变量名是一种糟糕的编程习惯。
    • @user3629249 感谢您查看此内容。仅供参考,在 OS X 中支持双向管道 。引用:“模式参数是指向以空字符结尾的字符串的指针,该字符串必须是 'r' 用于读取,'w' 用于写入,或 'r+' 用于读写。” 代码编译并运行没有任何错误,即使在打印到管道后没有调用fflush()。是的,我知道错误消息属于 stderr。 bc 也有可能返回不止一行的输出,但考虑到这只是一个概念证明,因此在涵盖所有可能性方面没有多大意义。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-01-16
    • 1970-01-01
    • 2015-08-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多