# Nach Verbindung gleich ObjectStream empfangen



## Bolty (13. Dez 2013)

Hallo,

ich habe mich zuletzt recht intensiv mit java.net.* und der Server-Client-Entwicklung beschäftigt.

Als kleinen Test für mich selbst habe ich dann ein Client-Server-basiertes Chatprogramm für mehrere Clients für die Kommandozeile entwickelt, welches auch gut funktioniert hat, aber dennoch sehr simpel war.

Jetzt möchte ich das ganze verbessern, indem ich für den Server und den Client eine graphische Oberfläche entwickle und der User sich zudem mit einem Nickname und weiteren Daten anmelden kann usw.


Zu diesem Zweck möchte ich, dass der Client direkt nach dem verbinden über einen ObjectStream die von mir erstellte Klasse ClientInfo überträgt (werde ich jetzt hier nicht posten, enthält nur ein paar Attribute sowie getter und setter).

Nun bin ich mir nicht sicher, wie ich das realisieren soll; im Grunde wird jeder Client der sich verbindet in einem Thread gehandlet.

Derzeit habe ich folgende Klassen:
serverFrame = grafische Oberfläche, beinhaltet keinen relevanten Code
Server = extends Thread, läuft in Endlosschleife um eingehen Verbindungen anzunehmen.
ClientListener = extends Thread, für jede neue Verbindung (jeden neuen Socket) wird diese Klasse neu instanziert. Hier wird auf das eintreffen des ObjectStreams und dann im Anschluss auf eingehende Nachrichten gewartet.

Mein Problem: Wenn sich ein Client verbindet, aber kein ClientInfo im Anschluss sendet, läuft der Thread ewig weiter und wartet auf einen ObjectStream, ohne eine Möglichkeit diesen beenden zu können. Wie kann ich dieses Problem lösen?

Dieses Problem tritt z.B. auf, wenn ich versuche mit dem alten Kommandozeilenprogramm zu verbinden. Die Verbindung klappt, allerdings erhält der Server dann natürlich kein Object.

Hier der run-Methode der Server-Klasse:

```
public synchronized void run()
    {
        try 
        {
            addLogEntry("Starte Server auf Port " + mPort);
            mServerSocket = new ServerSocket(mPort);
            addLogEntry("Server gestartet, Clients können sich verbinden.");
            mServerDispatcher = new ServerDispatcher(mClients);
            /*
             * Endlosschleife um Clientanfragen zu bearbeiten.
             * Wenn ein Client angenommen wurde, wird ein Object (ClientInfo)
             * erwartet. Nach einer gewissen Wartezeit ohne gültiges Objekt
             * soll der Client verworfen werden. Kommt ein gültiges Objekt an, wird
             * dieses in den Clients-Vektor gespeichert.
             * 
             * mClients = Vector, der alle ClientInfos enthält.
             * mMaxUsers = int zum prüfen der maximalen Useranzahl
             */
            while(true)
            {
                try
                {
                    Socket newClient = mServerSocket.accept();
                    if(mClients.size() <= mMaxUsers)
                    {
                        ClientListener newClientInfo = new ClientListener(newClient, mClients);
                        newClientInfo.start();
                        addLogEntry("Neuer User verbunden");
                    }
                }
                catch(Exception e)
                {
                    addLogEntry("User hat versucht sich zu verbinden, ist aber fehlgeschlagen.");
                }
            }
        } 
        catch (IOException ex) 
        {
            Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
        }
   }
```

Und hier die run-Methode der ClientListener-Klasse:


```
public synchronized void run()
    {
        try 
        {
            ObjectInputStream in = new ObjectInputStream(mClient.getInputStream());
            Object o = in.readObject();
            if(o instanceof ClientInfo) 
            {
                mClientInfo = (ClientInfo)o;
                mClientInfo.setConnection(mClient);
                mClients.add((ClientInfo)o);
                in.close();
                
                 try { 
            while (!isInterrupted()) { 
                try { 
                    String message = mSocketReader.readLine(); 
                    if (message == null) 
                        break; 
                    mServerMessageHandler.sendMessage(message); 
                } 
                catch (SocketTimeoutException ste) 
                { 
                    
                } 
            } 
        } catch (IOException ioex) {  
        } 
            }
            else
                mClient.close();
        }
        catch (IOException ex) 
        {
            Logger.getLogger(ClientListener.class.getName()).log(Level.SEVERE, null, ex);
        } 
        catch (ClassNotFoundException ex) 
        {
            Logger.getLogger(ClientListener.class.getName()).log(Level.SEVERE, null, ex);
        }        
    }
```

Dies wäre eigentlich meine Hauptfrage.

Ansonsten hätte ich noch zwei nebensächliche: Dieses Projekt ist bisher das größte, an dem ich bisher gearbeitet habe, und mir gefällt die Art meiner Programmierung nicht bzw. ich weiß nicht wie ich sie besser machen könnte.

Im Grunde ist ja alles von der serverFrame-Klasse abhängig. Dort werden die Vectoren für die Clients initialisiert und noch einige andere Variablen.
Diese muss ich natürlich weitergeben. Allein die Klasse mClients (ein Vector, der alle ClientInfos hält) wird vom ServerFrame an die Klasse Server weitergegeben, von da an die Klasse ClientListener usw. So kommt es dazu dass ich z.T. sehr lange Konstruktoren für die anderen Klassen habe. Das sieht nicht gut aus und ist auch nicht übersichtlich. Gibt es da bessere Varianten?

Weiterhin würde ich gerne wissen, wie man sicher mit Passwörtern im Sinne dieser Client-Server-Geschichte arbeiten kann.

Ich dachte mir das so: Client schickt password - Server prüft Passwort - Server sendet richtig/falsch.
Ohne dass sowas eventuell mitgesnifft werden kann (quasi Verschlüsselung usw.).

Sorry für die Wall-of-Text und danke für eure Hilfe,

Bolty


----------



## Rock45 (17. Dez 2013)

Hey Bolty.

Es gibt eine Methode der Klasse Socket die _public void setSoTimeout(int timeout)_ heisst. Diese wirft eine _SocketTimeoutException_ nachdem die Zeit abgelaufen ist. Du müsstest deinen Code in der ClientListener geringfügig umbauen.



> Ansonsten hätte ich noch zwei nebensächliche: Dieses Projekt ist bisher das größte, an dem ich bisher gearbeitet habe, und mir gefällt die Art meiner Programmierung nicht bzw. ich weiß nicht wie ich sie besser machen könnte.
> 
> Im Grunde ist ja alles von der serverFrame-Klasse abhängig. Dort werden die Vectoren für die Clients initialisiert und noch einige andere Variablen.
> Diese muss ich natürlich weitergeben. Allein die Klasse mClients (ein Vector, der alle ClientInfos hält) wird vom ServerFrame an die Klasse Server weitergegeben, von da an die Klasse ClientListener usw. So kommt es dazu dass ich z.T. sehr lange Konstruktoren für die anderen Klassen habe. Das sieht nicht gut aus und ist auch nicht übersichtlich. Gibt es da bessere Varianten?



Bessere Varianten gibt es immer. 

Zur Art. Da hilft üben und vor allem auch das Lesen. Schau dir an wie andere deine Probleme lösen und nimm mit was du gut findest und schmeiss weg, was du nicht brauchst. Oracle, Gurus, Professoren. Alle bringen dir bei wie du programmieren kannst. Entwurfsmuster, strukturiertes Programmieren etc. sind Dinge die man mal googeln kann.
Beispielsweise folgendes.
Du schreibst, deine ServerFrame Klasse ist für die grafische Benutzeroberfläche zuständig. Wieso initialisierst du dann da Clients und andere Variablen? Lass die Klasse ihre Arbeit machen, nämlich Dinge anzeigen lassen. Probier alles in kleine Probleme aufzuteilen und Klassen zu schreiben die ein Teilproblem des großen Problems lösen. Logisch, funktional, ganz egal wie.
Des weiteren solltest du dir einen festen Einrückungsstil zulegen und dich danach halten.
Nicht Java Gurus wie ich haben echt Probleme herauszulesen wo welche Klammer zu welchem Bereich gehört.


```
try
        {
            ObjectInputStream in = new ObjectInputStream(mClient.getInputStream());
            Object o = in.readObject();
            if(o instanceof ClientInfo)
            {
                ....               
                 try {
            while (!isInterrupted()) {
                try {
                    String message = mSocketReader.readLine();
                    if (message == null)
                        break;
                    mServerMessageHandler.sendMessage(message);
                }
                catch (SocketTimeoutException ste)
                {
                   
                }
            }
        } catch (IOException ioex) {  
        }
            }
            else
                mClient.close();
        }
```

Gerade den Abschnitt um die While Schleife finde ich sehr tragisch. 
Es ist nur eine persönliche Vorliebe, aber ich finde Code, in dem die geöffnete und geschlossene Klammer in einer eigenen Zeile stehen sehr viel besser zu lesen als 

```
while(---) {
}
```
sowas. Gerade wenn wild geschachtelt wird bei try -catch, wirds manchmal nicht leicht. Da kann ich das Buch "Auf dem Weg zum Java Profi" von Michael Inden empfehlen, oder falls das Geld nicht da ist mal nach Java Style Guide oder Bad Smells googeln.

Für deine Java Chatanwendung wäre es vielleicht ganz praktisch, wenn du dich mal mit RMI auseinandersetzt. Nutzt auch TCP, aber ermöglicht es dir angemeldete Clients wie Objekte zu behandeln, die auf dem Serverrechner laufen. Gehe davon aus, dass ClientInfo eben genau das tut. Dem Server Infos über sich schicken?! Brauchst du mit RMI alles nicht.

TCP solltest du dir aber vorher weiterhin anschauen. Da kann ich dir Rainer Oechsles Buch "Parallele und verteilte Anwendungen in Java" unglaublich empfehlen. Beim Thema TCP solltest du besonders nicht mit der Zeit geizen, um das Thema öffnen und schließen von Ressourcen zu verstehen, gerade wenn es um das Öffnen und Schließen mehrere Ressourcen geht, was bei dir ja der Fall ist. Die Methode _close()_; beispielsweise kann eine IOException werfen, was unglaublich viele Programmierer aber nicht beachten.
Auch das kreuz und quere Öffnen und Schließen von Ressourcen kann zu Problemen führen.
In ClientListener öffnest du in Zeile 10 eine TCP Verbindung, aber wenn du da eine IOException geschmissen bekommst, wer schließt dir dann zwei Zeilen darunter den Strom? (Sorry falls ich den Code falsch verstehe bzw. du daran auch gedacht hast. BTW: Falls ich mit der TCP Verbindung richtig liege, wieso baust du die Verbindung in Zeile 12 ab und vor allem wie baust du davor eine Verbindung auf (Portproblem)?)
In dem Bereich solltest du dich mit dem seit Java 7 implementierten Konzept von "try-with-ressources" einlesen. Unglaublich gute Entwicklung, die dir ein paar try-catch Blöcke weg nimmt. Google in dem Zusammenhang Client Server einfach mal nach TCP Seriell/Parallelserver. Da gibts es schon viel.


Zum Rest vielleicht ein ander Mal was, muss jetzt mal schlafen. Freundin meckert


----------

