开发者

How to IPC between PHP clients and a C Daemon Server?

and thanks for taking a look at the question.

The background

I have several machines that continuously spawn multiple (up to 300) PHP console scripts in a very short time frame. These scripts run quickly (less than a second) and then exit. All of these scripts need read only access to a large trie structure which would be very expensive to load into memory each time each one of the scripts runs. The server runs Linux.

My solution

Create a C daemon that keeps the trie structure in memory and receives requests from the PHP clients. It would receive a request from every one of the PHP clients, perform the lookup on the memory structure and respond with the answer, saving the PHP scripts from doing that work. Both requests and responses are short strings (no longer than 20 characters)

My problem

I am very new to C daemons and inter process communication. After much research, I have narrowed the choices down to Message Queues and Unix domain sockets. Message Queues seem adequate because I think (I may be wrong) that they queue up all of the requests for the daemon to answer them serially. Unix domain sockets seem to be easier to use, though. However, I have various questions I have not been able to find answers to:

  1. How can a PHP script send and receive messages or use a UNIX socket to communicate with the daemon? Conversely how does the C daemon keep track of which PHP process it has to send a reply to?
  2. Most examples of daemons I have seen use an infinite while loop with a sleep condition inside. My daemon needs to service many connections that can come at any time, and response latency is critical. How would the daemon react if the PHP script sends a request while it is sleeping? I have read about poll and epoll, would this be the correct way to wait for a received message?
  3. Each PHP process will always send one request, and then will wait to receive a response. I need to make sure that if the daemon is down / unavailable, the PHP process will wait for a response for a set maximum time, and if no answer is received will continue regardless instead of hanging. Can this be done?

The actual lookup of the data structure is very fast, I don't need any complex multi-threading or similar solution, as I believe handling the requests in a FIFO manner will be enough. I also need to keep it simple stupid, as this is a mission critical service, and I am fairly new to this type of program. (I know, but I really have no way around this, and the learning experience will be great)

I would really appreciate code snippets that shine some light into the specific questions that I have. Links to guides and pointers that will further my understanding into this murky world of low level IPC are also welcome.

Thanks for your help!


Update

Knowing much more now than I did at the time of asking this question, I just wanted to point out to anybody interested that both the Thrift framework and ZeroMQ do a fantastic job of abstracting away the hard, socket-level programming. Thrift even gives you the scaffolding for the server for free!

In fact, instead of going to all the hard work of building a network server, consider just writing you applications server code using a good asynchronous server that has already solved the problem for you. Of course servers that use asynch开发者_如何学Pythonronous IO are great for network applications that don't require intensive CPU processing (or else the event loop blocks).

Examples for python: Twisted, gevent. I prefer gevent, and I don't include tornado because it is focused on the HTTP server side.

Examples for Ruby: EventMachine

Of course, Node.js is basically the default choice for an async server nowadays.

If you want to go deeper, read the C10k Problem, and Unix Network Programing.


I suspect Thrift is what you want. You'd have to write a little glue code to do PHP <-thrift-> C++ <-> C, but that would probably be more robust than rolling your own.


You could also load the data structure into shared memory using PHP's shared memory functions http://www.php.net/manual/en/book.shmop.php.

Oh, it's not obvious from the documentation but the coordinating variable is $key in shmop_open. Every process needing access to the shared memory should have the same $key. So, one process creates the shared memory with $key. The other processes then can access that shared memory if they use the same $key. I believe you can choose whatever you like for $key.


Here is a working example where the php script sends a request to a C daemon and then waits for the response. It uses Unix domain sockets in datagram mode so it is fast and simple.

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);
?>

server.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");
  }

}


nanomsg is coded in plain C so I guess it is better suited for your needs than Thrift and ZeroMQ that are coded in C++.

It has wrappers for many languages including PHP.

Here is a working example using the NN_PAIR protocol: (you can use NN_REQREP too)

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";

?>

server.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;
}


The "problem" (maybe not?) is that there can certainly be many consumers/producers on the SysV MQs. Though perfectly possible for what you're doing if you don't necessarily have an m:n need on the producer:consumer to resources model, you have a request/response model here.

You can get some strange hangups with SysV MQ as it is.

First, are you sure that INET sockets aren't fast enough for you? A quick PHP example using unix domain sockets is at http://us.php.net/socket-create-pair (just as code example of course, use socket_create() for the PHP endpoint).


Although I have never tried it, memcached along with an appropriate PHP extension ought to eliminate most of the grunt work.

Clarification: I was implicitly assuming that if you did this, you would put the individual leaves of the trie into the memcache using flattened keys, ditching the trie. The feasibility and desirability of this approach, of course, depends on many factors, first and foremost being the data source.


IPC between script can be done much easily using Pipes. Which makes a much simple implementation.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜