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的主要内容,如果未能解决你的问题,请参考以下文章

Python和C++之间最基本的IPC

bash特性之逻辑运算和shell脚本的编写

带有命令行参数的 C++ 的 Makefile 和 Bash 脚本

从 apache 正在运行的 c++ 代码调用 bash 脚本的权限问题

Python 和 PHP 之间的简单、强大的 IPC

算法及shell脚本编程基础