server di chat in php con i socket 3


Del server di chat è stata realizzata una nuova versione

Oggi vediamo come una chat in php usando le socket di basso livello, naturalmente usando il php da terminale.
Per comodità di utilizzo e di schematizzazione del codice ho creato una classe.

il codice è il seguente:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
<?php
/************************************************************
copyright claudio cardinale 2004-2011! tutti i diritti reservati
contatti :  cardi@thecsea.it(o http://www.thecsea.it)
versione :  0.10.0
data :      16/02/2011 16:51
*************************************************************/
class Server{
    //proprieta private
    private $indirizzo = "";
    private $porta = "";
    private $sock = "";
    private $clienti = array();
    private $i = 0;
 
    //costruttore
    public function __construct($indirizzo, $porta){
        $this->indirizzo = $indirizzo;
        $this->porta = $porta;
 
        print "avvio server in corso...\n";
 
        set_time_limit(0);
        ob_implicit_flush();
        $this->sock = @socket_create(AF_INET, SOCK_STREAM, SOL_TCP) or die("socket_create() fallito: motivo: " . socket_strerror($this->sock) . "\n");
        $ret = @socket_bind($this->sock, $this->indirizzo, $this->porta) or die("socket_bind() fallito: motivo: " . socket_strerror($ret) . "\n");
        $ret = @socket_listen($this->sock, 5) or die("socket_listen() fallito: motivo: " . socket_strerror($ret) . "\n");
        $ret = @socket_set_nonblock($this->sock) or die("socket_set_nonblock() fallito: motivo: " . socket_strerror($ret) . "\n");
        $this->ciclo();
    }
 
    //distruttore
    public function __destruct(){
        socket_close($this->sock);
        foreach($this->clienti as $key=>$value)
            $this->disconnetti($key);
 
    }
 
    //metodi privati
 
    //ciclo principale
    private function ciclo(){
        print "server avviato...\n";
        for(;;){
            $this->aggiungi();
            $this->chat();
            usleep(5);
        }
    }
 
    //aggiunta clienti
    private function aggiungi(){
        if($buf = @socket_accept($this->sock)){
            $this->clienti[$this->i] = $buf;
             
            $msg = "\nBenvenuto in chat...\nscrivere quit per uscire...\n";
            if($this->scrivi($buf, $msg)===false){
                if($this->disconnetti($this->i))
                    print "errore con il cliente ".($this->i+1)." (in scrittura) -> disconnesso correttamente\n";
                else
                    print "errore con il cliente ".($this->i+1)." (in scrittura) -> impossibile disconnetterlo\n";           
                return false;
            }
 
            $this->i++;
            print "cliente ".$this->i." aggiunto\n";
            return true;
        }
    }
     
    //richiamo metodi per chat
    private function chat(){
        //controllo se ci sono clienti
        if(!$this->clienti)
            return false;
 
        //controllo cambiamenti
        $read = $this->clienti;
        $var = null;
        $var2 = null;
        socket_select($read,$var,$var2,0);
 
        //lettura clienti
        foreach($read as $key1=>$value1){
            $ret = $this->leggi($value1);
 
            //key corrispondente nell'array clienti
            $key_o = array_search($value1, $this->clienti);
 
            //uscita
            if(trim($ret)=="quit")
                if($this->disconnetti($key_o))
                    print "cliente ".($key_o+1)." disconesso correttamente\n";
                else
                    print "errore con cliente ".($key_o+1)." (in disconnessione)\n";
 
            //stampa sui clienti
            else if($ret)
                foreach($this->clienti as $key2=>$value2)
                    if($key_o!=$key2 && $this->scrivi($value2, $ret)===false)
                        if($this->disconnetti($key2))
                            print "errore con il cliente ".($key2+1)." (in scrittura) -> disconnesso correttamente\n";
                        else
                            print "errore con il cliente ".($key2+1)." (in scrittura) -> impossibile disconnetterlo\n";
        }
        return true;
    }
 
    //lettura
    private function leggi($sock){
        return @socket_read($sock, 2048, PHP_NORMAL_READ);
    }
 
    //scrittura
    private function scrivi($sock, $mex){
        return @socket_write($sock, $mex, strlen ($mex));
    }
 
    //disconnessione cliente
    private function disconnetti($chiave){
        if(@socket_close($this->clienti[$chiave])===false)
            return false;
        unset($this->clienti[$chiave]);
        return true;
    }
}
 
$server1 = new Server("127.0.0.1","9000");
$server1 = 0;
?>

Per chi vuole subito provarlo basta cambiare i parametri di new Server(“127.0.0.1″,”9000”) il primo indica l’indirizzo IP della macchina il secondo la porta.

Per avviarlo basta fare php serve.php oppure se lo si vuole avviare in background: nohup php server.php /dev/null 2>&1 &

Per connettersi come client basta eseguire un normale telnet alla porta specificata, esempio: telnet 127.0.0.1 900

N.B. Se si avvia il server usando come indirizzo IP l’indirizzo locale della macchina (es: 192.168.0.2) bisogna anche da telnet connettersi su quell’indirizzo e non su localhost(127.0.0.1) anche se ci si trova in locale

Naturalmente in seguito si può facilmente creare un client e fare varie migliorie come ad esempio una gestione degli utenti.

Dopo questa breve introduzione passiamo ad una spiegazione del codice.

Costruttore:
Il costruttore non fa altro che creare una socket tcp sulla porta specificata:

1
2
$this->sock = @socket_create(AF_INET, SOCK_STREAM, SOL_TCP) or die("socket_create() fallito: motivo: " . socket_strerror($this->sock) . "\n");
$ret = @socket_bind($this->sock, $this->indirizzo, $this->porta) or die("socket_bind() fallito: motivo: " . socket_strerror($ret) . "\n");

in seguito si mette in ascolto permettendo di avere una coda massima di 5 client, togliendo il blocco in modo da poter andare avanti nelle azioni:

1
2
$ret = @socket_listen($this->sock, 5) or die("socket_listen() fallito: motivo: " . socket_strerror($ret) . "\n");
$ret = @socket_set_nonblock($this->sock) or die("socket_set_nonblock() fallito: motivo: " . socket_strerror($ret) . "\n");

infine richiama il metodo ciclo che, come vedremo, contiene al suo interno un ciclo infinito

Ciclo:
non fa altro che creare un ciclo infinito che ogni volta richiama i metodi per vedere se aggiungere clienti in chat e per vedere se ci sono nuovi messaggi in attesa di essere recapitati, infine fa una piccola pausa di 5 millisecondi per non sovraccaricare troppo il processore.

Aggiungi:
Verifica se un client ha tentato di connettersi (con socket_accept($this->sock)) se è così lo aggiunge alla lista dei clients, in seguito prova a vedere se la connessione funziona correttamente provando a scrivere un messaggio al client, se risultano esserci problemi lo disconnette completamente eliminandolo anche dalla lista.

Chat:
Grazie alla socket_select verifica se dei clients hanno inviato qualcosa:

1
2
3
4
$read = $this->clienti;
$var = null;
$var2 = null;
socket_select($read,$var,$var2,0);

$read in questo caso contiene un vettore contenente i resource delle socket di quei client che hanno scritto qualcosa.

In seguito viene letto tutto il vettore $read leggendo gli input dal client e cercando ogni volta la chiave corrispondente nel vettore originale (clienti):

1
2
$ret = $this->leggi($value1);
$key_o = array_search($value1, $this->clienti);

Poi viene controllato se l’utente desidera disconnettersi:

1
2
3
4
5
if(trim($ret)=="quit")
    if($this->disconnetti($key_o))
        print "cliente ".($key_o+1)." disconesso correttamente\n";
    else
        print "errore con cliente ".($key_o+1)." (in disconnessione)\n";

ed infine viene scritto il messaggio in tutti gli altri client, se non rispondono vengono disconnessi:

1
2
3
4
5
6
7
else if($ret)
    foreach($this->clienti as $key2=>$value2)
        if($key_o!=$key2 && $this->scrivi($value2, $ret)===false)
            if($this->disconnetti($key2))
                print "errore con il cliente ".($key2+1)." (in scrittura) -> disconnesso correttamente\n";
            else
                print "errore con il cliente ".($key2+1)." (in scrittura) -> impossibile disconnetterlo\n";

Leggi e scrivi:
sono dei metodi che non fanno altro che richiamare le corrispondenti funzioni delle scoket.

Disconnetti:
non fa altro che chiudere completamente la connessione con il client ed eliminarlo dalla lista

N.B. Per comodità di implementazione ogni messaggio può essere lungo al massimo 2048 caratteri

CC BY-SA 4.0 server di chat in php con i socket by cardinale claudio is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.


Lascia un commento

3 commenti su “server di chat in php con i socket