junio - ¿Cómo IPC entre clientes PHP y un servidor Daemon C?
ipc 2018 (7)
Aquí hay un ejemplo de trabajo donde el script php envía una solicitud a un demonio C y luego espera la respuesta. Utiliza sockets de dominio Unix en modo datagrama, por lo que es rápido y simple.
cliente.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);
?>
servidor.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");
}
}
y gracias por echar un vistazo a la pregunta.
El fondo
Tengo varias máquinas que generan continuamente scripts de consola de PHP múltiples (hasta 300) en un período de tiempo muy corto. Estos scripts se ejecutan rápidamente (menos de un segundo) y luego salen. Todos estos scripts necesitan acceso de solo lectura a una estructura de trie grande que sería muy costoso cargar en la memoria cada vez que se ejecute cada uno de los scripts. El servidor ejecuta Linux.
Mi solución
Cree un demonio C que mantenga la estructura de trie en la memoria y reciba solicitudes de los clientes PHP. Recibiría una solicitud de cada uno de los clientes de PHP, realizaría la búsqueda en la estructura de la memoria y respondería con la respuesta, evitando que los scripts de PHP realizaran ese trabajo. Tanto las solicitudes como las respuestas son cadenas cortas (no más de 20 caracteres)
Mi problema
Soy muy nuevo en C daemons y la comunicación entre procesos. Después de mucha investigación, he reducido las opciones a las colas de mensajes y los sockets de dominio Unix. Las colas de mensajes parecen adecuadas porque creo (puedo estar equivocado) que ponen en cola todas las solicitudes para que el daemon las responda en serie. Sin embargo, los sockets de dominio Unix parecen ser más fáciles de usar. Sin embargo, tengo varias preguntas que no he podido encontrar respuestas a:
- ¿Cómo puede un script PHP enviar y recibir mensajes o usar un socket UNIX para comunicarse con el demonio? A la inversa, ¿cómo realiza el daemon C un seguimiento de a qué proceso PHP debe enviar una respuesta?
- La mayoría de los ejemplos de demonios que he visto utilizan un bucle infinito mientras que tiene una condición de sueño en su interior. Mi daemon necesita atender muchas conexiones que pueden llegar en cualquier momento, y la latencia de respuesta es crítica. ¿Cómo reaccionaría el demonio si el script PHP envía una solicitud mientras está durmiendo? He leído sobre encuestas y comentarios, ¿sería esta la manera correcta de esperar un mensaje recibido?
- Cada proceso de PHP siempre enviará una solicitud y luego esperará para recibir una respuesta. Necesito asegurarme de que si el daemon está inactivo / no disponible, el proceso PHP esperará una respuesta por un tiempo máximo establecido, y si no se recibe una respuesta, continuará independientemente en lugar de colgarse. Se puede hacer esto?
La búsqueda real de la estructura de datos es muy rápida, no necesito ningún subproceso múltiple complejo o solución similar, ya que creo que con el manejo de las solicitudes de manera FIFO será suficiente. También necesito mantenerlo simple y estúpido, ya que este es un servicio de misión crítica y soy bastante nuevo en este tipo de programa. (Lo sé, pero realmente no tengo forma de evitar esto, y la experiencia de aprendizaje será excelente)
Realmente apreciaría fragmentos de código que iluminen un poco las preguntas específicas que tengo. Los enlaces a guías e indicadores que me ayudarán a comprender mejor este mundo turbio de IPC de bajo nivel también son bienvenidos.
¡Gracias por tu ayuda!
Actualizar
Sabiendo mucho más ahora que yo al momento de formular esta pregunta, solo quería señalar a cualquier persona interesada que tanto el marco Thrift como el ZeroMQ hacen un trabajo fantástico al abstraer la programación a nivel de socket. ¡Thrift incluso te da los andamios para el servidor de forma gratuita!
De hecho, en lugar de continuar con el arduo trabajo de construir un servidor de red, considere simplemente escribir su código de servidor de aplicaciones usando un buen servidor asíncrono que ya haya resuelto el problema por usted. Por supuesto, los servidores que utilizan E / S asíncronas son excelentes para las aplicaciones de red que no requieren un procesamiento intensivo de la CPU (o los bloques de bucle de eventos).
Ejemplos para python: Twisted , gevent . Prefiero gevent, y no incluyo tornado porque está enfocado en el lado del servidor HTTP.
Ejemplos para Ruby: EventMachine
Por supuesto, Node.js es básicamente la opción predeterminada para un servidor asíncrono en la actualidad.
Si desea profundizar, lea el Problema C10k y la Programación de red Unix .
Aunque nunca lo he intentado, memcached junto con una extensión PHP adecuada debería eliminar la mayor parte del trabajo duro.
Aclaración: asumí implícitamente que si hicieras esto, pondrías las hojas individuales del trie en el memcache usando llaves aplanadas, abandonando el trie. La viabilidad y la conveniencia de este enfoque, por supuesto, depende de muchos factores, principalmente como fuente de datos.
El "problema" (¿quizás no?) Es que ciertamente puede haber muchos consumidores / productores en los MQ de SysV. Aunque es perfectamente posible para lo que está haciendo si no tiene necesariamente una m: n necesidad en el productor: modelo de consumidor a recursos, tiene un modelo de solicitud / respuesta aquí.
Puede obtener algunos problemas extraños con SysV MQ como es.
Primero, ¿estás seguro de que los enchufes INET no son lo suficientemente rápidos para ti? Un ejemplo rápido de PHP que usa sockets de dominio Unix está en http://us.php.net/socket-create-pair (solo como ejemplo de código, use socket_create () para el punto final de PHP).
IPC entre script se puede hacer fácilmente usando Pipes. Lo que hace una implementación mucho más simple.
Sospecho que Thrift es lo que quieres. Tendrías que escribir un pequeño código de pegamento para hacer PHP <-thrift-> C ++ <-> C, pero probablemente sería más robusto que rodar el tuyo.
También puede cargar la estructura de datos en la memoria compartida utilizando las funciones de memoria compartida de PHP http://www.php.net/manual/en/book.shmop.php .
Oh, no es obvio en la documentación, pero la variable de coordinación es $ key en shmop_open. Todo proceso que necesite acceso a la memoria compartida debe tener la misma $ clave. Entonces, un proceso crea la memoria compartida con $ key. Los otros procesos pueden acceder a esa memoria compartida si usan la misma $ clave. Creo que puedes elegir lo que quieras por $ clave.
nanomsg está codificado en C simple, por lo que creo que es más adecuado para sus necesidades que Thrift y ZeroMQ que están codificados en C ++.
Tiene wrappers para muchos idiomas, incluyendo PHP.
Aquí hay un ejemplo NN_PAIR
usa el protocolo NN_PAIR
: (también puede usar NN_REQREP
)
cliente.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";
?>
servidor.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;
}