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:

<?php
/************************************************************
copyright claudio cardinale 2004-2011! tutti i diritti reservati
contatti : 	[email protected](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:

$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:

$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:

$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):

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

Poi viene controllato se l’utente desidera disconnettersi:

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:

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