Bash 脚本和 C++ 之间的持久 IPC
Posted
技术标签:
【中文标题】Bash 脚本和 C++ 之间的持久 IPC【英文标题】:Persistent IPC between Bash script and C++ 【发布时间】:2011-08-16 17:17:38 【问题描述】:问题: 有一个 C 应用程序 会在每次事件发生时调用 Bash 脚本。还有一个 C++ 应用程序 需要跟踪这些事件。 C++ 应用程序 由 select() 事件循环驱动。在 Bash 脚本 和 C++ 应用程序 之间实现最简单的 IPC 是什么?
C Application ---Each time calls Bash script---> Bash application ---???---> C++ Application
我想到的几个解决方案:
-
要使用 TCP 网络套接字,但这意味着 select 必须同时处理 Listening 和 Actual 套接字的事件
要使用命名管道,但一旦 bash 脚本终止,管道的另一端也会关闭
有没有更简单的方法可以让我在 select() 中只使用一个文件描述符?
【问题讨论】:
【参考方案1】:Unix datagram 或 UDP 套接字可以。 bash 脚本只会将数据报发送到该套接字(您可能需要一个在该套接字上执行sendmsg()
或sendto()
的帮助程序,例如socat 或netcat/nc)。接收者不需要接受数据报套接字的连接,一旦它准备好读取,就必须有一个数据报在等待。受数据报长度限制。
【讨论】:
+1 因为数据报套接字不必与 Accept() 混淆。同样根据手册页 Unix 域数据报套接字是可靠的,而 UDP 套接字则不是。所以这正是我想要的。谢谢。 我认为我使用简单管道的建议在这里效果更好,因为它使用的资源更少,不涉及文件系统并且完全可以在 bash 中使用(无需为sendmsg()
/@987654326 使用外部程序@)。
helper程序可能不需要(我没试过),因为socket文件名是目的地址,所以echo "abc" > socket
就够了。
必须使用 socat 或 nc 代替“echo abc > socket”(版本 4.4+?,其中 nc 允许同时使用 -U 和 -u)。因为您不能像在带有重定向符号的常规文件中那样在套接字中写入。【参考方案2】:
我会使用未命名的pipe()
。请记住:在 UNIX 中,在两个进程中 fork()
和 execve()
之后,文件描述符保持打开状态!所以你可以使用pipe()
获取一对文件描述符,然后使用bash的echo >&FD
写入fd,其中FD
是文件描述符编号。
这非常直接、简单,并且使用的资源比我想象的任何其他东西都要少。使用select()
没问题,只是不要阻止read()
是我在我的示例中所做的,但select()
是pfds[0]
。
示例程序(生成 10 个 bash 进程,它们发送“你好工作,我的 pid:XXX”,在生成进程之间等待 1 秒。示例只为所有子进程使用一个管道。我这样更改它是因为作者询问了在实践中,我会不推荐它(请参阅示例下方的注释)):
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
int main(int argc, char **argv)
int pfds[2];
pid_t p;
assert(0 == pipe(pfds));
p = fork();
if (p == 0)
unsigned int i;
char str_fd[3]; char *env[] = NULL;
char *args[] = "/bin/bash", "-c", "echo >&$1 hello world, my pid: $$"
, "-s", str_fd, NULL;
snprintf(str_fd, 3, "%d", pfds[1]);
str_fd[2] = 0;
for (i = 0; i < 10; i++)
p = fork();
if(0 == p)
assert(0 ==
execve( "/bin/bash", (char *const*)args
, (char *const*)env));
else if (0 > p)
perror("fork");
exit(1);
else
wait(NULL);
sleep(1);
else if(p > 0)
char *buf = malloc(100);
ssize_t sz;
printf("fd is %d, <hit Ctrl+C to exit>\n", pfds[1]);
while(0 < ( sz = read(pfds[0], buf, 100)))
buf[99] = 0;
printf("received: '%s'\n", buf);
free(buf);
if (0 == sz)
fprintf(stderr, "EOF!");
else
perror("read from bash failed");
wait(NULL);
else
perror("fork failed");
exit(1);
return 0;
示例程序输出:
$ gcc test.c && ./a.out
fd is 4, <hit Ctrl+C to exit>
received: 'hello world, my pid: 779
'
received: 'hello world, my pid: 780
'
received: 'hello world, my pid: 781
'
received: 'hello world, my pid: 782
'
received: 'hello world, my pid: 783
'
received: 'hello world, my pid: 784
'
received: 'hello world, my pid: 785
'
received: 'hello world, my pid: 786
'
received: 'hello world, my pid: 787
'
received: 'hello world, my pid: 788
'
工作,bashs 发送 'hello world, my pid: XXX\n' 到父进程都使用一个管道:-)。
尽管如此,这似乎可以像演示程序显示的那样工作(使用 POSIX 语义并在 Linux 和 MacOS X 下测试应该没问题),我建议每个子进程使用一个 pipe()
。这将导致更少的问题,并且一次运行多个子进程也是可能的。 select()
或 epoll()
(如果您有许多子进程)是您的朋友。
由于pipe()
非常便宜,特别是与 bash! 相比,我绝对不会为多个孩子使用同一管道(就像我现在更新的示例一样)。
【讨论】:
每次事件发生时都会创建 Bash 进程。一旦 Bash 进程的第一个实例终止,管道的两端就会关闭。这意味着我需要从 C++ 应用程序中快速重新打开管道,对吗? 我对示例程序稍作改动。我现在确实产生了 10 个 bash 进程,它们都发送到管道。它总是相同的管道。这只有在每次只有一个 bash 进程发送到该管道时才有效! @Ansis Atteka,正如您在我的演示中看到的那样,它只使用一个管道。但是由于 pipe() 非常便宜,您也可以在每个 bash 中使用一个 pipe()。然后,当多个 shell 同时运行时,您将不会遇到任何问题。如果您有很多文件描述符要监听,请使用epoll()
。
+1 努力工作 :) 但无论如何我需要 Bash 脚本和 C++ 应用程序之间的 IPC,而不是 C 应用程序(创建 Bash 脚本)和 Bash 脚本本身(参见问题陈述)之间的 IPC。不可避免地,使用这种方法我将不得不使用命名管道(因此涉及文件系统),因为在我的情况下,Bash 脚本现在没有提前 fd。它也不会真正关闭管道,因为 fork() 不是我的选择...
@Ansis Atteka:您必须使用fork()
在 UNIX 中生成进程。即使您使用system()
,它也会执行fork()
+execve()
。对于我的方法,我使用参数 ($1
) 将 fd 传递给 bash 脚本,如果您不喜欢这样,也可以使用文件。【参考方案3】:
您可以使用像ZeroMQ 这样的轻量级消息队列。我想你会使用它的 PUSH-PULL 机制。您的 C 程序或 bash 脚本在 C++ 应用程序拉取事件时推送事件。 ZeroMQ 是用 C 语言编写的,但除了许多其他语言之外,还有 C++ 和 Python 绑定。请参阅here 了解推拉示例。
【讨论】:
+1。我也考虑过这种方法,但它似乎对我的问题来说太重了(至少最初是这样)。我一直在寻找不添加额外依赖项的东西。以上是关于Bash 脚本和 C++ 之间的持久 IPC的主要内容,如果未能解决你的问题,请参考以下文章
带有命令行参数的 C++ 的 Makefile 和 Bash 脚本