Introduzione ai socket
I libri sono fondamentali, ma non si impara ad andare in bicicletta leggendo libri e nessuno può diventare un pilota ascoltando i resoconti dei viaggi aerei fatti da altri. Certe cose si imparano, invece, facendo pratica, sbagliando, correggendosi e, cosa più importante, riuscendo ad ottenere il risultato voluto. Perché quando si sente che si è vicini alla meta aumenta la fiducia nelle proprie capacità e ci si convince che ogni obiettivo è raggiungibile.
Programmare il computer non è differente: potrebbe sembrare difficile, astruso o forse anche noioso, ma non è così. In realtà , la programmazione è estremamente gratificante, perché si esercita il controllo completo sul computer. E' possibile far svolgere al computer tutte le cose nel modo esattamente desiderato senza accontentarsi di soluzioni approssimative. E se si è bravi nella programmazione questo può essere l'inizio per una nuova attività .
In questi brevi appunti vogliamo illustrare le basi elementari per imparare la programmazione dei socket in php. La maggior parte delle funzioni e dei parametri usati hanno nomi simili ai corrispondenti usati nella programmazione in linguaggio C. Tuttavia a differenza di C, i programmi per socket scritti in php funzionano allo stesso modo su qualunque sistema operativo su cui è installato php, e quindi il codice non ha bisogno di modifiche specifiche alla piattaforma usata.
In estrema sintesi i socket sono delle strutture fondamentali che stanno dietro qualsiasi tipo di comunicazione di rete fatto dal computer. Ad esempio, quando si digita www.google.it nel browser web, si apre un socket e ci si connette a google.it per recuperare la pagina che si vuol vedere. La stessa cosa avviene con qualsiasi client di chat come gtalk o skype. Qualsiasi comunicazione di rete passa attraverso un socket.
Diamo per scontato che il lettore conosca già un po' di php ed anche come si eseguono gli script PHP a linea di comando da un terminale. Gli script PHP sono normalmente eseguiti all'interno del browser puntando alla directory usata dal server apache come /var/www
oppure ~/public_html
. Tuttavia questi programmi PHP possono essere eseguiti oltre che dal browser anche a riga di comando e da qualsiasi directory.
Schema grafico di una connessione TCP
Lo schema seguente rappresenta le tipiche operazioni che Server e Client compiono per instaurare una connessione ed una comunicazione con il classico protocollo TCP, che prevede, appunto, prima l'attivazione della connessione e poi il dialogo.
Creazione di un socket
La prima cosa da fare è la creazione di un socket, che si esegue con la funzione socket_create
.
$sock = socket_create(AF_INET, SOCK_STREAM, 0);
La funzione socket_create
crea un socket e restituisce un descrittore di socket che può essere usato per eseguire altri comandi di rete.
Il codice precedente creerà un socket con le seguenti proprietà :
Famiglia di indirizzi: AF_INET (in questo caso è IP versione 4, per la versione 6 si usa AF_INET6)
Tipo: SOCK_STREAM (questo significa connessione orientata al protocollo TCP)
Protocollo: 0 (oppure IPPROTO_IP, questo è il protocollo IP)
Nota:
Oltre il tipo di socket SOCK_STREAM ce n'è un altro tipo chiamato SOCK_DGRAM che indica il protocollo UDP ed è un socket senza connessione. In questa prima sezione di appunti prenderemo in considerazione solo i socket SOCK_STREAM orientati al protocollo TCP, nel seguito lavoreremo anche con il protocollo UDP.
Gestione degli errori:
Se qualcosa non riesce durante l'esecuzione delle funzioni socket è possibile recuperare le informazioni sull'errore tramite le funzioni:
socket_last_error
socket_strerror
come mostrato dal seguente spezzone di codice.
if (!($sock = socket_create(AF_INET, SOCK_STREAM, 0))) { $errorcode = socket_last_error(); $errormsg = socket_strerror($errorcode); die("Creazione del socket non riuscita: [$errorcode] $errormsg \n"); } echo "Socket creato correttamente.";
La prima funzione restituisce il codice numerico dell'ultimo errore intercettato, mentre la seconda funzione, socket_strerror
, restituisce la stringa messaggio associata ad un determinato codice di errore.
Connessione al server
Dopo aver creato correttamente il socket cercheremo di connetterci concretamente ad un qualche server, per esempio www.google.it, usando proprio il socket appena creato.
Ci dobbiamo connettere ad un server remoto su un determinato numero di porta, pertanto ci servono 2 informazioni: l'indirizzo IP ed il numero di porta. Quindi ci interessa conoscere l'indirizzo IP del server remoto a cui vogliamo connetterci. Per adesso usiamo come esempio l'indirizzo IP di google, tra poco vedremo come ottenere l'indirizzo IP dato il nome del dominio.
L'ultima cosa che ci serve è la funzione socket_connect
, a cui per connettersi serve un identificatore di socket ($sock
) ed i parametri reali di un socket (indirizzo IP + numero di porta). Ecco il codice d'esempio.
Esecuzione dello script
Dopo aver salvato il codice precedente nella cartella public_html
dandogli il nome socket00.php
, lo possiamo mandare in esecuzione direttamente dalla riga di comando di un terminale:
$ php ~/public_html/socket00.php
ottenendo in risposta:
Socket creato correttamente. Connessione attivata.
Anche se nel seguito, per praticità , useremo spesso la modalità a linea di comando, un risultato analogo lo si ottiene avviando il browser all'indirizzo: 127.0.0.1/~user/socket00.php
, dove user è il nome dell'utente che ha effettuato il login. La figura illustra il risultato:
Abbiamo quindi avuto la conferma della creazione di un socket e dell'attivazione della connessione con quell'indirizzo specifico. Provando a collegarsi ad una porta diversa dalla porta 80 non si dovrebbe essere in grado di connettersi, il che indica che la porta non è aperta per la connessione. Questa logica può essere utilizzata per la costruzione di uno scanner.
Nel prossimo paragrafo vedremo come inviare dei dati al server remoto.
Nota:
Due parole sul concetto di "connessione" applicato al tipo di socket SOCK_STREAM / TCP. Per connessione si intende un "flusso" affidabile di dati, in modo tale che, se ci sono più di tali flussi, la comunicazione di ciascuno non deve interferire con quella degli altri. Si possono immaginare delle condotte idriche parallele i cui flussi sono completamente separati gli uni dagli altri.
Altri socket, come UDP, ICMP, ARP, non hanno il concetto di connessione. Queste sono comunicazioni non basate sulla connessione, il che significa che si continua a ricevere e inviare pacchetti di dati da tutti e verso tutti.
Invio di dati
La funzione socket_send
è quello che serve per inviare dei dati. Ha bisogno del descrittore di socket, dei dati da inviare e la loro dimensione.
Ecco un esempio molto semplice per inviare alcuni dati all'indirizzo IP di google.it:
if (!($sock = socket_create(AF_INET, SOCK_STREAM, 0))) { $errorcode = socket_last_error(); $errormsg = socket_strerror($errorcode); die("Creazione del socket non riuscita: [$errorcode] $errormsg \n"); } echo "Socket creato correttamente. \n"; // Connessione del socket al server remoto if (!socket_connect($sock , '173.194.112.51' , 80)) { $errorcode = socket_last_error(); $errormsg = socket_strerror($errorcode); die("Connessione non riuscita: [$errorcode] $errormsg \n"); } echo "Connessione attivata. \n"; $message = "GET / HTTP/1.0\r\n\r\n"; // Invio di un messaggio al server if ( ! socket_send ( $sock , $message , strlen($message) , 0)) { $errorcode = socket_last_error(); $errormsg = socket_strerror($errorcode); die("Invio dati non riuscito: [$errorcode] $errormsg \n"); } echo "Invio messaggio riuscito. \n";
Nell'esempio precedente ci siamo prima connessi ad un indirizzo IP e poi gli abbiamo inviato la stringa messaggio:
"GET / HTTP/1.0\r\n\r\n
"
che è un tipico comando http usato per prelevare la pagina principale di un sito web. Il server, infatti, riceve la richiesta ed analizza la stringa:
GET - Questo comando dice al server di individuare e leggere il file, di seguito indicato, e restituirlo al client.
/ - L'analisi del path dice al server il percorso, il nome del file ed il formato del documento (HTML). In questo caso, essendo indicata solo la radice del path, si intende il file iniziale predefinito. Altrimenti si potrebbe specificare qualcosa del tipo: /path/page.html
.
HTTP/1.0 - Specifica quale protocollo di comunicazione usare per colloquiare con il server.
\r\n\r\n - Sono i 4 byte di chiusura della stringa messaggio: CR+LF+CR+LF. Equivale, in pratica, ad un doppio INVIO
.
Nota:
Quando si inviano dei dati ad un socket si effettua fondamentalmente una scrittura di dati sul socket. E' l'equivalente della scrittura di dati su un file. Quindi si può anche utilizzare la funzione socket_write
per inviare dei dati ad un socket, cosa che faremo nel seguito di questi appunti.
Ricezione dati
Dopo aver inviato alcuni dati al server vediamo come sia possibile ottenere una risposta.
La funzione socket_recv
è quella che ci serve per ottenere la risposta del server. Nell'esempio seguente invieremo lo stesso messaggio usato in precedenza e vedremo il tipo di risposta emesso dal server.
Esecuzione dello script
Dopo aver salvato il codice precedente nella cartella public_html
dandogli il nome socket01.php
, lo possiamo mandare in esecuzione direttamente dalla riga di comando di un terminale:
$ php ~/public_html/socket01.php
ottenendo in risposta:
Socket creato correttamente. Connessione attivata. Invio messaggio riuscito. HTTP/1.0 302 Found Cache-Control: private Content-Type: text/html; charset=UTF-8 Location: http://www.google.it/?gfe_rd=cr&ei=0FfvVJzHN8KG8QeNm4GgDw Content-Length: 258 Date: Thu, 26 Feb 2015 17:28:48 GMT Server: GFE/2.0 Alternate-Protocol: 80:quic,p=0.08 <HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8"> <TITLE>302 Moved</TITLE></HEAD><BODY> <H1>302 Moved</H1> The document has moved <A HREF="http://www.google.it/?gfe_rd=cr&ei=0FfvVJzHN8KG8QeNm4GgDw">here</A>. </BODY></HTML>
Quanto è stato inviato in risposta dal server sembra qualcosa che assomiglia ad HTML, ed è proprio HTML. Google.it ha risposto semplicemente con il contenuto della pagina che abbiamo richiesto.
Chiusura del socket
Adesso che abbiamo ricevuto la risposta dal server è arrivato il momento di chiudere il socket e la funzione socket_close
è quello che ci serve.
socket_close($sock);
Punto della situazione
In questa sezione abbiamo imparato come:
1 - Creare un socket
2 - Connettersi ad un server remoto
3 - Inviare dei dati
4 - Ricevere una risposta
5 - Chiudere un socket
E' bene sapere che un browser web fa la stessa cosa quando dialoga con www.google.it. Questo tipo di attività rappresenta un socket CLIENT. Un client è un sistema che si connette a un sistema remoto per recuperare dei dati.
Un altro di attività fatta da un socket è chiamata SERVER. Un server è un sistema che utilizza i socket per ricevere connessioni in entrata e fornire loro dei dati. E' esattamente il complementare del client. Quindi www.google.it è un server e il browser Web è un client. Oppure, più tecnicamente, www.google.it è un server HTTP ed il browser web è un client HTTP.
Nota finale:
Non perdersi d'animo se apparentemente non dovesse andare a buon fine l'esecuzione a linea di comando dei 2 esempi proposti. Nel 99% dei casi ciò può dipendere dall'indirizzo IP impostato per google.it. Nella sezione successiva impareremo come ottenre con sicurezza questa informazione. Per il momento, in ambiente Linux, potete dare il seguente comando:
$ nslookup www.google.it
ottenendo in risposta qualcosa del tipo:
Non-authoritative answer: Name: www.google.it Address: 173.194.112.55 Name: www.google.it Address: 173.194.112.63 Name: www.google.it Address: 173.194.112.56 Name: www.google.it Address: 173.194.112.47
oppure con il comando:
$ ping www.google.it
ottenendo in risposta qualcosa del tipo:
PING www.google.it (173.194.112.55) 56(84) bytes of data. 64 bytes from mrs04s09-in-f3.1e100.net (173.194.112.55): icmp_req=1 ttl=54 time=53.5 ms .......
In questo caso occorre, però, bloccare l'output con la combinazione di tasti Ctrl-C.
In entrambi i casi, come si vede, è possibile recuperare un indirizzo IP valido per un determinato nome di dominio.
Abbastanza di frequente i comandi di rete utilizzati per Linux, in questi esempi, trovano un equivalente anche in ambito Windows, attivando un terminale con l'avvio di cmd.exe, ma questa corrispondenza esula dagli scopi di questi appunti.
La saggezza non viene dall'intelligenza ma è suggerita dal cuore.
Bibbia