执行 linux 程序并通过 C/C++ 中的 stdin/stdout 与其通信的最简单方法

Posted

技术标签:

【中文标题】执行 linux 程序并通过 C/C++ 中的 stdin/stdout 与其通信的最简单方法【英文标题】:Easiest way to execute linux program and communicate with it via stdin/stdout in C/C++ 【发布时间】:2013-10-25 19:50:13 【问题描述】:

我有无法修改的程序,原样,我需要执行它,将一些数据写入其标准输入,并以编程方式自动从标准输出中获取答案。 最简单的方法是什么?

我想像这样的伪 C 代码

char input_data_buffer[] = "calculate 2 + 2\nsay 'hello world!'";
char output_data_buffer[MAX_BUF];
IPCStream ipcs = executeIPC("./myprogram", "rw");
ipcs.write(input_data_buffer);
ipcs.read(output_data_buffer);
...

PS:我想到了popen,但是AFAIK在linux中只有单向管道

编辑:

假设这将是来自每一方的一条消息。首先父进程将输入发送到子进程的标准输入,然后子进程将输出提供给它的标准输出并退出,同时父进程读取它的标准输出。现在关于通信终止:我认为当子进程退出时,它会将 EOF 终止符发送到标准输出,因此父进程将确切知道子进程是否完成,另一方面保证父进程知道子进程期望什么样的输入。

通常这个程序(家长) - 学生的解决方案测试员。它从 CLI 获取其他两个可执行文件的路径,第一个是要测试的学生程序,第二个是标准具正常工作的程序,它解决了同样的问题。

学生程序的输入/输出是严格规定的,因此测试人员运行这两个程序并比较其输出的大量随机输入,所有不匹配都会被报告。

输入/输出最大大小估计为几百千字节

示例:..实现插入排序算法...第一行是序列长度...第二行是数字序列 a_i 其中|a_i|

输入:

5
1 3 4 6 2

预期输出:

16
1 2 3 4 6

【问题讨论】:

发送到其他程序的消息有多大?和回应?你会以一种方式发送一条消息,还是一个持续的对话?这些问题很重要,因为两个程序都需要知道它们何时到达当前输入消息的末尾。一个发送一个短请求和一个短响应的简单系统很容易编码(短小于大约 4 KiB)。随着消息变得越来越长,同步两个程序变得更加棘手。您可能会使用“半双工”协议;一个进程在发送,另一个进程一直在接收——这也更容易。 请查看我的编辑。也许,是否可以先在一侧使用“半双工”,然后再到另一侧? 【参考方案1】:

阅读Advanced Linux Programming-至少有一整章可以回答您的问题-并了解有关execve(2)、fork(2)、waitpid(2)、pipe(2)、dup2(2)、poll(2) 的更多信息...

请注意,您需要(至少在单线程程序中)对程序的输入和输出进行多路复用(使用poll)。否则您可能会遇到死锁:子进程可能会被阻止写入您的程序(因为输出管道已满),并且您的程序可能会被阻止读取它(因为输入管道是空的)。

顺便说一句,如果您的程序有一些 event loop 可能会有所帮助(实际上 poll 正在为简单的事件循环提供基础)。而Glib(来自GTK)为spawn processes提供功能,Qt有QProcess,libevent知道它们等等。

【讨论】:

好吧,看起来没有最佳实践模式来做这么简单的事情,我必须更深入地研究 linux IPC 编码。现在我很模糊理解如何做到这一点,所以我会听取你的建议并了解 poll 和大量的内核函数。不,没有事件循环,只是类似函数的 start-input-output-end 通信。【参考方案2】:

鉴于处理只是从父母到孩子的一条消息(必须在孩子响应之前完成)和从孩子到父母的一条消息的问题,那么处理起来很容易:

创建两个管道,一个用于与子通信,一个用于与父通信。 叉子。 子进程将管道的相关端(“to-child”管道的读取端,“to-parent”管道的写入端)复制到标准输入、输出。 子关闭所有管道文件描述符。 子执行测试程序(或打印标准错误报告失败的消息并退出)。 父级关闭管道的不相关端。 父级将消息写入子级并关闭管道。 父级读取子级的响应并关闭管道。 父母继续愉快地前进。

这会使子进程像僵尸一样四处游荡。如果父级要多次执行此操作,或者只需要知道子级的退出状态,则在关闭读取管道后,它将等待子级死亡,并收集其状态。

所有这些都是直截了当的常规编码。我相信您可以在 SO 上找到示例。


由于 Stack Overflow 上显然没有合适的示例,这里是上面概述的代码的简单实现。有两个源文件,basic_pipe.c 用于基本管道工作,myprogram.c 应该响应问题中显示的提示。第一个几乎是通用的;它可能应该循环读取操作(但这在我测试它的机器上并不重要,它运行的是 Ubuntu 14.04 衍生版本)。第二个很专业。

系统调用

pipe() fork() dup2() execv() waitpid() close() read() write()

basic_pipe.c

#include <assert.h>
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>

static char msg_for_child[] = "calculate 2 + 2\nsay 'hello world!'\n";
static char cmd_for_child[] = "./myprogram";

static void err_syserr(const char *fmt, ...);
static void be_childish(int to_child[2], int fr_child[2]);
static void be_parental(int to_child[2], int fr_child[2], int pid);

int main(void)

  int to_child[2];
  int fr_child[2];
  if (pipe(to_child) != 0 || pipe(fr_child) != 0)
    err_syserr("Failed to open pipes\n");
  assert(to_child[0] > STDERR_FILENO && to_child[1] > STDERR_FILENO &&
         fr_child[0] > STDERR_FILENO && fr_child[1] > STDERR_FILENO);
  int pid;
  if ((pid = fork()) < 0)
    err_syserr("Failed to fork\n");
  if (pid == 0)
    be_childish(to_child, fr_child);
  else
    be_parental(to_child, fr_child, pid);
  printf("Process %d continues and exits\n", (int)getpid());
  return 0;


static void be_childish(int to_child[2], int fr_child[2])

  printf("Child PID: %d\n", (int)getpid());
  fflush(0);
  if (dup2(to_child[0], STDIN_FILENO) < 0 ||
      dup2(fr_child[1], STDOUT_FILENO) < 0)
    err_syserr("Failed to set standard I/O in child\n");
  close(to_child[0]);
  close(to_child[1]);
  close(fr_child[0]);
  close(fr_child[1]);
  char *args[] =  cmd_for_child, 0 ;
  execv(args[0], args);
  err_syserr("Failed to execute %s", args[0]);
  /* NOTREACHED */


static void be_parental(int to_child[2], int fr_child[2], int pid)

  printf("Parent PID: %d\n", (int)getpid());
  close(to_child[0]);
  close(fr_child[1]);
  int o_len = sizeof(msg_for_child) - 1; // Don't send null byte
  if (write(to_child[1], msg_for_child, o_len) != o_len)
    err_syserr("Failed to write complete message to child\n");
  close(to_child[1]);
  char buffer[4096];
  int nbytes;
  if ((nbytes = read(fr_child[0], buffer, sizeof(buffer))) <= 0)
    err_syserr("Failed to read message from child\n");
  close(fr_child[0]);
  printf("Read: [[%.*s]]\n", nbytes, buffer);
  int corpse;
  int status;
  while ((corpse = waitpid(pid, &status, 0)) != pid && corpse != -1)
    err_syserr("Got pid %d (status 0x%.4X) instead of pid %d\n",
               corpse, status, pid);
  printf("PID %d exited with status 0x%.4X\n", pid, status);


static void err_syserr(const char *fmt, ...)

  int errnum = errno;
  va_list args;
  va_start(args, fmt);
  vfprintf(stderr, fmt, args);
  va_end(args);
  if (errnum != 0)
    fprintf(stderr, "(%d: %s)\n", errnum, strerror(errnum));
  exit(EXIT_FAILURE);

myprogram.c

#include <stdio.h>

int main(void)

  char buffer[4096];
  char *response[] =
  
    "4",
    "hello world!",
  ;
  enum  N_RESPONSES = sizeof(response)/sizeof(response[0]) ;

  for (int line = 0; fgets(buffer, sizeof(buffer), stdin) != 0; line++)
  
    fprintf(stderr, "Read line %d: %s", line + 1, buffer);
    if (line < N_RESPONSES)
    
      printf("%s\n", response[line]);
      fprintf(stderr, "Sent line %d: %s\n", line + 1, response[line]);
    
  
  fprintf(stderr, "All done\n");

  return 0;

示例输出

请注意,不能保证子代将在父代开始执行be_parental() 函数之前完成。

Child PID: 19538
Read line 1: calculate 2 + 2
Sent line 1: 4
Read line 2: say 'hello world!'
Sent line 2: hello world!
All done
Parent PID: 19536
Read: [[4
hello world!
]]
PID 19538 exited with status 0x0000
Process 19536 continues and exits

【讨论】:

感谢您的回复,我会在几天内尝试并一定会让您知道 奇怪的是,在 SO 上似乎还没有很好的例子,所以我将在这里添加一个参考:gist.github.com/konstantint/d49ab683b978b3d74172 @KT。我添加了执行答案中描述的工作代码。【参考方案3】:

您可以使用 expect 来实现: http://en.wikipedia.org/wiki/Expect

这是一个通常的期望程序会做的事情:

# Start the program
spawn <your_program>
# Send data to the program
send "calculate 2 + 2"
# Capture the output
set results $expect_out(buffer)

Expect 可以在 C 程序中使用 expect 开发库,因此您可以将以前的命令直接转换为 C 函数调用。这里有一个例子:

http://kahimyang.info/kauswagan/code-blogs/1358/using-expect-script-cc-library-to-manage-linux-hosts

您也可以从 perl 和 python 中使用它,它们通常比 C 更容易用于这些类型的目的。

【讨论】:

但这不是 C,OP 询问了 C 编程! 对,好的部分是它也可以从 C 中使用 expect-dev 完成。即使我通常更喜欢 perl 或 python 来完成这类任务。 感谢您的回答,有趣的事情,我从未听说过。但我想只使用本机系统工具的解决方案,没有任何辅助库/数据包。这在某些情况下看起来很方便!

以上是关于执行 linux 程序并通过 C/C++ 中的 stdin/stdout 与其通信的最简单方法的主要内容,如果未能解决你的问题,请参考以下文章

C或C++如何通过程序执行shell命令并获取命令执行结果?

开发环境Ubuntu 中使用 VSCode 开发 C/C++ ⑤ ( tasks.json 中的 args 数组配置分析 | 编译并执行 C++ 程序 )

开发环境Ubuntu 中使用 VSCode 开发 C/C++ ⑤ ( tasks.json 中的 args 数组配置分析 | 编译并执行 C++ 程序 )

Unix/Linux环境C编程新手教程(22) C/C++怎样获取程序的执行时间

C/C++:项目中的dll的构建和dll的导入函数并执行(VS2015)

C/C++:项目中的dll的构建和dll的导入函数并执行(VS2015)