25

并感谢您查看问题。

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

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

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

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

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

我真的很感激能对我遇到的具体问题有所启发的代码片段。也欢迎链接到指南和指针,以进一步了解这个低级 IPC 的阴暗世界。

谢谢你的帮助!


更新

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

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

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

Ruby 示例:EventMachine

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

如果您想深入了解,请阅读C10k 问题Unix 网络编程

4

7 回答 7

6

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

于 2009-11-17T03:38:11.683 回答
2

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

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

于 2009-11-17T04:43:15.877 回答
2

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

它有许多语言的包装器,包括 PHP。

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

客户端.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;
}
于 2017-04-14T20:47:08.677 回答
2

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

客户端.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");
  }

}
于 2017-04-15T03:58:54.253 回答
1

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

使用 SysV MQ,您可能会遇到一些奇怪的挂断。

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

于 2009-11-17T03:57:54.923 回答
0

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

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

于 2009-11-17T02:33:38.447 回答
0

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

于 2014-07-17T13:34:57.707 回答