Socket Mehrere gleichzeitig eingehende Nachrichten

Creylon

Mitglied
Hallo,

ich saß die letzten Tage an einem Server für ein kleines Spiel. Synchro läuft super, bis ich es mit 3 Leuten gleichzeitig ausprobiert habe...

Ich bekomme eine java.i😵ptionalDataException, was in meinen Augen heißt, das der aktuelle Client gleichzeitig die Nachricht von den beiden anderen Clients bekommt.

Hat jemand eine Ahnung, wie ich das am besten Löse?

Ich habe überlegt, ob man, wenn es mehrere Nachrichten sind, diese in einen Array schreiben könnte.

MIt freundlichen Grüßen
 
Naja man kann nicht wirklich viel sehen.

Hier sendet der Server
Java:
message = (Message)thisClient.ois.readObject();
				
				message.ID = thisClient.ID;
				
				for(Clients client : clients)
				{
					if(client.ID != thisClient.ID)
					{
						client.oos.writeObject(message);
						client.oos.flush();
					}
				}

Hier empfängt der Client.

Java:
public void run() {
		while(true)
		{
			try {
				
				message = (Message)ois.readObject();
				
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (ClassNotFoundException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
			HasInput = true;
			
		}
	}

Ich habe für jeden Client auf dem Server einen Thread, weswegen mehrere Nachrichten gleichzeitig kommen.
 
JabvaDoc lesen könnte helfen:

OptionalDataException (Java 2 Platform SE v1.4.2)

This exception may be thrown in two cases:

* An attempt was made to read an object when the next element in the stream is primitive data. In this case, the OptionalDataException's length field is set to the number of bytes of primitive data immediately readable from the stream, and the eof field is set to false.
* An attempt was made to read past the end of data consumable by a class-defined readObject or readExternal method. In this case, the OptionalDataException's eof field is set to true, and the length field is set to 0.

Scheint so als ob du dein eigenes Protokoll nicht sauber einhälst.

Schreibst du vielleicht mit mehreren Threads in ein und denselben Stream eines einzelnen Clients? Stelle sicher dass nur immer ein Thread in einen OutputStream was schreiben kann. Evtl so:

Java:
ObjectOutputStream oos = client.oos;
synchronized(oos) {
   oos.writeObject(message);
   oos.flush();
}

Nebenbei: statischer Zugriff auf solche Variablen mag zwar an manchen Stellen praktisch sein, wird aber generell nicht empfohlen. Besser ein "getOos()" in "client" einbauen.

- Alex
 
Die Methode funktioniert, jedoch lagt dann bei drei Spielern immer einer, weil ja immer nur von einer Seite was kommt.

Ich habe mir nun auf meinem Server einen Array entsprechend der Slotanzahl angelegt. Ich empfange vom client eine Message, trag sie in den für ihn vorgesehen ArrayIndex hinein und schick den gesamt Array an den einen Client zurück.

Mein Problem: Beim Client kommt zwar einmal die Nachricht an, die Koordinaten beispielsweise sind dann immer 0...
 
Die Methode funktioniert, jedoch lagt dann bei drei Spielern immer einer, weil ja immer nur von einer Seite was kommt.

Ist IMO auch etwas blödsinnig das ganze Spiel über alle Clients zu synchronisieren. Wenn du ein Client hast der eine Nachricht synchron zum Server schickt, und dieser synchron sequentiell alle Clients benachrichtigt, dann skaliert das gaaanz schlecht. Je mehr Clients zu hast, desto langsamer wird es.

Optimieren könntest du jetzt in der Schleife in der alle Clients benachrichtigt werden. An der Stelle kannst du parallelisieren. Warum einen nach dem anderen informieren? Spendiere 2-3 Threads die sich gleichzeitig um diese Aufgabe kümmern.

Vielleicht solltest du deine Spiellogik auch nochmal überdenken: Ist es wirklich notwendig dass der Client wartet bis alle anderen Clients seine Meldung erhalten haben?

Ich habe mir nun auf meinem Server einen Array entsprechend der Slotanzahl angelegt. Ich empfange vom client eine Message, trag sie in den für ihn vorgesehen ArrayIndex hinein und schick den gesamt Array an den einen Client zurück.

Mein Problem: Beim Client kommt zwar einmal die Nachricht an, die Koordinaten beispielsweise sind dann immer 0...

Keine Ahnung was du hier treibst. Klingt auf alle Fälle "seltsam".

Generell mein Rat:

* Synchrone Stellen in der High-Level Kommunikation verhinden wo's nur geht. Sprich: Einen Client nicht auf eine Antwort des Servers warten lassen wenns nicht doch anders geht. Heisst nicht, dass du im Code nirgends ein "synchronized" Block verwenden darfst. Oft kommst du um sowas gar nicht drum rum.

Gruß
Alex
 
Okay anscheinend hast du das nicht so ganze verstanden ^^

Ich will erstmal nur erreichen, dass man zusammen auf einem Server ist, wo man einfach nur seine Spielfigur bewegen kann und die Positionen der anderen Spielern immer aktualisiert werden.

1. Meine Position an den Server schicken
2. Positionen der anderen Spieler empfangen
3. Spieler an der entsprechenden Stelle Zeichnen

zu 1.

Ich hab ein Objekt, dass alle nötigen Informationen zu meinem Spieler enthält. (X und Y)

Dieses sende ich via ObjectOutputStream zum Server.

zu 2.

Ich erhalte einen Array des selben Objektes, welches ich schon zum Server hingeschickt habe, wobei jeder verbundene Spieler einen Index hat.

zu 3.

Ich lese das ganze mit einer For-Schleife aus und aktualisiere die Koordinaten oder erstelle falls notwendig einen neuen Spieler.


Server:
Hier habe ich einen Array, der stehts alle Koordinaten verbundener Spieler enthält.

Jeder Client hat einen eigenen Thread. Ich empfange also die Koordinaten des einen Spielers und aktualisiere damit die aus dem Array.

Nun nehme ich diesen Array, der, wie bereits erwähnt, die Koordinaten aller Spieler enthält, und sende diesen an den einen Client zurück, von dem ich vorher die Koordinaten bekommen habe.

Dies geschieht separat für jeden einzelnen Client parallel.


Ich hoffe ich konnte es etwas verständlicher erklären.
 
Okay, verstanden. D.h. ein Client bekommt nur dann die Positionsupdates anderer Spieler mit, wenn er selbst ein Update sendet? Würde das von "pull" auf "push" umbauen. Positions-Updates werden empfangen wenn es der Server für richtig hält die Clients mit neuen Updates zu versehen. Aber gut, musst du wissen was für dich geschickter ist.

Bzgl. dem "ist 0"... Ich glaube da bist du in eine Object*Stream-Falle getappt.... Der OOS und der OIS merken sich, welche Objekte sie bereits übertragen haben. Wird das selbe Objekt nochmal übertragen (nur mit anderen Werten drin), dann wird nicht das Objekt neu serialisiert, sondern nur eine Referenz-ID übertragen. Der Empfänger weiß dann: "Oh, eine Referenz-ID "XYZ". Das ist das Objekt von vorhin. Prima. Das kenn ich schon." Und der Server spart sich das aufwendige serialisieren des Objekts. Geht schneller, klar, aber du hast nicht den gewünschten Effekt, nämlich das gleiche Objekt, nur mit geändertem Inhalt übertragen...

Das Problem ist hier nochmal ganz gut veranschaulicht: [JavaSpecialists 088] - Resetting ObjectOutputStream

Die Lösung:

Java:
oos.reset();

Macht aber nur dann Sinn zu resetten, wenn das Objekt tatsächlich neuen Inhalt hat. Wenn du immer resettest, sparst du dir das prüfen ob sich der Objektinhalt geändert hat, aber du verlierst den Vorteil dass nur eine ID und nicht das ganze Objekt übertragen werden muss.

- Alex
 

Zurück
Oben