Un semplice chat server

Da Head First Java O'Reilly, capitolo 15. Networking e threads

Estendiamo il nostro esempio di una applicazione client/server creando un semplice sistema di chat. Il passo in avanti é che usiamo il multithreading (wow).

In questo post vediamo la parte server del sistema. Si tratta di un server semplificato al massimo, quello che fa é creare un server socket sulla porta 4242 e restare appeso in attesa di connessione dal lato client. Ogni volta che un client si connette creiamo un oggetto ChatClientHandler e facciamo partire un nuovo thread che gestisce la connessione.

Nel libro il client handler é una inner class del server. A me le classi interne mi innervosiscono, mi pare rendano il codice meno leggibile. In questo caso, poi, la connessione tra le due classi non é particolarmente forte, ho preferito quindi utilizzare un meccanismo di call back: il server passa se stesso alla classe interna in modo che questa possa richiamare il suo metodo tellEveryone().

Il client handler implementa l'interfaccia Runnable, in quanto vogliamo eseguire i suoi oggetti in differenti thread. Il costruttore ha come parametri il ChatServer, in modo da poter fare la callback, e il socket che usiamo per la connessione al client. Dal socket estraiamo l'input stream e con esso, via InputStreamReader, costruiamo il BufferedReader che, variabile di istanza, utilizziamo per leggere le comunicazioni che ci arrivano dal client.

Il metodo run() resta appeso in lettura sul BufferedReader. Come arriva una stringa facciamo la callback al chat server invocando il metodo tellEveryone() che manda il messaggio a tutti i sottoscrittori attivi della chat.

Questo il codice:

package Chap15;

import java.io.*;
import java.net.Socket;

public class ChatClientHandler implements Runnable {

private ChatServer server;
private BufferedReader reader;

public ChatClientHandler(ChatServer server, Socket socket) {
this.server = server;
try {
InputStream is = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
reader = new BufferedReader(isr);
}
catch (IOException ioe) {
ioe.printStackTrace();
}
}

public void run() {
String message;
try {
while ((message = reader.readLine()) != null) {
System.out.println("Got message: " + message);
server.tellEveryone(message);
}
}
catch (IOException ioe) {
ioe.printStackTrace();
}
}
}

Il chat server implementa il main(), che semplicemente crea un oggetto ChatServer. Nel costruttore il loop infinito crea un nuovo thread per ogni nuovo cliente che si connette alla chat e aggiunge un oggetto PrintWriter, derivato dal output stream del socket di connessione al client, alla lista mantenuta come variabile di istanza nell'oggetto.

Il metodo tellEveryone() viene chiamato dal gestore dei client e fa un loop su tutti gli oggetti PrintWriter nella lista, su ognuno dei quali chiama la println() per notificare il messaggio arrivato.

package Chap15;

import java.io.*;
import java.net.*;
import java.util.*;

public class ChatServer {
private ArrayList<PrintWriter> streams = new ArrayList<PrintWriter>();

public ChatServer() {
try {
ServerSocket ss = new ServerSocket(4242);
System.out.println("Chat server started on port 4242");

while(true) {
Socket client = ss.accept();
streams.add(new PrintWriter(client.getOutputStream()));

Thread t = new Thread(new ChatClientHandler(this, client));
t.start();
}
}
catch(Exception ex) {
ex.printStackTrace();
}
}

/**
* package visibility, to be used by the client handler
*/
void tellEveryone(String message) {
Iterator<PrintWriter> it = streams.iterator();
while(it.hasNext()) {
try {
PrintWriter pw = it.next();
pw.println(message);
pw.flush();
} catch(Exception ex) {
ex.printStackTrace();
}
}
}

public static void main(String[] args) {
new ChatServer();
}
}

Da notare che il codice é veramente minimale e richiede una cooperazione assidua da parte dell'utilizzatore. Occorre lanciare prima il server, poi i client; un client che non trova il server ad attenderlo sulla porta specificata, infatti, non sa che fare. Allo stesso modo, per terminare l'esecuzione conviene prima chiudere il server e poi i client, altrimenti al server verrà recapitata un'eccezione ogni volta che un client chiude, dato che la readLine() al reader nel client handler verrà notificata una eccezione SocketException dovuta al reset della connesione sottostante.

Nessun commento:

Posta un commento