【问题标题】:Persistent IPC between Bash script and C++Bash 脚本和 C++ 之间的持久 IPC
【发布时间】: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

我想到的几个解决方案:

  1. 要使用 TCP 网络套接字,但这意味着 select 必须同时处理 Listening 和 Actual 套接字的事件
  2. 要使用命名管道,但一旦 bash 脚本终止,管道的另一端也会关闭

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

【问题讨论】:

    标签: c++ linux bash ipc


    【解决方案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。我也考虑过这种方法,但它似乎对我的问题来说太重了(至少最初是这样)。我一直在寻找不添加额外依赖项的东西。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-14
    • 1970-01-01
    • 1970-01-01
    • 2014-06-26
    • 1970-01-01
    • 2011-09-04
    相关资源
    最近更新 更多