Lettura obbligatoria prima dello sviluppo

Per sviluppare applicazioni con Workerman, è necessario comprendere i seguenti contenuti:

I. Differenze tra sviluppo Workerman e sviluppo PHP normale

A parte le funzioni delle variabili associate al protocollo HTTP che non possono essere utilizzate direttamente, lo sviluppo con Workerman non è molto diverso dallo sviluppo PHP normale.

1. Differenze nei protocolli di livello applicativo

  • Lo sviluppo PHP normale è generalmente basato sul protocollo di livello applicativo HTTP, in cui il WebServer ha già effettuato l'analisi del protocollo per lo sviluppatore.
  • Workerman supporta vari protocolli, attualmente include protocolli come HTTP e WebSocket. Workerman raccomanda agli sviluppatori di utilizzare comunicazioni con protocolli personalizzati più semplici.

2. Differenze nel ciclo di vita delle richieste

  • PHP nelle applicazioni Web rilascia tutte le variabili e le risorse dopo ogni richiesta.
  • Le applicazioni sviluppate con Workerman rimangono in memoria dopo il primo caricamento e analisi, il che consente che la definizione delle classi, gli oggetti globali e i membri statici delle classi non vengano rilasciati, facilitando un riutilizzo successivo.

3. Attenzione a evitare definizioni duplicate di classi e costanti

  • Poiché Workerman memorizza nella cache i file PHP compilati, è necessario evitare di richiedere/includere più volte lo stesso file di definizione di classi o costanti. È consigliabile utilizzare require_once/include_once per caricare i file.

4. Attenzione al rilascio delle risorse di connessione nel pattern Singleton

  • Poiché Workerman non rilascia oggetti globali e membri statici delle classi dopo ogni richiesta, nelle istanze Singleton come il database, spesso viene mantenuta l'istanza del database (che contiene una connessione socket al database) tra i membri statici del database, consentendo a Workerman di riutilizzare questa connessione socket per tutta la durata del processo. È importante notare che quando il server del database rileva che una connessione è inattiva per un certo periodo di tempo, potrebbe chiudere attivamente la connessione socket. In questo caso, l'utilizzo successivo di quest'istanza di database genererà errori (il messaggio di errore è simile a "mysql gone away"). Workerman offre una classe per il database con una funzionalità di riconnessione, che gli sviluppatori possono utilizzare direttamente.

5. Attenzione a non utilizzare le istruzioni exit o die

  • Workerman gira in modalità da riga di comando di PHP; quando viene chiamata un'istruzione exit o die, il processo corrente termina. Anche se un nuovo processo figlio verrà immediatamente ricreato per continuare il servizio, ciò potrebbe comunque influenzare l'attività.

6. Le modifiche al codice richiedono un riavvio del servizio per avere effetto

Poiché Workerman rimane in memoria, le definizioni dei classi e delle funzioni vengono caricate una sola volta e non verranno lette nuovamente dal disco. Pertanto, dopo ogni modifica del codice, è necessario riavviare il servizio affinché le modifiche abbiano effetto.

II. Concetti di base da comprendere

1. Protocollo TCP di livello di trasporto

Il TCP è un protocollo di trasporto orientato alla connessione, affidabile e basato su IP. Una caratteristica importante del protocollo TCP è che è basato su flussi di dati; le richieste del client vengono inviate continuamente al server, e i dati ricevuti dal server potrebbero non essere una richiesta completa, ma potrebbero anche essere più richieste concatenate. Questo richiede di distinguere i confini di ciascuna richiesta all'interno di questo costante flusso di dati. Il protocollo di livello applicativo definisce un insieme di regole per i confini delle richieste, evitando confusione nei dati delle richieste.

2. Protocollo di livello applicativo

Il protocollo di livello applicativo (application layer protocol) definisce come i processi delle applicazioni in sistemi diversi (client, server) si scambiano i messaggi. Esempi di protocolli di livello applicativo includono HTTP e WebSocket. Ad esempio, un semplice protocollo di livello applicativo può essere rappresentato come {"module":"user","action":"getInfo","uid":456}\n". Questo protocollo segna la fine della richiesta con "\n" (si noti che "\n" rappresenta il carattere di invio), e il corpo del messaggio è una stringa.

3. Connessioni brevi

Una connessione breve si riferisce a stabilire una connessione quando ci sono scambi di dati, e la connessione viene interrotta una volta completato l'invio dei dati. Ogni connessione completa solo una singola operazione inviata. I servizi HTTP di siti web generalmente utilizzano connessioni brevi.

Per lo sviluppo di applicazioni con connessioni brevi, puoi fare riferimento al capitolo sul flusso di sviluppo di base.

4. Connessioni lunghe

Le connessioni lunghe consentono l'invio continuo di più pacchetti di dati su una singola connessione.

Nota: un'applicazione con connessione lunga deve includere heartbeat, altrimenti la connessione potrebbe essere interrotta dai firewall dei nodi di routing a causa di inattività prolungata.

Le connessioni lunghe sono utilizzate principalmente in situazioni di comunicazione punto a punto con operazioni frequenti. Ogni connessione TCP richiede tre fasi di handshake, il che richiede tempo; se ogni operazione comporta prima una connessione e poi un'operazione, la velocità di elaborazione diminuirà notevolmente. Pertanto, le connessioni lunghe non si interrompono dopo ogni operazione, permettendo di inviare direttamente i pacchetti di dati per elaborazioni successive senza stabilire una nuova connessione TCP. Ad esempio, le connessioni al database utilizzano connessioni lunghe; l'uso di connessioni brevi per comunicazioni frequenti causerebbe errori di socket e il frequente stabilimento di socket rappresenterebbe uno spreco di risorse.

Quando è necessario inviare attivamente dati al client, ad esempio in applicazioni di chat, giochi in tempo reale, notifiche mobili, è necessario utilizzare connessioni lunghe.
Per lo sviluppo di applicazioni con connessioni lunghe, puoi fare riferimento al flusso di sviluppo di Gateway/Worker.

5. Riavvio fluido

Il processo di riavvio normale prevede di fermare tutti i processi e poi creare nuovi processi di servizio. Durante questo processo, ci sarà un breve intervallo in cui nessun processo fornisce servizi, il che porterà a una temporanea indisponibilità del servizio, causando, in particolare in situazioni di alta concorrenza, il fallimento delle richieste.

Il riavvio fluido, invece, non ferma tutti i processi contemporaneamente, ma li interrompe uno alla volta. Dopo aver interrotto un processo, viene immediatamente creato un nuovo processo per sostituirlo, fino a quando tutti i processi vecchi non sono stati sostituiti.

Per effettuare un riavvio fluido in Workerman, puoi utilizzare il comando php your_file.php reload, che consente di aggiornare l'applicazione senza compromettere la qualità del servizio.

Nota: solo i file caricati all'interno dei callback on{...} saranno automaticamente aggiornati dopo un riavvio fluido. I file caricati direttamente nello script di avvio o il codice hard-coded non verranno aggiornati automaticamente quando si esegue il reload.

III. Differenza tra processo principale e processo figlio

È necessario prestare attenzione a se il codice viene eseguito nel processo principale o nel processo figlio; in generale, il codice eseguito prima della chiamata a Worker::runAll(); viene eseguito nel processo principale, mentre il codice eseguito nei callback onXXX appartiene ai processi figli. Tieni presente che il codice scritto dopo Worker::runAll(); non verrà mai eseguito.

Ad esempio, il codice seguente:

use Workerman\Worker;
use Workerman\Connection\TcpConnection;
require_once __DIR__ . '/vendor/autoload.php';

// Eseguito nel processo principale
$tcp_worker = new Worker("tcp://0.0.0.0:2347");
// L'assegnazione avviene nel processo principale
$tcp_worker->onMessage = function(TcpConnection $connection, $data)
{
    // Questa parte viene eseguita nel processo figlio
    $connection->send('hello ' . $data);
};

Worker::runAll();

Nota: Non inizializzare connessioni a database, memcache, redis e simili nel processo principale, poiché le connessioni inizializzate nel processo principale possono essere automaticamente ereditate dai processi figli (soprattutto quando si utilizza il Singleton); tutti i processi detengono la stessa connessione, e i dati restituiti dal server attraverso questa connessione sono accessibili da più processi, il che può portare a incoerenze nei dati. Allo stesso modo, se uno dei processi chiude una connessione (ad esempio, nel caso in cui il processo principale esca in modalità daemon, causando la chiusura della connessione), tutte le connessioni dei processi figli verranno anch'esse chiuse, generando errori imprevisti, come errori "mysql gone away".

È consigliabile inizializzare le risorse di connessione all'interno di onWorkerStart.