2009-11-17 18 views
22

i dziękuję za spojrzenie na to pytanie.Jak IPC między klientami PHP a serwerem C Daemon?

Tło
mam kilka maszyn, które stale tarło wielokrotność (do 300) skrypty PHP konsoli w bardzo krótkim czasie. Skrypty te działają szybko (mniej niż sekundę), a następnie kończą działanie. Wszystkie te skrypty wymagają dostępu tylko do odczytu do dużej struktury trie, co byłoby bardzo kosztowne, aby załadować je do pamięci za każdym razem, gdy uruchamia się każdy ze skryptów. Na serwerze działa Linux.

Moje rozwiązanie
Utwórz demona C, która utrzymuje strukturę TRIE w pamięci i odbiera żądania od klientów PHP. Otrzyma żądanie od każdego klienta PHP, wykona wyszukiwanie w strukturze pamięci i odpowie odpowiedzą, oszczędzając skrypty PHP przed wykonaniem tej pracy. Oba wnioski i odpowiedzi są krótkie ciągi (nie dłużej niż 20 znaków)

mój problem
Jestem bardzo nowe do demonów C i komunikacji między procesami. Po wielu badaniach ograniczyłem wybór do gniazd kolejki wiadomości i gniazd domenowych systemu Unix. Kolejki wiadomości wydają się odpowiednie, ponieważ myślę (może się mylę), że kolejkują wszystkie żądania demona, aby odpowiedzieć na nie serialnie. Jednak domeny unixowe wydają się łatwiejsze w użyciu. Jednak mam różne pytania, na które nie byłem w stanie znaleźć odpowiedzi:

  1. W jaki sposób skrypt PHP może wysyłać i odbierać wiadomości lub używać gniazda UNIX do komunikowania się z demonem? Odwrotnie, w jaki sposób demon C śledzi, na jakim procesie PHP ma wysłać odpowiedź?
  2. Większość przykładów demonów, które widziałem, używa nieskończonej pętli while ze stanem snu wewnątrz. Mój demon musi obsługiwać wiele połączeń, które mogą pojawić się w dowolnym momencie, a opóźnienie odpowiedzi jest krytyczne. Jak demon zadziała, jeśli skrypt PHP wyśle ​​żądanie podczas snu? Czytałem o ankiecie i epolu, czy byłby to właściwy sposób oczekiwania na otrzymaną wiadomość?
  3. Każdy proces PHP zawsze wysyła jedno żądanie, a następnie czeka na odpowiedź. Muszę się upewnić, że jeśli demon jest wyłączony/niedostępny, proces PHP będzie czekał na odpowiedź przez ustawiony maksymalny czas, a jeśli żadna odpowiedź nie zostanie odebrana, będzie kontynuowana niezależnie od zawieszania. Czy można to zrobić?

Faktyczne wyszukiwanie struktury danych jest bardzo szybkie, nie potrzebuję żadnego skomplikowanego wielowątkowego lub podobnego rozwiązania, ponieważ uważam, że obsługa zgłoszeń w sposób FIFO będzie wystarczająca. Muszę również zachować głupotę, ponieważ jest to usługa o znaczeniu krytycznym, a ja jestem całkiem nowy w tego rodzaju programie. (Wiem, ale naprawdę nie mam na to rady, a nauka będzie wspaniała)

Naprawdę doceniam fragmenty kodu, które rzucają nieco światła na konkretne pytania, które mam. Zapewniam także linki do przewodników i wskaźników, które pomogą mi lepiej zrozumieć ten mroczny świat niskiego poziomu IPC.

Dzięki za pomoc!


Aktualizacja

Wiedząc o wiele bardziej teraz niż ja w czasie z tym pytaniem, chciałem tylko zwrócić uwagę wszystkich zainteresowanych, że zarówno Thrift ramy i ZeroMQ zrobić fantastyczną pracę abstrahując od hotelu trudne, programowanie na poziomie gniazd. Oszczędzanie daje ci nawet rusztowanie na serwer za darmo!

W rzeczywistości, zamiast iść do ciężkiej pracy nad budową serwera sieciowego, rozważ napisanie kodu serwera aplikacji przy użyciu dobrego asynchronicznego serwera, który już rozwiązał problem. Oczywiście serwery korzystające z asynchronicznego IO doskonale nadają się do aplikacji sieciowych, które nie wymagają intensywnego przetwarzania procesora (lub bloków pętli zdarzeń).

Przykłady dla python: Twisted, gevent. Wolę gevent i nie włączam tornada, ponieważ koncentruje się on na stronie serwera HTTP.

Przykłady Ruby: EventMachine

Oczywiście Node.js jest w zasadzie domyślnym wyborem dla serwera async dzisiejszych czasach.

Jeśli chcesz wejść głębiej, przeczytaj C10k Problem i Unix Network Programing.

+0

Tak, demon powinien być odpytywanie i wierzę, że z gniazda unix wszystko co musisz zrobić, to mieć gniazda obsługują skompilowane w PHP .. ale nie jestem całkowicie pewny. – Earlz

Odpowiedz

4

Podejrzewam, że Thrift jest tym, czego potrzebujesz. Trzeba by napisać mały kod na klejenie do PHP < -thrift-> C++ < -> C, ale prawdopodobnie byłby bardziej wytrzymały niż toczenie własnym.

+2

Thrift jest zdecydowanie standardem branżowym - http://wiki.apache.org/thrift/PoweredBy - i był początkowo pomyślany dokładnie w tym celu (podłączanie demonów PHP do C). Jeśli jesteś "bardzo nowy dla demonów C i komunikacji między procesami" powinieneś zdecydowanie rzucić okiem; stworzy dla ciebie miły, szybki serwer oparty na libevent i poradzi sobie z serializacją PHP i C. – cce

+0

Spojrzałem na Thrift, który był dla mnie nowy i muszę powiedzieć, że jestem pod wrażeniem! Wygląda na to, że automatycznie generuje on wspaniałego demona z opieką wszystkich IPC i muszę tylko dodać rzeczywistą funkcjonalność! Imponujące, dzięki! –

+0

Zgadzam się, ZeroC's Ice - zeroc.com to kolejna "biblioteka" klienta/serwera, z której można korzystać po wyjęciu z pudełka, co daje również składniki klienta i serwera. –

0

Chociaż nigdy tego nie próbowałem, memcached wraz z odpowiednim PHP extension powinien wyeliminować większość pracy gruntu.

Wyjaśnienie: Domyślam się, że jeśli to zrobisz, umieścisz indywidualne liście triesty w memcache za pomocą spłaszczonych klawiszy, porzucając trie. Wykonalność i celowość tego podejścia zależy oczywiście od wielu czynników, przede wszystkim od źródła danych.

+0

Tylko koszt serializacji/odszyfrowywania ogromnej struktury danych za każdym razem gdy ją odzyskałem z Memcached jest powodem, dla którego wypróbowuję to inne rozwiązanie, ale dzięki! To może być ostateczne rozwiązanie, którego użyję. –

+0

Zależy od wielkości trie. Memcached nie przechowuje niczego większego niż meg. A następnie masz narzut deserializacji obiektu na każde żądanie. –

1

"Problem" (być może nie?) Polega na tym, że na SysV MQs może z pewnością być wielu konsumentów/producentów. Choć jest to absolutnie możliwe, jeśli nie masz koniecznie potrzeby producenta na poziomie: konsument do modelu zasobów, masz tutaj model żądania/odpowiedzi.

Możesz uzyskać dziwne zawieszenie z SysV MQ takim, jaki jest.

Po pierwsze, czy jesteś pewien, że gniazda INET nie są wystarczająco szybkie? Szybki przykład PHP używający gniazd domeny unix to http://us.php.net/socket-create-pair (tak jak przykładowy kod, użyj socket_create() dla punktu końcowego PHP).

2

Można również wczytać strukturę danych do pamięci współdzielonej przy użyciu funkcji pamięci wspólnej PHP http://www.php.net/manual/en/book.shmop.php.

Och, to nie jest oczywiste z dokumentacji, ale zmienną koordynującą jest $ key w shmop_open. Każdy proces wymagający dostępu do pamięci współużytkowanej powinien mieć ten sam klucz $. Zatem jeden proces tworzy wspólną pamięć za pomocą $ key. Pozostałe procesy mogą uzyskać dostęp do pamięci współdzielonej, jeśli używają tego samego klawisza $. Wierzę, że możesz wybrać, co chcesz za $ key.

0

IPC pomiędzy skryptami można zrobić bardzo łatwo za pomocą potoków. Co czyni prostą implementację.

0

jest kodowany w prostym C, więc domyślam się, że jest lepiej dopasowany do twoich potrzeb niż Thrift i ZeroMQ, które są kodowane w C++.

Posiada wrappers dla wielu języków, w tym PHP.

Oto przykład działa przy użyciu protokołu NN_PAIR: (można użyć NN_REQREP zbyt)

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; 
} 
0

Oto przykład roboczych gdzie skrypt php wysyła żądanie do demona C, a następnie czeka na odpowiedź. Używa gniazd domeny Unix w trybie datagramowym, więc jest szybki i prosty.

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

} 
Powiązane problemy