Bash脚本与C++之间的持久IPC通信

5
问题:有一个C应用程序,每次事件发生时调用一个Bash脚本。还有一个C++应用程序需要跟踪这些事件。C++应用程序由select()事件循环驱动。在Bash脚本和C++应用程序之间实现最简单的IPC是什么?
C Application ---Each time calls Bash script---> Bash application ---???---> C++ Application

我想到了几种解决方案:

  1. 使用TCP网络套接字,但这意味着select将需要处理监听和实际套接字的事件
  2. 使用命名管道,但一旦bash脚本终止,管道的另一端也会关闭

有没有更简单的方法可以在select()中只使用一个文件描述符?

3个回答

3

我会使用一个无名的pipe()来完成任务。请记住,在UNIX系统中,通过fork()execve()创建的文件描述符会在两个进程中保持打开状态! 所以,您可以使用pipe()获取一对文件描述符,然后使用bash的echo >&FD将内容写入文件描述符,其中FD是文件描述符号码。

这非常简单易懂,且比其他任何方法都要节省资源。使用select()也没有问题,只需要不要像我在示例中那样在read()上阻塞,而是在pfds[0]上使用select()

下面是示例程序(产生10个bash进程,每个进程发送“hello work, my 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
'

这个程序可以使用一个管道,让子进程都向父进程发送“hello world, my pid: XXX\n”信息。虽然这个演示程序可以工作(在Linux和MacOS X下测试过,应该满足POSIX语义规范),但我仍然建议每个子进程使用单独的pipe()。这样做会减少问题,并且允许同时运行多个子进程,如果你有很多子进程,可以使用select()epoll()

由于pipe()非常便宜,特别是与bash比较,我绝对不会使用同一个管道来处理多个子进程(就像我的更新示例一样)。


Bash进程在每次事件发生时都会被创建。一旦第一个Bash进程实例终止,管道就会在两端关闭。这意味着我需要快速从C++应用程序重新打开管道,对吗? - user389238
我稍微修改了示例程序。现在它会生成10个bash进程,它们都会发送到同一个管道。这只有在任何时候都只有一个发送到该管道的bash进程运行时才能正常工作! - Johannes Weiss
@Ansis Atteka,正如您在我的演示中所看到的那样,它只需要一个管道即可工作。但是由于pipe()非常便宜,您也可以为每个bash使用一个pipe()。然后,当多个shell同时运行时,您将不会遇到任何问题。如果您有许多文件描述符要监听,则使用epoll() - Johannes Weiss
+1 鼓励你的辛勤工作 :) 但无论如何,我需要在 Bash 脚本和 C++ 应用程序之间进行 IPC,而不是在创建 Bash 脚本的 C 应用程序和 Bash 脚本本身之间进行IPC(请参见问题说明)。由于在我的情况下 Bash 脚本事先不知道 fd,因此不可避免地要使用命名管道(因此涉及文件系统)。另外,fork()对我来说也不是一个选项,这会真正关闭管道... - user389238
@Ansis Atteka:在UNIX中,您必须使用fork()来生成进程。即使您使用system(),它也会执行fork()+execve()。对于我的方法,我使用参数($1)将fd传递给bash脚本,如果您不喜欢这样,也可以使用文件。 - Johannes Weiss

3

Unix数据报或UDP套接字即可。Bash脚本只需向该套接字发送一个数据报(您可能需要一个辅助程序,在该套接字上执行sendmsg()sendto(),例如socat或netcat/nc)。接收方不需要接受数据报套接字的连接,一旦准备好读取,必须有一个数据报等待。受数据报长度限制。


+1 因为数据报套接字不需要处理 Accept()。此外,根据 man 手册,Unix 域数据报套接字是可靠的,而 UDP 套接字则不是。这正是我所需要的。谢谢。 - user389238
1
我认为在这里使用简单管道的提议更好,因为它使用的资源较少,不会触及文件系统,并且完全可以在bash中完成(无需使用sendmsg()/sendto()的外部程序)。 - Johannes Weiss
辅助程序可能不是必需的(我还没有尝试过),因为套接字文件名就是目标地址,所以 echo "abc" > socket 可能就足够了。 - Maxim Egorushkin
3
不应该使用像 "echo abc > socket" 这样的重定向符号来写入套接字,而应该使用 socat 或者 nc(版本 4.4+?),其中 nc 允许同时使用 -U 和 -u 参数。这是因为在套接字中无法像在普通文件中一样直接写入。请注意,翻译过程中不会添加解释或其他内容。 - user389238

1
你可以使用像ZeroMQ这样的轻量级消息队列。我认为你会使用它的PUSH-PULL机制。你的C程序或bash脚本推送事件,而C++应用程序则拉取它们。ZeroMQ是用C编写的,但除了许多其他语言外,还有一个C++和Python绑定。在这里可以看到一个PUSH-PULL示例。

+1。我也考虑过这种方法,但对于我的问题来说似乎有点过重(至少最初是这样)。我正在寻找不会增加额外依赖项的解决方案。 - user389238

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接