Linux中C语言双向IPC的最佳方式

时间:2014-12-08 23:30:22

标签: c linux

当被问到this question时,我有一个问题要完成我的例子 我搜索了在谷歌实施IPC的方法 我无法决定哪种方式最适合编写我的程序 我尝试了很多实现,并且有很多并发症。

我希望:
 1.管理子进程的父进程 - 确定(模板)
 2.父母和孩子必须实施新消息信号回调  3.一个进程不知道来自其他进程的消息大小(char *)

我的代码:

header.h:

#ifndef MESSAGES_H
#define MESSAGES_H

#include <stdio.h>
#include <stdlib.h>

// need here: some includes and definitions

inline char * read_message( /* need here: some params */ ) {
    // need here: read message function
}

inline char * send_message( /* need here: some params */ ) {
    // need here: send message function
}
#endif

parent.c:

#include "header.h"

// parent specyfic includes and definitions

void on_message( /* need here: some parameters */ ) {
    char *message = read_message( /* need here: some other parameters */ );
    // do something with / if message etc.
}

int runChild(key) {
    int pid = fork();
    if (pid == 0) {
        execl("./child", "./child", /* params here */, null);
    }else{
        return pid;
    }
}

int main(int argc, char *argv[]) {
    // need here: prepare IPC
    // need here: on new message event call function "on_message"
    int childPid = runChild(key);
    // loop - for example: gtk_main()
    // need here: close childs
}

child.c

#include "header.h"

// child specyfic includes and definitions

void on_message( /* need here: some parameters */ ) {
    char *message = read_message( /* need here: some other parameters */ );
    // do something with / if message etc.
}

int main(int argc, char *argv[]) {
    // need here: prepare IPC
    // need here: on new message event call function "on_message"
    int pid = getpid();
    int parentPid = getppid();
    printf("Child with pid %d is ready for messages from parent with pid: %d", pid, parentPid);
    // event loop - for example: gtk_main()
}

在该示例程序模板中哪种IPC方式更好(安全和速度)? 你能分享一个与上述模板匹配的非常简单的例子吗?

2 个答案:

答案 0 :(得分:9)

实施IPC有许多不同的方法。要获得良好的比较,请参阅Stevens'本书。经典之作是在UNIX环境中&#39;高级编程,但也有&#39; UNIX网络编程,第2卷,第2版:进程间通信&#39; 。我知道有时候在其他地方指向引用并不是一种好的形式,但无论这是一个学术问题还是商业问题,大多数UNIX程序员都会认为史蒂文斯是一个非常宝贵的资源。

那就是说,这是IPC的主要选择:

  1. 在进程之间使用pipe()。格式将始终基于流;如果你要发送数据结构,这可能会很麻烦,因为你不仅要担心序列化,还需要担心缓冲和翻译数据包。回到消息。管道是单向的,因此您需要两个用于双向通信。

  2. 使用命名管道或fifo。这允许多对一通信,并且在一端退出后,fifo仍然存在。否则按照(1)。

  3. 在进程之间使用socketpair - 特别是unix域套接字。套接字是双向的。您可以使用流套接字(SOL_STREAM),数据报(不可靠,无法保证排序 - SOCK_DGRAM)或可能更好的顺序可靠双向数据包通信(SOCK_SEQPACKET)。基于分组的通信意味着您可以(例如)在每个数据包中放置一个数据结构。

  4. 使用信号。实际上,您可以一次发送整数。信号与线程混合不好,处理中断的系统调用很难,并且各种竞争条件使它们不可靠,除非您知道自己在做什么并且不太担心可移植性。在大多数情况下最好避免。

  5. 使用系统V信号量(semget等)或POSIX信号量(sem_open等)。用于在进程之间发送信号以实现同步,但不多。

  6. 使用共享内存(shmget等) - 使多个进程可以看到相同的页面。您需要结合某种同步方法。

  7. System V消息队列(msgget等) - 维护两个进程之间的数据包(消息)队列。

  8. 以上的一些组合。

  9. 我只在内核的分支(例如Binder)或正在开发中(例如KDBus)省略了一些东西。

    几乎所有上述内容的示例和教程都可以找到here

    现在,大部分内容都可用于您提及的应用程序。看起来你想发送可变大小的消息,所以如果你使用基于流的协议,正常的hack是将数据包的长度作为前1,2或4个字节发送(取决于最大长度)。基于分组的协议在这里(显然)更容易,但每个都有自己的最大数据包大小。你关心可靠性吗?你关心便携性吗?你关心速度吗?在选择它们时,所有这些都是有效的关注点。

    最后,基于FD的方法(例如管道,套接字)的一个优点是你可以将它们添加到普通的select()循环中;如果您的计划中还有其他事情发生,这可能会有所帮助。

    您在评论中询问了socketpair代码的一些示例。我重申最高层对斯蒂芬斯的评论。如果没有:

    • Socketpair() in C/Unix显示了使用fork()为IPC设置套接字对的一个很好的示例。
    • 上面提到的教程在socketpair() here上有一个很好的部分。

答案 1 :(得分:2)

这是我最近写的一个多进程程序的一些设置代码,使用select来提供非阻塞等待。这显然也是在C ++中实现它的更好方法之一,因为从我收集的文件描述符得不到标准库的良好支持......

// Parent
int main(int argc, char **argv) {

    // Pipe, fork, exec (to run robot in child)
    int toParent[2], fromParent[2];
    pipe(toParent);
    pipe(fromParent);

    // Redirect childs stdin/stdout
    if (fork()) { // parent
        close(toParent[1]); // [1] == write
        close(fromParent[0]); // [0] == read
    }
    else {
        close(toParent[0]);
        close(fromParent[1]);
        dup2(toParent[1], 1);
        dup2(fromParent[0], 0);
        close(toParent[1]);
        close(fromParent[0]);
        execl("../robot/robot", "../robot/robot", (char *) NULL);
    }


    FILE * output = fdopen(fromParent[1], "w");
    FILE * input = fdopen(toParent[0], "r");

    // Set up for select() read of input pipe
    fd_set set;
    struct timeval timeout;

    // Initialize the file descriptor set.
    FD_ZERO(&set);
    FD_SET(toParent[0], &set);

    // Initialize the timeout data structure
    timeout.tv_sec = 0;
    timeout.tv_usec = 10;

    while(1) {
        // Non-blocking read of pipe
        // NOTE: only expecting to read one pipe so no need to check which pipe got data
        if (select(toParent[0]+1, &set, NULL, NULL, &timeout) > 0) {
            // read the input pipe here
        }
        // Reset select FD -- maybe only do this when an input has been read?
        FD_ZERO(&set);
        FD_SET(toParent[0],&set);
}

一般的想法是允许孩子通过stdin / stdout(通过使用dup2())与父进行通信,然后使用FILE *输出和输入写入child。唯一的警告是调试打印到子节点中的stdout可能会导致意外行为,如果父节点没有处理它,所以一般来说最安全的是将调试消息打印到子节点中的stderr。

至于回调,您可以使用选择,这在其他地方已有很好的记录。