Io non ho ben capito se la progettazione che hai fatto lato server sia corretta o meno (hai postato il client, che mi sembra corretto, ma non si sa nulla del server). Ti spiego, a grandi linee, come dovrebbe funzionare lato server.
Ciascun client collegato al server è servito da un thread. Quindi ti serve una classe che modelli il thread. E' il thread a possedere la socket generata dalla connessione e i relativi ObjectInput/OutputStreams.
La classe del thread deve esporre un metodo per permettere al server di poter inviare messaggi al client che essa gestisce e deve avere un riferimento al server per potergli comuinicare informazioni di servizio.
Il server manterrà in una struttura dati apposita tutte le istanze dei thread di gestione dei client (la HashMap va benissimo!).
Quando un client richiede l'invio di un messaggio "privato" (il tipo di messaggio/richiesta dovrebbe essere parte del protocollo di comunicazione) il server deve solo andare a prelevare il thread che gestisce il destinatario e spedirgli il messaggio usando il metodo esposto a tale scopo (quello di cui parlavo prima).
Fine.
Vediamo un po' di pseudo-codice:
Il thread che gestisce il singolo client (lato server)
codice:
public class ClientThread extends Thread {
private Server theServer;
private Socket socket;
private ObjectInputStream ois;
private ObjectOutputStream oos;
private String nome;
public ClientThread(Server theServer, Socket socket) {
this.theServer = theServer;
this.socket = socket;
nome = ""; // Il nome mi arriverà tramite protocollo
}
@Override
public void run() {
try {
oos = new ObjectOutputStream( socket.getOutputStream() );
ois = new ObjectInputStream( socket.getInputStream() );
while( !isInterrupted() ) {
// Gestisco i messaggi in arrivo dal client
Message m = (Message) ois.readObject();
// Li elaboro
dispatchMessage( m );
}
} catch (...) { ... }
}
// Questo metodo verrà invocato all'interno di "dispatchMessage",
// quando il client mi comunica il suo nome utente
private void setNome(String nome) {
this.nome = nome;
// Comunico il mio nome al server, così può rintracciarmi
theServer.setClientName(this, nome);
}
public void sendMessage(Message m) throws Exception {
// Invio un messaggio al client
oos.writeObject();
oos.flush();
}
}
Vediamo il server (solitamente, anch'esso è un Thread)
codice:
public class Server extends Thread {
private HashMap<String,ClientThread> myClients = new HashMap<String,ClientThread>();
private ServerSocket server;
...
@Override
public void run() {
try {
server = new ServerSocket( porta );
while( !isInterrupted() ) {
Socket s = server.accept();
// E' arrivata una connessione... ne delego la gestione ad un nuovo thread
(new ClientThread(this, s)).start();
}
} catch ( ... ) { ... }
}
public void setClientName(ClientThread ct, String nome) {
// Il client mi ha comunicato il suo nome... posso mapparlo!
myClients.put(nome, ct);
}
public void sendPrivateMessage(Message m, String destinatario) {
// Quando arriva un messaggio privato, il thread che gestisce il client invocherà questo metodo
// Recupero il thread che gestisce il destinatario
ClientThread clDest = myClients.get( destinatario );
// Gli invio il messaggio privato
clDest.sendMessage( m );
}
}
Questa è l'ossatura di base della gestione. L'ordine con cui ho aperto gli streams è coerente con l'ordine in cui tu apri gli stream sul client (mi sono basato sul codice che hai postato).
Ciao.