【问题标题】:Communicate with child process stdout/stdin与子进程 stdout/stdin 通信
【发布时间】:2014-02-14 19:10:22
【问题描述】:

因此我尝试以编程方式替换 shell 用户。一个比喻的例子:想象一下,出于某种原因,我想在 C 中使用 VIM。然后我还需要编写命令(stdout)并从编辑器(stdin)读取内容。

最初我认为这可能是一项微不足道的任务,但似乎没有标准方法。 int system(const char *command); 只是执行一个命令并将命令 stdin/stdout 设置为调用进程之一。

因为这无济于事,我查看了FILE *popen(const char *command, const char *type);,但手册页指出:

因为根据定义,管道是单向的,类型参数可以只指定读或写,不能同时指定;结果流是相应的只读或只写。

及其含义:

popen() 的返回值在所有方面都是一个普通的标准 I/O 流,除了它必须用 pclose() 而不是 fclose(3) 关闭。 写入这样的流会写入标准输入 命令的;命令的标准输出与调用popen()的进程的相同,除非这被命令本身改变。相反,从“弹出”流中读取 命令的标准输出命令的标准输入与调用popen()的进程相同

因此使用popen() 并非完全不可能,但在我看来它非常不雅,因为我必须解析调用进程的标准输出(调用popen() 的代码)才能解析从 popen 命令发送的数据(使用 popen 类型 'w' 时)。

相反,当使用类型 'r' 调用 popen 时,我需要写入调用的进程标准输入,以便将数据写入 popened 命令。在这种情况下,我什至不清楚这两个进程是否在标准输入中接收到相同的数据......

我只需要控制一个程序的标准输入和标准输出。我的意思是不能有这样的功能:

stdin_of_process, stdout_of_process = real_popen("/path/to/bin", "rw")
// write some data to the process stdin
write("hello", stdin_of_process)
// read the response of the process
read(stdout_of_process)

所以我的第一个问题:实现上层功能的最佳方式是什么?

目前我正在尝试以下方法与另一个进程进行通信:

  1. 使用int pipe(int fildes[2]); 设置两个管道。一个管道读取进程的标准输出,另一个管道写入进程的标准输入。
  2. 叉子。
  3. 使用int execvp(const char *file, char *const argv[]);在分叉的子进程中执行我想与之通信的进程。
  4. 在原流程中使用两个管道与孩子通信。

这很容易说机器人实现起来并不那么简单(至少对我来说)。在一种情况下,我奇怪地设法做到了,但是当我试图用一个更简单的例子来理解我在做什么时,我失败了。这是我目前的问题:

我有两个程序。第一个只是每 100 毫秒向它的标准输出写入一个递增的数字:

#include <unistd.h>
#include <time.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>

void sleepMs(uint32_t ms) {
    struct timespec ts;
    ts.tv_sec = 0 + (ms / 1000);
    ts.tv_nsec = 1000 * 1000 * (ms % 1000);
    nanosleep(&ts, NULL);
}

int main(int argc, char *argv[]) {
    long int cnt = 0;
    char buf[0x10] = {0};

    while (1) {
        sleepMs(100);
        sprintf(buf, "%ld\n", ++cnt);
        if (write(STDOUT_FILENO, buf, strlen(buf)) == -1)
            perror("write");
    }
}

现在第二个程序应该读取第一个程序的标准输出(请记住,我最终想用一个进程来读写,所以在上层用例中使用 popen() 的技术正确解决方案在这种特定情况下可能是正确的,因为我简化了我的实验以仅捕获底部程序的标准输出)。我希望底层程序能够读取上层程序写入标准输出的任何数据。但它不读取任何内容。原因可能在哪里? (第二个问题)。

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

void sleepMs(uint32_t ms) {
    struct timespec ts;
    ts.tv_sec = 0 + (ms / 1000);
    ts.tv_nsec = 1000 * 1000 * (ms % 1000);
    nanosleep(&ts, NULL);
}

int main() {
    int pipe_fds[2];
    int n;
    char buf[0x100] = {0};
    pid_t pid;

    pipe(pipe_fds);

    char *cmd[] = {"/path/to/program/above", NULL};

    if ((pid = fork()) == 0) { /* child */
        dup2(pipe_fds[1], 1); // set stdout of the process to the write end of the pipe
        execvp(cmd[0], cmd); // execute the program.
        fflush(stdout);
        perror(cmd[0]); // only reached in case of error
        exit(0);
    } else if (pid == -1) { /* failed */
        perror("fork");
        exit(1);
    } else { /* parent */

        while (1) {
            sleepMs(500); // Wait a bit to let the child program run a little
            printf("Trying to read\n");
            if ((n = read(pipe_fds[0], buf, 0x100)) >= 0) { // Try to read stdout of the child process from the read end of the pipe
                buf[n] = 0; /* terminate the string */
                fprintf(stderr, "Got: %s", buf); // this should print "1 2 3 4 5 6 7 8 9 10 ..."
            } else {
                fprintf(stderr, "read failed\n");
                perror("read");
            }
        }
    }
}

【问题讨论】:

标签: c linux ipc stdout pipe


【解决方案1】:

Here 是一个(C++11 风格的)完整示例。

然而,出于许多实际目的,Expect library 可能是一个不错的选择(查看其源代码分发的 example 子目录中的代码)。

【讨论】:

  • 看起来又硬又酷。但精髓很简单 dup2(write_pipe.read_fd(), STDIN_FILENO); 。谢谢。
【解决方案2】:

您的想法是对的,我没有时间分析您的所有代码以指出具体问题,但我确实想指出一些您可能忽略的关于程序和终端工作。

将终端作为“文件”的想法是幼稚的。 vi 等程序使用库 (ncurses) 发送特殊控制字符(并更改终端设备驱动程序设置)。例如,vi 将终端设备驱动程序本身置于一种可以一次读取一个字符的模式。

以这种方式“控制”像 vi 这样的程序非常重要。

关于你的简化实验...

您的缓冲区太小了一个字节。另外,请注意 IO 有时是行缓冲的。因此,您可以尝试确保正在传输换行符(使用 printf 而不是 sprintf/strlen/write...您已经将标准输出连接到管道),否则在换行符被击中之前您可能看不到数据。我不记得管道是行缓冲的,但值得一试。

【讨论】:

  • 我已经用 printf 做到了,并改变了当前的 write/sprintf 方法,因为我无法让它工作。原来我一直在 execvp() 中使用错误的路径,上面的实验现在就像一个魅力 -_- 我会留下这个问题,因为我的第一个问题仍未得到解答:获取标准输入的最佳方法是什么/stdout 对一个进程并在 C 中与它们通信?
  • 你已经找到了。 pipe(2) 系统调用。就是这样,没有别的了。您创建了两对...一个从进程中读取,一个用于写入。使用 dup2 将每一对的一端放置在 fd 0 和 1 的位置(如果您关心错误,可能还有 2)。
猜你喜欢
  • 2011-06-22
  • 2012-05-20
  • 1970-01-01
  • 1970-01-01
  • 2017-02-01
  • 1970-01-01
  • 2021-03-20
  • 2021-06-02
  • 2018-09-24
相关资源
最近更新 更多