user2138912
user2138912

Reputation: 1698

Persistent IPC between Bash script and C++

Problem: There is a C application that calls a Bash script each time an event happens. And there is also a C++ application that needs to track down those events. The C++ application is driven by a select() event loop. What would be the most simplest IPC to implement between Bash script and C++ application?

C Application ---Each time calls Bash script---> Bash application ---???---> C++ Application

Few solutions that came into my mind:

  1. To use TCP networking sockets, but this would mean that select will have to handle events for both Listening and Actual sockets
  2. To use Named pipes, but once the bash script terminates then the other end of the pipe is closed as well

Is there something simpler that would allow me to use only one File Descriptor in select()?

Upvotes: 5

Views: 3330

Answers (3)

Maxim Egorushkin
Maxim Egorushkin

Reputation: 136435

Unix datagram or UDP socket would do. The bash script would just send a datagram to that socket (you may need a helper program that does sendmsg() or sendto() on that socket, such as socat or netcat/nc). The receiver does not need to accept connections for datagram sockets, once it is ready for read there must be a datagram waiting. Subject to datagram length restrictions.

Upvotes: 3

Johannes Weiss
Johannes Weiss

Reputation: 54081

I would do it with a unnamed pipe(). Remember: In UNIX file descriptors remain open after fork() and execve() in both processes! So you can use pipe() to get a pair of file descriptors and then write into the fd using bash's echo >&FD where FD is the file descriptor number.

That is very straight forward, easy and uses less resources than anything else I assume. The use of select() is no problem, just do not block on read() is I do in my sample but select() on pfds[0].

Sample program (spawns 10 bash processes which send 'hello work, my pid: XXX', waiting 1s between spawning the processes. The sample only uses one pipe for all the children. I changed it that way because the author asked about that. In practice, I would NOT recommend it (see the note below the sample)):

#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;
}   

sample program output:

$ 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
'

works, bashs send 'hello world, my pid: XXX\n' to parent process all using one pipe :-).

Nevertheless that seems to work as the demo program shows (should be ok using the POSIX semantics and tested under Linux and MacOS X), I would recommend using one pipe() per child process. That will lead to fewer problems and running more than one child process at a time is possible, too. select() or epoll() (if you have MANY child processes) are your friend.

Since pipe() is very cheap, in particular compared to bash!, I would definitely not use the same pipe for more than more than one child (as my updated sample now does).

Upvotes: 3

Jonny Dee
Jonny Dee

Reputation: 849

You could use a lightweight messaging queue like ZeroMQ. I think you would use its PUSH-PULL mechanism. Your C program or the bash script pushes events while the C++ application pulls them. ZeroMQ is written in C but, besides many others, there exists a C++ and a Python binding for it. See here for a PUSH-PULL example.

Upvotes: 1

Related Questions