【问题标题】:How to IPC between PHP clients and a C Daemon Server?如何在 PHP 客户端和 C 守护程序服务器之间进行 IPC?
【发布时间】:2010-12-17 07:14:24
【问题描述】:

感谢您查看问题。

背景
我有几台机器在很短的时间内连续生成多个(最多 300 个)PHP 控制台脚本。这些脚本快速运行(不到一秒)然后退出。所有这些脚本都需要对大型trie 结构进行只读访问,每次每个脚本运行时将其加载到内存中都会非常昂贵。服务器运行 Linux。

我的解决方案
创建一个将 trie 结构保存在内存中并接收来自 PHP 客户端的请求的 C 守护程序。它将接收来自每个 PHP 客户端的请求,对内存结构执行查找并以答案进行响应,从而使 PHP 脚本免于执行该工作。请求和响应都是短字符串(不超过 20 个字符)

我的问题
我对 C 守护进程和进程间通信非常陌生。经过大量研究,我将选择范围缩小到消息队列和 Unix 域套接字。消息队列似乎足够了,因为我认为(我可能错了)它们将所有请求排队等待守护程序以串行方式回答它们。不过,Unix 域套接字似乎更易于使用。但是,我有各种各样的问题无法找到答案:

  1. PHP 脚本如何发送和接收消息或使用 UNIX 套接字与守护程序通信?相反,C 守护进程如何跟踪它必须向哪个 PHP 进程发送回复?
  2. 我见过的大多数守护程序示例都使用无限 while 循环,其中包含睡眠条件。我的守护进程需要为随时可能出现的许多连接提供服务,并且响应延迟至关重要。如果 PHP 脚本在休眠时发送请求,守护程序将如何反应?我已阅读有关 poll 和 epoll 的信息,这是等待收到消息的正确方法吗?
  3. 每个 PHP 进程总是会发送一个请求,然后等待接收响应。我需要确保如果守护程序关闭/不可用,PHP 进程将等待响应设置的最长时间,如果没有收到响应,则无论是否挂起,都将继续。可以这样做吗?

数据结构的实际查找速度非常快,我不需要任何复杂的多线程或类似的解决方案,因为我相信以 FIFO 方式处理请求就足够了。我还需要保持简单愚蠢,因为这是一项关键任务服务,而且我对这种类型的程序相当陌生。 (我知道,但我真的无路可走,学习体验会很棒)

我非常感谢代码 sn-ps 能够为我的具体问题提供一些启发。也欢迎提供指南和指针的链接,以进一步了解这个低级 IPC 的阴暗世界。

感谢您的帮助!


更新

现在比我问这个问题时知道的要多得多,我只是想向任何感兴趣的人指出Thrift 框架和ZeroMQ 在抽象出困难的套接字方面做得非常出色-水平编程。 Thrift 甚至免费为您提供服务器的脚手架!

事实上,与其费力地构建网络服务器,不如考虑使用已经为您解决问题的优秀异步服务器编写应用程序服务器代码。当然,使用异步 IO 的服务器非常适合不需要密集 CPU 处理(或者事件循环阻塞)的网络应用程序。

python 示例:Twistedgevent。我更喜欢gevent,我不包括tornado,因为它专注于HTTP服务器端。

Ruby 示例:EventMachine

当然,Node.js 基本上是当今异步服务器的默认选择。

如果您想深入了解,请阅读C10k ProblemUnix Network Programing

【问题讨论】:

  • 是的,守护进程应该是轮询的,我相信使用 unix 套接字,你所要做的就是将套接字支持编译到 PHP 中。但我不完全确定

标签: php c ipc daemon


【解决方案1】:

nanomsg 是用纯 C 编码的,所以我想它比用 C++ 编码的 Thrift 和 ZeroMQ 更适合您的需求。

wrappers 支持多种语言,包括 PHP。

这是一个使用NN_PAIR 协议的工作示例:(您也可以使用NN_REQREP

client.php

<?php

$sock = new Nanomsg(NanoMsg::AF_SP, NanoMsg::NN_PAIR);

$sock->connect('ipc:///tmp/myserver.ipc');

$sock->send('Hello World!', 0);

$sock->setOption(NanoMsg::NN_SOL_SOCKET, NanoMsg::NN_RCVTIMEO, 1000);

$data = $sock->recv(0, 0);

echo "received: " . $data . "\n";

?>

服务器.c

#include <stdio.h>
#include <string.h>
#include <nanomsg/nn.h>
#include <nanomsg/pair.h>

#define address "ipc:///tmp/myserver.ipc"

int main() {
  unsigned char *buf = NULL;
  int result;
  int sock = nn_socket(AF_SP, NN_PAIR);
  if (sock < 0) puts("nn_socket failed");

  if (nn_bind(sock, address) < 0) puts("bind failed");

  while ((result = nn_recv(sock, &buf, NN_MSG, 0)) > 0) {
    int i, size = strlen(buf) + 1;  // includes null terminator
    printf("RECEIVED \"%s\"\n", buf);
    for (i = 0; buf[i] != 0; i++)
      buf[i] = toupper(buf[i]);
    nn_send(sock, buf, size, 0);
    nn_freemsg(buf);
  }
  nn_shutdown(sock, 0);
  return result;
}

【讨论】:

    【解决方案2】:

    这是一个工作示例,其中 php 脚本向 C 守护程序发送请求,然后等待响应。它在数据报模式下使用 Unix 域套接字,因此它既快速又简单。

    client.php

    <?php
    
    do {
      $file = sys_get_temp_dir() . '/' . uniqid('client', true) . '.sock';
    } while (file_exists($file));
    
    $socket = socket_create(AF_UNIX, SOCK_DGRAM, 0);
    
    if (socket_bind($socket, $file) === false) {
      echo "bind failed";
    }
    
    socket_sendto($socket, "Hello World!", 12, 0, "/tmp/myserver.sock", 0);
    
    if (socket_recvfrom($socket, $buf, 64 * 1024, 0, $source) === false) {
      echo "recv_from failed";
    }
    echo "received: [" . $buf . "]   from: [" . $source . "]\n";
    
    socket_close($socket);
    unlink($file);
    ?>
    

    服务器.c

    #include <stdio.h>
    #include <sys/un.h>
    #include <sys/socket.h>
    
    #define SOCKET_FILE "/tmp/myserver.sock"
    #define BUF_SIZE    64 * 1024
    
    int main() {
      struct sockaddr_un server_address = {AF_UNIX, SOCKET_FILE};
    
      int sock = socket(AF_UNIX, SOCK_DGRAM, 0);
      if (sock <= 0) {
          perror("socket creation failed");
          return 1;
      }
    
      unlink(SOCKET_FILE);
    
      if (bind(sock, (const struct sockaddr *) &server_address, sizeof(server_address)) < 0) {
          perror("bind failed");
          close(sock);
          return 1;
      }
    
      while (1) {
        struct sockaddr_un client_address;
        int i, numBytes, len = sizeof(struct sockaddr_un);
        char buf[BUF_SIZE];
    
        numBytes = recvfrom(sock, buf, BUF_SIZE, 0, (struct sockaddr *) &client_address, &len);
        if (numBytes == -1) {
          puts("recvfrom failed");
          return 1;
        }
    
        printf("Server received %d bytes from %s\n", numBytes, client_address.sun_path);
    
        for (i = 0; i < numBytes; i++)
          buf[i] = toupper((unsigned char) buf[i]);
    
        if (sendto(sock, buf, numBytes, 0, (struct sockaddr *) &client_address, len) != numBytes)
          puts("sendto failed");
      }
    
    }
    

    【讨论】:

      【解决方案3】:

      脚本之间的 IPC 可以使用 Pipes 轻松完成。这使得实现变得非常简单。

      【讨论】:

      • 由于生产持续下降,我正在切换到套接字; bugs.php.net/bug.php?id=67320 建议这是因为 PHP-FPM 不处理 SIGPIPE,而且我的消费者不时死去。我的意思是他们是一个安全的“写后忘记”;相反,他们正在锁定生产。
      【解决方案4】:

      您还可以使用 PHP 的共享内存函数 http://www.php.net/manual/en/book.shmop.php 将数据结构加载到共享内存中。

      哦,这在文档中并不明显,但协调变量是 shmop_open 中的 $key。每个需要访问共享内存的进程都应该有相同的 $key。因此,一个进程使用 $key 创建共享内存。如果其他进程使用相同的 $key,则它们可以访问该共享内存。我相信你可以为 $key 选择任何你喜欢的东西。

      【讨论】:

        【解决方案5】:

        “问题”(也许不是?)是 SysV MQ 上肯定有很多消费者/生产者。如果您在生产者:消费者到资源模型上不一定有 m:n 需求,那么您正在做的事情完全有可能,但您在这里有一个请求/响应模型。

        使用 SysV MQ 时,您可能会遇到一些奇怪的问题。

        首先,您确定 INET 套接字对您来说不够快吗? http://us.php.net/socket-create-pair 是一个使用 unix 域套接字的快速 PHP 示例(当然,就像代码示例一样,对 PHP 端点使用 socket_create())。

        【讨论】:

          【解决方案6】:

          我怀疑Thrift 是你想要的。您必须编写一些胶水代码来执行 PHP C++ C,但这可能比自己编写代码更健壮。

          【讨论】:

          • Thrift 绝对是当今的行业标准 — wiki.apache.org/thrift/PoweredBy — 最初就是为这种用途而构思的(将 PHP 连接到 C 守护程序)。如果您“对 C 守护程序和进程间通信非常陌生”,那么您绝对应该看看;它将为您创建一个漂亮、快速的基于 libevent 的 C 服务器,并处理 PHP 和 C 之间的所有序列化。
          • 我看了 Thrift,这对我来说是新的,我必须说我印象深刻!似乎它会自动生成一个很棒的守护进程,并处理所有 IPC,我只需要添加实际功能!令人印象深刻,谢谢!
          • 同意,ZeroC 的 Ice - zeroc.com 是另一个开箱即用的客户端/服务器“库”,它也为您提供客户端和服务器组件。
          【解决方案7】:

          虽然我从未尝试过,但 memcached 和适当的 PHP extension 应该可以消除大部分繁重的工作。

          澄清:我隐含地假设如果你这样做,你会使用扁平键将 trie 的各个叶子放入 memcache,放弃 trie。当然,这种方法的可行性和可取性取决于许多因素,首先是数据源。

          【讨论】:

          • 每次我从 Memcached 检索庞大的数据结构时,序列化/反序列化的成本就是我尝试其他解决方案的原因,但谢谢!这可能是我将使用的最终解决方案。
          • 取决于树的大小。 Memcached 不会存储大于 meg 的任何内容。然后你就会在每个请求上反序列化对象的开销。
          • 在我看来,一个完整的 RPC 系统听起来太过分了,而 memcached 或 redis 都是您真正想要的(驻留在内存中的数据树)。假设您的树结构相当平坦,并且您的值可以存储“下一个”值,您可以将每次调用减少到几个查找。这将为您节省大量时间。
          • 一个常驻的“数据树”——它肯定不是一棵树,但可以这样工作。
          猜你喜欢
          • 2012-11-22
          • 1970-01-01
          • 2015-07-02
          • 2021-12-11
          • 1970-01-01
          • 2011-02-10
          • 2019-03-07
          • 2013-02-17
          • 2020-06-12
          相关资源
          最近更新 更多