【问题标题】:Linux best way in two-way IPC in CC中双向IPC的Linux最佳方式
【发布时间】:2015-02-06 18:07:43
【问题描述】:

当被问到this question 时,我在完成我的示例时遇到了问题。
我在 Google 中搜索了实现 IPC 的方法。
我无法决定哪种方式最适合编写我的程序。
我尝试了很多实现,但遇到了很多复杂问题。

我想拥有:
1.父进程管理子进程-OK(模板)
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

父.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 方式更好(安全和速度)? 你能分享一个与上述模板匹配的非常简单的例子吗?

【问题讨论】:

  • 在头文件中编写除内联以外的函数实现是一种不好的做法。
  • @jujj 已更正 - 谢谢
  • 在下面的评论中,您声明您正在使用 GTK+。 GTK+ 有GtkSocketGtkPlug,它们允许一个GTK+ 应用程序嵌入来自另一个应用程序的小部件(使用GTK+ 或Qt 或其他遵循XEMBED 规范的小部件)。
  • 如果使用管道、命名管道或套接字进行通信,可以使用fcntl(descriptor, F_SETFL, O_ASYNC)设置此描述符生成SIGIO信号;最好使用例如将其更改为实时信号。 fcntl(descriptor, F_SETSIG, SIGRTMIN+0)。但是,信号处理程序只能使用async-signal safe 函数,否则应用程序行为未定义。

标签: c linux


【解决方案1】:

这是我最近编写的一个多进程程序的一些设置代码,使用 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 *输出和输入写入孩子。唯一需要注意的是调试如果父级不处理它,打印到子级的 stdout 可能会导致意外行为,因此通常将调试消息打印到子级的 stderr 是最安全的。

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

【讨论】:

  • 两次使用管道函数是什么原因?
  • 为每个数据发送方向创建一个单独的管道,管道通常不是双向的。
  • 如何在异步模式下做到这一点?
  • 什么意思?子和父异步通信
  • 只需使用socketpair 而不是管道。
【解决方案2】:

有许多不同的方式来实现 IPC。如需更好的比较,请参阅 Stevens' 书籍。经典的是“UNIX 环境中的高级编程”,但也有“UNIX 网络编程,第 2 卷,第二版:进程间通信”。我知道有时指向其他地方的引用并不被认为是一种好的形式,但无论这是学术问题还是商业问题,大多数 UNIX 程序员都会认为 Stevens 是一种宝贵的资源。

也就是说,以下是 IPC 的主要选项:

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

  2. 使用命名管道或 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. 以上的一些组合。

我只在内核分支(例如 Binder)或开发中(例如 KDBus)中省略了一些内容。

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

现在,其中大部分都可以用于您提到的应用程序。看起来您想发送可变大小的消息,因此如果您使用基于流的协议,通常的技巧是将数据包的长度作为前 1、2 或 4 个字节发送(取决于最大长度)。基于数据包的协议在这里(显然)更容易,但每个协议都有自己的最大数据包大小。你关心可靠性吗?你关心便携性吗?你关心速度吗?在它们之间进行选择时,所有这些都是合理的考虑因素。

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

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

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

【讨论】:

  • “你关心可靠性吗?” “你关心速度吗?” - 是的,这很重要。 “你关心可移植性吗?- 一开始不是那么关心,但后来是的。然后我将使用具有自己循环的 GTK+ 库。
  • 管道和套接字速度惊人。如果您正在使用 GTK+ 事件循环,我会使用套接字对。
  • 谢谢。你能举个例子吗?最重要的是:在新消息发出信号时阅读消息。
  • @s77s:我添加了几个例子。
  • 最后一个问题:sa_flags 中的 SIGIO 是否适合在 socketpair 中捕获新消息信号(第 11.4 节)?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-09-15
  • 1970-01-01
  • 2010-09-20
  • 1970-01-01
相关资源
最近更新 更多