# Problem mit der Client Server



## SchachFritz (13. Nov 2005)

Guten Tag. 

Ich bin zur Zeit dabei ein Spiel das über LAN spielbar sein soll zu programmieren. Dazu muss übers Netzwerk ein Objekt, das den aktuellen Spielstand charakterisiert übers Netz verschickt werden. Diese Klasse habe ich schon erstellt. Ich habe jedoch Probleme was die Client-Server Architektur angeht. Ich habe bisher eine methode set geschrieben, die das Objekt vom Client auf den Server überträgt. Auf dem Server wird diese Objekt mit einer globalen Variable referenziert. Auf dem Client gibt es ausserdem eine Methode get, die dieses Objekt vom Server zum Client sendet. Ich habe für das ganze Socket benutzt. Um sicherzustellen, dass jedesmal nur einer auf den Stream schreibt, habe ich eine variable Busy genommen, die anzeigt ob gelesen oder geschrieben werden kann. 

Auf dem Server habe ich also, für jeden Client einen Thread. Auf dem Client, habe ich ein Thread das alle drei Sekunden  die get Funktion aufruft. Dafür habe ich eine while(true) in Verbindung mit sleep benutzt. 
Um nun die set funktion zu benutzen wird dann die boolean variable Busy auf true gesetzt. In der while Schleife des Threads wird getestet Busy getestet, falls dies true ist, so wird nicht gelesen. Das gleiche gilt auch fürs Schreiben. 

Nun auf den ersten Blick funktioniert dies auch. Allerdings treten dabei ein paar Probleme auf: 
- dies bedeutet einen ünnötigen Trafik auf dem Netzwerk, da alle drei Sekunden der Client eine Anfrage macht, und der Server darauf antwortet
- kann durch diese drei Sekunden Intervalle, das spiel nicht richtig synchronisiert werden, d.h. verschiedene Clients,bekommen den aktuellen Spielstand erst viel später als andere. 

Die optimale Architekur wäre, wenn ein Client jedesmal wenn er gespielt hat, die set Methode benutzt. Der Server referenziert wieder das Objekt, sendet es aber dann sofort an alle Clients, ohne dass die einen Anfrage gemacht habe. Allerdings habe ich da ein paar Probleme, was das Schreiben und Lesen auf dem Stream betreffen. Kann mir jemand erklären, wie ich da verhindern kann das ein Client gleichzeitig auf dem Stream schreibt und liest? Das gleiche Problem befindet sich ja auch auf dem Server. Das grösste Problem was ich habe ist, dass die Spieler nicht nach ein ander spielen müssen. Dies bedeutet, dass es möglich ist, dass der Server während der das Objekt an alle Clients sendet, ein Client versuchen wird ein neues Objekt zum Server zu senden. 

Ich habe ein wenig mit google gesucht, und da die Methode available() gefunden. Kann man damit herausfinden, ob bereits auf dem Stream gelesen oder geschrieben wird?

Ich hoffe Ihr könnt mich in dieser Sache beraten. 

Vielen Dank im Voraus. 
MfG


----------



## tuxedo (14. Nov 2005)

Hmm, du musst da eindeutig eine Synchronisation einbauen und die verteilung der Daten dem Server ueberlassen....Polling beim Client funktioniert ja nicht wie du bereits richtig erkannt hast.

Was ich noch nicht verstehe ist das "verhindern kann das ein Client gleichzeitig auf dem Stream schreibt und liest" ...
Wuerde das so machen (vllt naiver Ansatz -> untested):

Sofern immer nur ein Client zu einer Zeit dem Server seinen Spielstand mitteilen darf:
Dem Server ein Flag geben das anzeigt ob gerade jemand schreibt. D.h. wenn ein Client Daten zum Server sendet muss der Client erst dieses Flag abfragen. Wenn dieses Flag sagt "alles klar, keiner schreibt" sendet er. Der Server muss waehrend dessen das Flag auf "belegt" umschalten. Andere Clients duerfen somit gerade nicht schreiben und muessen sich in eine Warteschleife begeben und es nach einer Zeit X nochmal probieren. 
Ist der Client der gerade geschrieben hat fertig muss der Server sein Flag wieder auf "frei" umschalten. Die anderen Clients koennen dann so nacheinander ihre Daten abgeben..

Problem bei diesem Ansatz:
Unter ganz dummen Umstaeden kann es sein dass ein Client NIE dran kommt mit senden.. In diesem Falle koennte man sowas wie eine Wartenummer vergeben. Client fraegt an ob er senden darf... Server sagt belegt und gibt ihm eine Wartenummer die die Reihenfolge der Sendeerlaubnis darstellt.

Mit dem updaten der Clients vom Server:
Lasse beim Client einen Thread laufen der nach update-Nachrichten vom Server wartet. Jedesmal wenn ein Client seinen Spielstan dem Server mitgeteilt hat schickt der Server an alle gelisteten Clients die aktuelen Spielstaende...

Ist alles nur mal so kurz zusammengedacht und es gibt mit Sicherheit eine bessere Loesung: Try & Error...

greets
Alex


----------



## SchachFritz (14. Nov 2005)

Danke für deine Hilfe. 
Ich habe da  folgendes erstellt: 
ich habe auf derm Sever eine variable, die ich ganz passend Read genannt habe, die jedesmal auf true gesetzt wird, wenn der Server lesen soll. Falls der Server was schreiben muss, so wird Read auf false gesetzt. Das habe ich auch auf dem Client erstellt. 

Allerdings habe ich das Problem, dass nach in.readObject(), der ganze Thread t blockt und ich somit nach Read=false zwar eigentlich im Hauptthread schreiben könnte, der Thread T aber immer noch versucht zu lesen.
Nun zu meiner Frage 

 Kann ich irgendwie die methode in.readObject() unterbrechen?


----------



## tuxedo (14. Nov 2005)

SchachFritz hat gesagt.:
			
		

> Danke für deine Hilfe.
> Ich habe da  folgendes erstellt:
> ich habe auf derm Sever eine variable, die ich ganz passend Read genannt habe, die jedesmal auf true gesetzt wird, wenn der Server lesen soll. Falls der Server was schreiben muss, so wird Read auf false gesetzt. Das habe ich auch auf dem Client erstellt.
> 
> ...



Versteh ich nicht ganz...
Nochmal zum vrstaendnis:

Die hast fuer jeden client auf dem Server einen Thread. Jeder Thread hat zugriff auf eine boolsche Variable, nennen wir die "busy".
Diese Variable steht standardmaessig auf "false". Das heisst "Server ist nicht beschaeftigt".

Sendet ein Client seinen Spielstand an den Server, sprich, wurde ein Object mittels readObject gelesen wird das busy-flag auf TRUE gesetzt. Andere Clients koennen jetzt nichts senden. Der Server verarbeitet jetzt nun den Spielstand und schickt noetige Informationen an die Clients (die passende Empfangsthreads haben). Sobald er alle Informationen an die Clients verschickt hat wird das busy-Flag wieder zurueckgesetzt.

readObject ist "blockierend" und laesst sich meines wissens nicht "unterbrechen" (korrigiert mich bitte jemand wenn ich Muell erzaehle).
Problem bei meinem Ansatz ist dass erst nach dem empfangen eines Objectes vom Client der server nachschauen kann ob jemand anders gerade seinen Spielstand abliefert. In diesem Fall wird es zwangslaeufig irgendwann vorkommen dass 

1) Ein client seinen Spielstand gesendet hat und der Server diesen gerade arbeitet. Er ist somit beschaeftigt und kann waehrend dessen nix anderes tun (um inkonsistenz zu vermeiden).
2) Andere Clients die gerade ihren Spielstand abliefern senden diesen zum Server. Da readObject blockierend kann der Server erst nach einem erfolgreichen readObject nachschauen ob er den Spielstand gleich verarbeiten kann. Somit koennen mehrere Spielstaende gleichzeitig in am Server eintreffen und koennen nicht sofort verarbeitet werden.
3) Ist der erste Client der die verarbeitung anderer Spielstaende blockiert hat fertig wird das Flag wieder auf false gesetzt. Somit koennten die anstehenden Spielstaende verarbeitet werden. ABER: es stehen ja mehrere an. Wer darf zuerst ?

Ich denke du muesstest mal erklaeren wie das mit deinen Spielstaenden funktioniert:
Kommt es zu inkonsistenzen wenn der Server in mehreren Threads gleichzeitig Spielstaende verarbeitet ? 

- Alex


----------



## SchachFritz (14. Nov 2005)

Erstmal danke für deine Hilfe. Es ist jedesmal nur einer der Clients der den Spielstand sendet. Allerdings können sämtliche Clients zur jeder Zeit ein "Exit" schicken. 
Bei ankunft des exits an den Server, wird dieser die Socketverbindung schliessen. Danach sendet er das exit an alle anderen Clients. Diese werden dann wiederum eine Message anzeigen und dann auch ein exit an der Server senden. 

Was allerdings den Spielstand anbelangt kann also keine Inkonistenz auftreten. Jedoch kann es sein, dass der Client der gerade den Spielstand sendet auch ein Exit senden muss (wegen dummheit des Users).  In der Version die ich vorher hatte, hat dies einwandfrei funktioniert. Allerdings sendet der Client ja dafür auch jede 3 Sekunden eine Anfrage an den Server. Ich will aber den Trafik auf dem netz verringern. 

Code auf dem Client

```
class Updater implements Runnable
   {public boolean hastoRun = true;
       public void run()
       {
           while (hastoRun)
           { while (Read)
               try {
               
             Object obj=null ;              
               
                   obj=in.readObject();
                   System.out.println("Object readed");
                  
                   if (obj!=null){ System.out.println("Received partie");getPartie((packet) obj); }
                   else System.out.println("Partie £=null");
               }
      
           
                   
               
               
           catch (Exception Ex){System.out.println(Ex.toString());}}
            
           



       }
   }

   public void sendPartie(Partie partie)
   {
       
      
       Read = false;
       
       try{  in = null;
           
} catch(Exception ex){}
   
       System.out.println("Sending partie");

       packet toSend = new packet(Command.UPDATE_PARTIE,partie);
       System.out.println("Ok.. busy1");
       try {out.writeObject(toSend);out.flush();System.out.println("Wrriten to sefver"); out.close();  in = new ObjectInputStream(myclient.getInputStream());}
       catch (Exception Ex)
       {
        JOptionPane.showMessageDialog(null, CANNOT_SEND + Ex.toString(), "Erreur de connexion", JOptionPane.ERROR_MESSAGE);
        sendExit(null);
       }
       Read=true;
      

   }
```

Meine Idee war, dass der Client das lesen nur unterbrechen muss, wenn er etwas senden will. Das Problem ist jedoch, das wenn er bereits beim readObjekt angelangt ist, so ist der Thread ja blockiert. Und kann nicht mehr testen ob er lesen soll oder nicht. 

Auf dem Server habe ich folgendes: 




```
public void handleClient(Socket socket)
    {
        ObjectInputStream in = null;
        ObjectOutputStream out = null;
        boolean hastorun =true;
        try{
          in = new ObjectInputStream(socket.getInputStream());

      }
      catch (Exception ex) {display(CANNOT_SEND);}
       InetAddress client_address = socket.getInetAddress();

        display(NEW_CONNECTION+ client_address.getHostAddress());

       packet pa = null;
       Object ob = null;
       broadCast();
       while (hastorun){

           try {
               while (Read){
              ob=in.readObject();

               if (ob!=null){
               display("Getting command .... " + client_address);
               pa=(packet) ob;
               if (pa.getCOMMAND()==Command.UPDATE_PARTIE) {Actual= pa;broadCast();}
```
....

```
public void broadCast()
      {
          Read=false;
         
          display("Broadcasting");
          for (int i =0;i<clients.size();i++)
          {
              Socket sock = (Socket) clients.get(i);
             try{ ObjectOutputStream out = new ObjectOutputStream(sock.getOutputStream());
                 InetAddress adress = sock.getInetAddress();
                 display("Writing to  " + adress.getHostAddress());
                 out.writeObject(Actual);
                 out.flush();
                 display("Writing completed.. searching next client.");

             }
             catch(Exception Ex) {display("Broadcast error");}
          }
          display("Broadcast completed");
          Read=true;
   


      }
```

Kann mir jemand veraten wie ich meine Idee umsetzen kann. 

Danke


----------



## tuxedo (14. Nov 2005)

Korrigier mich wenn ichs schon wieder falsch verstanden habe.. Aber dein Problem besteht nur darin dass es zu komplikationen kommt wenn jemand ausversehen auf EXIT geklickt hat?

In deinem HandleClient hast du glaub auch nen Fehler drin.. Du hast ne While-Schleife deren Bedingung "read" ist... 
So wie ich das verstehe waere ein "if (read)" geschickter.

Aber wenn das gleichzeitige senden der Spielstaende von den Clients eh keine Inkonsistenzen macht koennen wir uns das READ an dieser Stelle sparen.


Ich hab nochmals deinen ersten Thread durchgelesen. Alle X Sekunden fraegen die Clients den Server nach Spielstaenden anderer Clients? Seh ich das richtig ? Im Code find ich das nicht, oder hast du das schon geaendert...

Ich glaub wir muessen mal alles unnoetige "rauskuerzen" damit wir wieder auf einen Nenner kommen 

- Alex


----------



## SchachFritz (14. Nov 2005)

Es kann sein dass ich das mit den Sockets falsch verstanden habe. Aber soweit ich weiss, kann man nicht gleichzeitig auf den Stream schreiben und lesen. Daher wollte ich die if oder while (Read) haben, damit der Thread weiss ob er lesen soll oder nicht. 
Da ja manchmal dieMethode sendPartie auf den Stream schreiben muss setzt sie Read auf false, somit soll der Thread Updater ja dann aufhören auf dem Stream zu lesen. Anschliessend kann sendPartie ein Objekt senden und dann Read wieder auf true setzen, und der Thread Updater kann wieder mit lesen anfangen. 

Das Problem bei dem ganzen ist ja, wenn sendPartie Read auf false setzt, so ist der Thread Updater meistens bereits wegen dem readObjekt blockiert und kann garnicht testen, ob er überhaupt noch lesen soll. Denn er wird auch nichts vom Server gesendet bekommen, da er es ist der senden soll. 

Denn der Server liest ja auch und wartet darauf dass ein Client was sendet. Anschliessen sendet der Server es an alle Clients. Ergo, wird der Client auch nichts vom Server bekommen. Beide lesen also auf dem selben Stream ohne, dass irgendwas gesendet wird.


*EDIT: *

Eine Möglichkeit wäre, dass wenn die Methode sendPartie etwas schreiben will, dass dann der ganze Thread Updater gestoppt wird. Jedoch wäre dies nicht besonders sauber.


----------

