如何使用套接字让多个进程与一个中心进程通信?

4
对于我的应用程序,我需要一个负责与许多客户端进程交互的中央进程。客户端进程需要一种识别和与中央进程通信的方法。此外,中央进程可能没有运行,客户端进程需要一种识别这一事实的方法。由于该应用程序将在类Unix系统上运行,因此我考虑使用套接字(sockets)来完成任务。我应该如何具体地使用套接字来完成这个任务(非常感谢提供实际代码!)?如果套接字不是最理想的选择,是否有更好的替代方案?

当客户端发现中央进程未运行时,它应该做什么(以及需要多快完成)? - Jonathan Leffler
它将fork()一个中央进程并与其通信。它必须足够快(即最多约0.5-1秒,但在操作系统术语中可能非常慢)。 - Mike
5个回答

7

命名管道并不是最理想的选择,它们最适合单读单写的情况。

而UNIX域套接字则非常适合此类场景。它们使用套接字API,端点名称是文件系统条目(与命名管道类似)。


以下是一个非常简单的示例(当然,您需要添加大量的错误检查!)。首先是服务器端:

#include <stdio.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>

#define SOCKNAME "/tmp/foo"

int main()
{
    int s1, s2;
    int i;
    struct sockaddr_un sa = { AF_UNIX, SOCKNAME };

    unlink(SOCKNAME);
    s1 = socket(AF_UNIX, SOCK_STREAM, 0);
    bind(s1, (struct sockaddr *)&sa, sizeof sa);
    listen(s1, 5);

    for (i = 0; i < 10; i++)
    {
        struct sockaddr_un sa_client;
        socklen_t sa_len = sizeof sa_client;
        FILE *f;

        s2 = accept(s1, (struct sockaddr *)&sa_client, &sa_len);
        f = fdopen(s2, "r+");
        fprintf(f, "Hello, you are client number %d\n", i + 1);
        fclose(f);
    }

    return 0;
}

现在是客户端部分:
#include <stdio.h>
#include <sys/socket.h>
#include <sys/un.h>

#define SOCKNAME "/tmp/foo"

int main()
{
    int s1;
    struct sockaddr_un sa = { AF_UNIX, SOCKNAME };
    FILE *f;
    char buffer[1024];

    s1 = socket(AF_UNIX, SOCK_STREAM, 0);
    connect(s1, (struct sockaddr *)&sa, sizeof sa);
    f = fdopen(s1, "r+");

    while (fgets(buffer, sizeof buffer, f) != NULL)
    {
        printf("Message received: %s", buffer);
    }

    fclose(f);

    return 0;
}

请注意,您应该在/var/run/yourappname下创建套接字,而不是在/tmp下。 man 7 unix是进一步调查的好资源。

0

这是一个已经使用数据库的应用程序吗?如果是,我会使用它而不是添加另一个全新的通信媒介。


不,它不使用数据库。 - Mike

0

D-Bus 提供面向对象的 IPC,还包括一个激活功能,如果服务器进程尚未运行,则会启动它。缺点是网络封装基本上是自己动手。


谢谢您的建议!不过我仍然想知道如何使用命名管道来实现,因为我想避免包含太多外部库。 - Mike
命名管道仅支持点对点连接,因此您需要为每个客户端进程创建一个管道,并且它们是单向的。传统的方法是使用套接字来实现,但您仍然需要自己处理激活。 - Ignacio Vazquez-Abrams
你能提供一些设置和使用套接字的示例代码吗?谢谢! - Mike
不,我自己从来没有做过。不过你可以查一下“Unix域套接字”。 - Ignacio Vazquez-Abrams
@Ignacio:FYI:你可以使用带有wait参数的inetd来启动服务。如果服务崩溃,下一次连接尝试将重新启动它。 - Robert S. Barnes
@Robert:假设您使用的是网络套接字,而不是Unix域套接字。 - Ignacio Vazquez-Abrams

0

你需要阅读该领域的标准教材之一,可能是Stevens的 'UNIX网络编程,第1卷,第3版'。

你需要做出许多决策,包括:

  • 中央服务器是否自己处理所有消息,还是接受其他进程的连接,fork一个子进程并将工作委托给其子进程,同时继续监听新连接?
  • 中央服务器是否需要保持会话运行,还是只对“一个(短)消息”做出响应?

这些决策会极大地影响所需的代码。如果进程每次只处理一个短消息,那么考虑使用UDP而不是TCP可能是合适的。如果进程正在与多个通信者进行长时间对话,并且没有fork一个子进程来处理它们,那么可以考虑使用线程编程。如果不是,则需要考虑及时性以及中央进程是否可能无限期地被阻塞。

如评论中所示,您还需要考虑进程应该如何处理中央进程未运行的情况,以及响应速度应该有多快。您还必须防止中央进程过于繁忙,以至于看起来它没有运行,即使它正在运行但正在忙于处理其他连接。如果无法连接到中央进程的进程尝试启动新副本,会遇到多大麻烦?

0
根据您提供的信息,似乎您正在构建一个分布式系统,其中有一个中央消息传递总线,组件使用该总线进行通信。出于以下原因,我建议使用TCP套接字:
  1. TCP sockets 可以让你灵活地在同一台机器或不同的机器上运行进程。
  2. 据我所知,在两个进程都在同一台机器上的情况下,许多 TCP 实现(例如 Linux 上的实现)将会识别这一点并绕过 TCP 协议栈,使TCP套接字像本地 Unix 域套接字一样快速。
  3. 使用 TCP 您可以实现“TCP Ping”,允许客户端检测中央进程是否正在运行/响应并识别该进程。您可以在 CPAN 上的 Perl Net::Ping 包中看到“TCP Ping”的实现。网络源码与 C 语言相似,应该能够理解它。基本上是一个非阻塞的 TCP 连接。
  4. 您可以设置 inetd 在每次连接尝试时自动启动服务。由于可能只想在同一时间运行一个副本,所以使用等待参数。这会导致一个副作用,即如果服务崩溃,下一个连接尝试将导致 inetd 重新启动它。请参阅此示例

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接