# Problem mit vielen Bytes über Socket



## xasz (17. Feb 2014)

Hallo Leute,

ich mache schon einige Tage an einem kleinen Problem mit Java Sockets rum.
Ich habe ein kleines Spiel geschrieben und möchte um Erfahrung zu sammeln mal ein bisschen mit Netzwerkprogrammierung arbeiten.

Ich habe mir ein kleines Protokollausgedacht, das mit Bytefolgen arbeitet.
Sprich mein Client bekommt ein bestimmtes byte was zum Beispiel für ein Bewegungsbefehl steht. Mein Client weis nun ok jetzt kommen X Bytes die den Bewegungsbefehl repräsentieren und liest die nächste X Bytes ein. Dann kommt das nächste Byte was das nächsten Befehl angibt usw.

Mein Problem ist, dass bei mir alles einwandfrei lokal funktioniert. Sprich wenn ich auf meinem Computer ein Spiel hoste und mit einer zweiten Instanz joine läuft alles perfekt und wunderbar, also über den lokalen TCP Stack.

Jetzt wollte ich die Sache einem Freund zeigen und schicke Ihm einen Client. Portforwardings etc sind gemacht. Ich starte meinen Server und mein Freund kann darauf Joinen und es läuft auch erstmal gut.

Wenn ich in meinem Beispiel z.b. 10 Einheiten erstelle und nur wenig Befehle übertragen werden, dann läuft alles gut und auch über längere zeit stabil. Wenn ich jetzt allerdings 1000 Einheiten habe, dann fängt es an zu laggen. Womit ich auch kein Problem habe ( Dient nur als Stresstest). Allerdings wenn ich jetzt z.b. 2000 Einheiten erstelle dann lagt es stark und der Client Crashed, Weil er eine Falsche Bytefolge einliest, bzw eine ungültige. Also es kommen einfach falsche Bits an, bzw es Fehlen welche. Da der Client z.b. einen Befehl Bewegung bekommt und dann soll er 12 Byte lesen und dann kommt das nächste Kontrollbit. Jetzt kommen allerdings nur z.b. 10 und dann das nächste Kontrollbit, womit mein Client natürlich das falsche Byte als Befehlsbyte liest und crashed.


Mir ist nicht ganz klar warum das passiert und was ich dagegen tun kann.
Das sind gerade meine Funktionen die zum Senden und Empfangen benutze:

*Server*:

```
public void send(byte[] bytes) throws IOException {
		if(this.socket.isClosed()){
			this.stopListen();
			this.gameServer.removeClientHandler(this);
			return;
		}
		this.outputStream.write(bytes);
		this.outputStream.flush();
	}
	public void read(byte[] bytes){
		try {
			this.inputStream.read(bytes);
		} catch (IOException e) {					
			this.gameServer.removeClientHandler(this);
			this.stopListen();
			e.printStackTrace();
		}
	}
```

*Client*:

```
public void send(byte[] bytes) throws IOException {
		this.outputStream.write(bytes);
		this.outputStream.flush();
	}
	public void read(byte[] bytes){
		try {
			this.inputStream.read(bytes);
			System.out.println("Recieve:"+NetworkConnector.bytesToString(bytes));
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
```

Über jede Idee oder Hilfe wäre ich sehr dankbar.

Bisher bin ich davon ausgegangen, dass die Java-Sockets TCP Benutzen und hier dann die Sichere Übertragung gewährleistet werden sein sollte.

Danke euch, xasz


----------



## Tobse (17. Feb 2014)

Das liegt daran, dass bei 20-30 einheiten die Menge an Bytes pro Sekunde eure Bandbreite/Rechengeschwindigkeit noch nicht überschreitet. Sprich: Es kommen weniger Aufrufe an die write-Methode als pro Sekunde übertagen werden könnten.
Wenn es jetzt aber sehr viele Threads sind, kommen die Befehle mit Zeitlicher überschneidung:

```
-------------------------------------------------->Zeit    Ausgabe an der Gegenseite:
Wenige Einheiten:                                          
Thread 1:   11 11 11 11                      11 11 11 11   11 11 11 11 22 22 22 22 11 11 11 11
Thread 2:                     22 22 22 22
Viele Einheiten:
Thread 1:   11 11 11 11                      11 11 11 11   11 11 44 11 33 44 11 33 33 44 44 33
Thread 2:                     22 22 22 22                  22 22 22 22 33 44 33 33 11 33 44 11
Thread 3:         33 33 33 33          33 33 33 33         11 44 44 11
Thread 4:      44 44 44 44                44 44 44 44
```

Du siehst, dass die zusammengehörenden Bytefolgen im Zweiten Beispiel nichtmehr zusammenhängend ankommen.
Die Abhilfe ist synchronisation des sendens der bytes. Das heisst: Es wird immer nur von einem Thread gleichzeitig gesendet. Und erst wenn der fertig ist, kommt der nächste. Siehe Java Keyword synchronized.

Und gleich als Tipp wie du das gescheit umsetzen kannst:

```
class ProtocolCommand
{
    private byte nummer;
    private byte[] argumente;
    
    /** getter und setter **/
}
class Protocol
{
    public synchronized sendCommand(ProtocolCommand cmd)
    {
        out.write(cmd.getNummer());
        out.write(cmd.getArgumente());
    }
    public sendMoveCommand(Einheit e, Position neuePosition)
    {
        sendCommand(new PositionsÄnderungsCommand(e, neuePosition));
    }
}
```
Es wird also immer nur eine zusammengehörende Kette an Bytes gleichzeitig gesendet. Das Ergebnis auf das Diagramm von oben wäre:

```
-------------------------------------------------->Zeit    Ausgabe an der Gegenseite:
Viele Einheiten:
Thread 1:   11 11 11 11                      11 11 11 11   11 11 11 11 44 44 44 44 33 33 33 33
Thread 2:                     22 22 22 22                  22 22 22 22 33 33 33 33 44 44 44 44
Thread 3:         33 33 33 33          33 33 33 33         11 11 11 11
Thread 4:      44 44 44 44                44 44 44 44
```

Jetzt wird aber natürlich der Befehl nichtmehr direkt an dem Zeitpunk übertragen wo er beim Client aufgetreten ist. D.h. du solltest bei größeren Datenmengen den Zeitpunkt mitschicken damit alle Clients zeitlich synchron sind.


----------



## xasz (17. Feb 2014)

Hallo und schonmal vielen Dank für deine ausführliche Antwort,

ich habs jetzt mit dem syncronized noch nicht ausprobiert. Werd ich heute nicht mehr zu kommen, werd mir das erstmal genau durchlesen, was das Schlüsselwort macht.

Allerdings weis ich nicht, ob das mein Problem ist, weil ich hab eigentlich nur ein Thread der die Netzerkommunikation macht. Bzw habe ich den "GameThread" und den "NetworkThread" auf Client als auch Serverseite.


----------



## Tobse (17. Feb 2014)

xasz hat gesagt.:


> Hallo und schonmal vielen Dank für deine ausführliche Antwort,
> 
> ich habs jetzt mit dem syncronized noch nicht ausprobiert. Werd ich heute nicht mehr zu kommen, werd mir das erstmal genau durchlesen, was das Schlüsselwort macht.
> 
> Allerdings weis ich nicht, ob das mein Problem ist, weil ich hab eigentlich nur ein Thread der die Netzerkommunikation macht. Bzw habe ich den "GameThread" und den "NetworkThread" auf Client als auch Serverseite.



Wenn du das Spiel nicht komplett mit einer Hauptschleife implementiert hast (was ich hoffen will) macht das keinen Unterschied. Die Threads aus dem Spiel rufen eine Methode auf der Netzwerk-Klasse auf. Und sobald das zu viele sind gibts eben das Chaos wie oben beschrieben.
Wenn die Anzahl der Threads nicht die Ursache ist, weiss ich absolut nicht, wo sonst das Problem liegen könnte.


----------



## xasz (17. Feb 2014)

*kopftisch*

Hast natürlich absolut Recht.
Daran hab ich jetzt nicht gedacht.


----------



## xasz (20. Feb 2014)

So nochmal ich.
Ich hab das jetzt mal getestet und irgenwie bin ich dem Problem noch nicht aus dem Weg gekommen.

Habe erst nur die Send Funktion syncronized gemacht und das Problem war noch da.
Dann hab ich auch die Read Funktion syncronized gemacht, dann hat irgendwie nicht mehr wirklich was funktioniert.

Nach meinem Verständnis müsste ich auch nur die Send Funktion syncronized machen.

Noch irgendne idee ?


----------



## Tobse (21. Feb 2014)

xasz hat gesagt.:


> So nochmal ich.
> Ich hab das jetzt mal getestet und irgenwie bin ich dem Problem noch nicht aus dem Weg gekommen.
> 
> Habe erst nur die Send Funktion syncronized gemacht und das Problem war noch da.
> ...



Kommt darauf an, ob die zusammengehörenden Pakete mit einem einzigen Aufruf an send() übertragen werden. Wenn es mehrere sind bringt dir das synchronized nichts.


----------



## xasz (21. Feb 2014)

Ich übertrage immer nur mit einem Send Aufruf.
Also das Bytes-Paket wird geschnürt und dann verschickt.


----------



## Tobse (21. Feb 2014)

Hm, dann sollte das eigentlich klappen. Wenn es das nicht ist kann ich mir nurnoch vorstellen dass die Computer mit der schieren Menge an Einheiten nichtmehr klar kommen. Aber dann bin ich mit meinem Latein auch am Ende, sorry.


----------



## xasz (21. Feb 2014)

Ich hab für mich nun beschlossen, ich mach nen neuen Branch auf meim svn aus und probier das mal mit java nio2.
Vielleicht tut nen kleiner Rewrite zu dieser Zeit des Entwicklungsstadiums nicht so weh, wie später.
Trotzdem danke.

Werde mich melden, falls ich noch was rausfinde.

Edit: Leider find ich keine wirklich guten Quellen zum lernen von java socketchannel etc.
Überall finde ich nur so Sachen .. so gehts.. Codebeispiel einfügen .. fertig. Aber ohne wirkliche Erklärung.
Hat da jemand nen guten Tipp ? Wäre Dankbar.


----------



## Barista (21. Feb 2014)

> Ich hab für mich nun beschlossen, ich mach nen neuen Branch auf meim svn aus und probier das mal mit java nio2.



Das löst nicht das ursprüngliche Problem.

Ich würde mir die gesendeten und empfangenen Daten in jeweils eine Log-Datei schreiben und diese dann prüfen.

Eventuell würde ich das Protokoll mit Prüfsummen absichern und bei einem Fehler eine Exception werfen, damit sich der Fehler nicht ausbreitet(Folgefehler).


----------



## xasz (23. Feb 2014)

Ok,

ich hab mich doch dazu entschieden mal beim alten Modell zu bleiben. Egal ob ich das später nochmal rewrite. Ich möchte das auch des Wissens-Willen hinbekommen.

Ich habe meine Send Funktion am Server mal so umgebaut:


```
public synchronized void send(byte[] bytes) throws IOException {
		if(this.isSending)System.out.println("Das sollte nicht sein");
		this.isSending = true;
		if(this.socket.isClosed()){
			this.stopListen();
			this.gameServer.removeClientHandler(this);
			return;
		}
		synchronized(this.outputStream){
			this.outputStream.write(bytes);
			this.outputStream.flush();
		}
		this.isSending = false;
	}
```

Das Problem ist, dass ich das so verstehe, dass durch das syncronized die Funktion nicht zur gleichen Zeit aufgerufen werden kann, sondern gequed wird, bis die andere fertig ist.

Mein Problem ist, dass "Das sollte nicht sein" trotzdem ausgegeben wird, und damit der Fehler leider immernoch da ist.

Noch irgendne Idee ?

Interessanter weise, crashed mein Client aber auch manchmal, ohne das am Server die Meldung kommt und somit ja quasi keine bytes am server durcheinander versendet werden, aber am Client in falscher Reihenfolge ankommen.

Ich setz hier mal noch ein bisschen Quellcode an:

Das hier ist mein Client:



Spoiler





```
package de.xasz.ParkTycoon.Controller.Networking;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.net.Socket;
import java.net.UnknownHostException;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.math.Vector3;

import de.xasz.ParkTycoon.GameObject;
import de.xasz.ParkTycoon.Controller.Entity.Entity;
import de.xasz.ParkTycoon.Controller.Level.ClientLevel;
import de.xasz.ParkTycoon.Controller.Level.Level;
import de.xasz.ParkTycoon.Controller.Level.Tiles.TerrainTile;
import de.xasz.ParkTycoon.Controller.Level.Tiles.Tile;
import de.xasz.ParkTycoon.Controller.Ways.Way;
import de.xasz.ParkTycoon.Controller.Ways.WayIsAlreadyInListException;
import de.xasz.ParkTycoon.Utill.IntVector3;

public class GameClient extends Thread {
	protected Socket socket;
	protected int serverPort;
	protected String host;
	protected BufferedInputStream inputStream;
	protected BufferedOutputStream outputStream;
	protected boolean isListening = false;
	protected GameObject gameObject;
	public GameClient(String host, int port, GameObject gameObject) {
		this.serverPort = port;
		this.host = host;
		this.gameObject = gameObject;
	}

	public void connect() throws UnknownHostException, IOException {
		this.socket = new Socket(host, serverPort);
		this.inputStream = new BufferedInputStream(this.socket.getInputStream());
		this.outputStream = new BufferedOutputStream(
				this.socket.getOutputStream());
	}

	@Override
	public void run() {
		this.isListening = true;
		while (this.isListening)
			listen();
	}

	public void stopListen() {
		this.isListening = false;
	}

	public void listen() {
		byte[] code = new byte[1];
		byte[] bytes = null;
		if (socket.isClosed()) {
			this.stopListen();
			Gdx.app.exit();
		}
		read(code);
		switch (code[0]) {
		case NetworkActionCode.GAME_PAUSE:
			this.gameObject.pauseGame();
			break;
		case NetworkActionCode.GAME_UNPAUSE:
			this.gameObject.unpauseGame();
			break;
		case NetworkActionCode.TERRAINTILE_CREATE:
			bytes = new byte[16];
			read(bytes);
			((ClientLevel)this.gameObject.getLevel())
					.remoteAddTile(NetworkParser.parseCreateTileEvent(bytes));
			break;
		case NetworkActionCode.TERRAINTILE_REMOVE:
			bytes = new byte[12];
			read(bytes);
			((ClientLevel)this.gameObject.getLevel())
					.remoteRemoveTile(NetworkParser.parseRemoveTileEvent(bytes));
			break;
		case NetworkActionCode.ENTITY_REMOVE:
			bytes = new byte[4];
			read(bytes);
			((ClientLevel)this.gameObject.getLevel())
					.remoteRemoveEntity(NetworkParser.parseRemoveEntityEvent(bytes));
			break;
		case NetworkActionCode.ENTITY_ADD:
			bytes = new byte[24 + 4 + 4];
			read(bytes);
			((ClientLevel)this.gameObject.getLevel())
					.remoteAddEntity(NetworkParser.parseAddEntityEvent(bytes));
			break;
		case NetworkActionCode.ENTITY_MOVE:
			bytes = new byte[24 + 4];
			read(bytes);
			NetworkParser.parseEntityMoveEvent(bytes, this.gameObject.getLevel());
			break;
		case NetworkActionCode.WAY_REMOVE:
			bytes = new byte[12];
			read(bytes);
			((ClientLevel)this.gameObject.getLevel())
					.remoteRemoveWay(NetworkParser.parseRemoveWayEvent(bytes));
			break;
		case NetworkActionCode.WAY_ADD:
			bytes = new byte[12];
			read(bytes);
			((ClientLevel)this.gameObject.getLevel())
					.remoteAddWay(NetworkParser.parseAddWayEvent(bytes));
			break;
			case NetworkActionCode.LEVEL_DIMENSION:
				this.gameObject.getLevel().createNewEmptyLevel();
			break;
		}
	}

	public synchronized void send(byte[] bytes) throws IOException {
		this.outputStream.write(bytes);
		try{
			this.outputStream.flush();
		}catch(Exception e){
			System.out.println(e.getLocalizedMessage());
		}
	}

	public void read(byte[] bytes) {
		try {
			this.inputStream.read(bytes);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public void closeStockets() {
		try {
			this.inputStream.close();
			this.outputStream.close();
			this.socket.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public void loadLevel() {
		try {
			send(new byte[] { NetworkActionCode.LEVEL_FULL_SYNC });
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	public void sendCreateTileEvent(Tile tile){
		byte[] bytes = NetworkParser.packCreateTileEvent(tile);
		try {
			this.send(bytes);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	public void sendRemoveTileEvent(IntVector3 vec){
		byte[] bytes = NetworkParser.packRemoveTileEvent(vec);
		try {
			this.send(bytes);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	public void sendAddWayEvent(Way way){
		byte[] bytes = NetworkParser.packAddWayEvent(way);
		try {
			this.send(bytes);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	public void sendRemoveWayEvent(IntVector3 vec){
		byte[] bytes = NetworkParser.packRemoveWayEvent(vec);
		try {
			this.send(bytes);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	public void sendAddEntityEvent(Entity ent){
		byte[] bytes = NetworkParser.packAddEntityEvent(ent);
		try {
			this.send(bytes);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	public void sendRemoveEntityEvent(int entID){
		byte[] bytes = NetworkParser.packRemoveEntityEvent(entID);
		try {
			this.send(bytes);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public void sendFullSync() {
		try {
			send(new byte[]{NetworkActionCode.LEVEL_FULL_SYNC});
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}
```




Das hier ist mein Clienthandler am Server. Für jeden Client läuft am Server ein Clienthandler als extra Thread:


Spoiler





```
package de.xasz.ParkTycoon.Controller.Networking;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.net.SocketException;

import com.badlogic.gdx.math.Vector3;

import de.xasz.ParkTycoon.Controller.Entity.Entity;
import de.xasz.ParkTycoon.Controller.Level.Level;
import de.xasz.ParkTycoon.Controller.Level.ServerLevel;
import de.xasz.ParkTycoon.Controller.Level.Tiles.TerrainTile;
import de.xasz.ParkTycoon.Controller.Level.Tiles.Tile;
import de.xasz.ParkTycoon.Controller.Ways.Way;
import de.xasz.ParkTycoon.Controller.Ways.WayIsAlreadyInListException;
import de.xasz.ParkTycoon.Utill.IntVector3;

public class ClientHandler extends Thread{
	protected NetworkServer gameServer;
	protected Socket socket;
	protected BufferedInputStream inputStream;
	protected BufferedOutputStream outputStream;
	protected boolean isListening = false;
	protected boolean isSending = false;
	public ClientHandler(NetworkServer gs, Socket socket) throws IOException{
		this.gameServer = gs;
		this.socket = socket;
		this.inputStream = new BufferedInputStream(this.socket.getInputStream());
		this.outputStream = new BufferedOutputStream(this.socket.getOutputStream());
	}
	@Override
	public void run(){
		this.isListening = true;
		while(this.isListening) listen();
	}
	public void stopListen(){
		this.isListening = false;
	}
	public void listen(){
			byte[] bytes = new byte[0];
			byte[] code = new byte[1];
			try {	
				read(code);
				switch(code[0]){
				case NetworkActionCode.GAME_PAUSE:
					this.gameServer.getGameObject().pauseGame();
					this.gameServer.send(bytes);
				break;
				case NetworkActionCode.GAME_UNPAUSE:
					this.gameServer.getGameObject().unpauseGame();
					this.gameServer.send(bytes);
				break;
				case NetworkActionCode.LEVEL_FULL_SYNC:
					this.gameServer.getGameObject().pauseGame();
					this.gameServer.send(new byte[]{NetworkActionCode.GAME_PAUSE});
					while(!this.gameServer.getGameObject().canSync());
					this.gameServer.send(new byte[]{NetworkActionCode.LEVEL_DIMENSION});
					
					Level level = this.gameServer.getGameObject().getLevel();
					for(int x = 0; x < level.LEVEL_X_SIZE;x++){
						for(int y = 0; y < level.LEVEL_Y_SIZE;y++){
							for(int z = 0; z < level.LEVEL_Z_SIZE;z++){
								Tile tile = level.getTile(x, y, z);
								if(tile != null){
									this.send(NetworkParser.packCreateTileEvent(tile));
								}
							}	
						}	
					}
					for(Way way : level.getWayManager().getWays()){
						this.send(NetworkParser.packAddWayEvent(way));
					}
					for(Entity ent: level.getEntities()){
						this.send(NetworkParser.packAddEntityEvent(ent));
					}
					this.gameServer.getGameObject().unpauseGame();
					this.gameServer.send(new byte[]{NetworkActionCode.GAME_UNPAUSE});
				break;
					case NetworkActionCode.TERRAINTILE_CREATE:
						bytes = new byte[16];
						read(bytes);
						this.gameServer.getGameObject().getLevel().addTile(NetworkParser.parseCreateTileEvent(bytes));
					break;
					case NetworkActionCode.TERRAINTILE_REMOVE:
						bytes = new byte[12];
						read(bytes);
						this.gameServer.getGameObject().getLevel().removeTile(NetworkParser.parseRemoveTileEvent(bytes));
					break;
					case NetworkActionCode.ENTITY_REMOVE:
						bytes = new byte[4];
						read(bytes);
						this.gameServer.getGameObject().getLevel().removeEntity(NetworkParser.parseRemoveEntityEvent(bytes));
					break;
					case NetworkActionCode.ENTITY_ADD:
						bytes = new byte[24+4+4];
						read(bytes);
						this.gameServer.getGameObject().getLevel().addEntity(NetworkParser.parseAddEntityEvent(bytes));
					break;
					case NetworkActionCode.ENTITY_MOVE:
						bytes = new byte[24+4];
						read(bytes);
						NetworkParser.parseEntityMoveEvent(bytes,this.gameServer.getGameObject().getLevel());
						
					break;
					case NetworkActionCode.WAY_REMOVE:
						bytes = new byte[12];
						read(bytes);
						this.gameServer.getGameObject().getLevel().removeWay(NetworkParser.parseRemoveWayEvent(bytes));
					break;
					case NetworkActionCode.WAY_ADD:
						bytes = new byte[12];
						read(bytes);
						this.gameServer.getGameObject().getLevel().addWay(NetworkParser.parseAddWayEvent(bytes));
					break;
				}
			} catch (SocketException e) {
				this.gameServer.removeClientHandler(this);
				this.stopListen();
				return;
			} catch (IOException e) {
				e.printStackTrace();
			}
	}
	public void closeStockets() {
		try {
			this.inputStream.close();
			this.outputStream.close();
			this.socket.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}	
	public synchronized void send(byte[] bytes) throws IOException {
		if(this.isSending)System.out.println("Das sollte nicht sein");
		this.isSending = true;
		if(this.socket.isClosed()){
			this.stopListen();
			this.gameServer.removeClientHandler(this);
			return;
		}
		synchronized(this.outputStream){
			this.outputStream.write(bytes);
			this.outputStream.flush();
		}
		this.isSending = false;
	}
	public void read(byte[] bytes){
		try {
			this.inputStream.read(bytes);
		} catch (IOException e) {					
			this.gameServer.removeClientHandler(this);
			this.stopListen();
			e.printStackTrace();
		}
	}
}
```





Das hier ist mein Networkserver, der die Clienthandlers managed:



Spoiler





```
package de.xasz.ParkTycoon.Controller.Networking;


import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import de.xasz.ParkTycoon.GameObject;
import de.xasz.ParkTycoon.Controller.Entity.Entity;
import de.xasz.ParkTycoon.Controller.Level.Tiles.Tile;
import de.xasz.ParkTycoon.Controller.Ways.Way;
import de.xasz.ParkTycoon.Utill.IntVector3;


public class NetworkServer extends Thread{
	protected int port;
	protected ClientAccepter clientAccepter;
	protected List<ClientHandler> clientHandler;
	protected GameObject gameObject;
	public NetworkServer(int port, GameObject gameObject){
		this.port = port;
		this.clientHandler = new ArrayList<ClientHandler>();
		this.clientAccepter = new ClientAccepter(this);
		this.gameObject = gameObject;
	}
	public GameObject getGameObject(){
		return this.gameObject;
	}
	public void stopListen(){
		this.clientAccepter.stopListen();
		for(ClientHandler ch : this.clientHandler){
			ch.stopListen();
		}
	}
	@Override
	public void start(){
		this.clientAccepter.start();
		super.start();
	}
	public synchronized void send(byte[] bytes) throws IOException {
		for(ClientHandler ch : this.clientHandler){
			ch.send(bytes);
		}
	}
	public void addClientHandler(ClientHandler newClient){
		synchronized( this.clientHandler){
			this.clientHandler.add(newClient);
			newClient.start();
		}
	}
	public void removeClientHandler(ClientHandler deleteClient){
		synchronized( this.clientHandler){
			this.clientHandler.remove(deleteClient);
		}
	}
	public int getPort(){
		return this.port;
	}

	public int getClientCount(){
		return this.clientHandler.size();
	}
	
	public void closeStockets() {
		this.clientAccepter.stopListen();
		this.clientAccepter.closeSockets();
		try {
			this.clientAccepter.join();
		} catch (InterruptedException e1) {
			e1.printStackTrace();
		}
		for(ClientHandler ch : this.clientHandler){
			ch.stopListen();
			try {
				ch.join();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	public void sendCreateTileEvent(Tile tile){
		byte[] bytes = NetworkParser.packCreateTileEvent(tile);
		try {
			this.send(bytes);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	public void sendRemoveTileEvent(IntVector3 vec){
		byte[] bytes = NetworkParser.packRemoveTileEvent(vec);
		try {
			this.send(bytes);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	public void sendAddWayEvent(Way way){
		byte[] bytes = NetworkParser.packAddWayEvent(way);
		try {
			this.send(bytes);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	public void sendRemoveWayEvent(IntVector3 vec){
		byte[] bytes = NetworkParser.packRemoveWayEvent(vec);
		try {
			this.send(bytes);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	public void sendAddEntityEvent(Entity ent){
		byte[] bytes = NetworkParser.packAddEntityEvent(ent);
		try {
			this.send(bytes);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	public void sendRemoveEntityEvent(int entID){
		byte[] bytes = NetworkParser.packRemoveEntityEvent(entID);
		try {
			this.send(bytes);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	public void sendEntityMoveEvent(Entity ent){
		byte[] bytes = NetworkParser.packEntityChangeDestinationEvent(ent);
		try {
			this.send(bytes);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}
```



Ich hoffe, dass mir jemand helfen kann, weil ich nun echt schon ewig hier hänge.


----------



## Tobse (23. Feb 2014)

Das 
	
	
	
	





```
synchronized (this.outputstream)
```
 sollte eigentlich schon reichen. Nun kann es aber sein, dass ein Socket mehrere OutputStream-Instanzen herausgibt. Ich habe jetzt mal meine Server-Codes ausgegraben die anfangs ein änliches Problem hatten. Dort ist es auf Client-Ebene so gelöst:


```
abstract class ServerProtocol
{
    private static Socket currentSocket;
    public static void doSomething() throws ProtocolException
    {
        synchronized (currentSocket)
        {
            out.write(...);
        }
    }
}
```

Auf dem Server wird auch für jeden Client ein neuer Thread gestartet der so ausschaut:

```
class UserHandler implements Runnable
{
    public void run()
    {
        try
        {
            while (!socket.isClosed())
            {
                byte opCode = in.read();
                switch (opCode)
                {
                    case OC_LOGIN:
                        doLogin();
                        break;
                    ...
                }
            }
        }
        catch (Excepiton ex) { ... }
    }
    private void doLogin() throws IOException
    {
        in.read(...);
    }
}
```

Alles was ich weiss ist, dass es funktioniert... Wenn ein 
	
	
	
	





```
synchronized(socket)
```
 bei dir auch nicht zieht würde sich mir die Frage stellen auf was für einer Maschine das läuft während es crashed. Wenn das eine Shared-VM ist würds mich nicht wundern...


----------



## Barista (23. Feb 2014)

Sorge einfach mal dafür, dass Du genau ein Socket und genau einen Stream hast.

Auf diesen kannst Du synchronisieren.

Shared-VM? Kenn ich nicht.


----------



## Tobse (23. Feb 2014)

Barista hat gesagt.:


> Sorge einfach mal dafür, dass Du genau ein Socket und genau einen Stream hast.
> 
> Auf diesen kannst Du synchronisieren.
> 
> Shared-VM? Kenn ich nicht.


Ähm, schon vergessen dass er mehrere User auf dem Server hat? Wie soll das denn mit nur einem Socket gehen?

Es gibt Server-Anbieter die Mehrere Server-Softwares in einer VM laufen lassen. Das funktioniert in der Theorie, die Praxis sieht da manchmal anders aus.


----------



## Barista (23. Feb 2014)

Besorg Dir mal ein Buch, ich habe hier

Java in verteilten Systemen



> Ähm, schon vergessen dass er mehrere User auf dem Server hat? Wie soll das denn mit nur einem Socket gehen?



Ja, dann ein einziger Serversocket aber mehrere Cient-Sockets mit jeweils genau einem Stream:


```
import java.io.DataInputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;


public final class ChatServer
{
    public ChatServer(
            final int port )
    throws IOException
    {
        final ServerSocket serverSocket = new ServerSocket( port );
        
        while ( true )
        {
            final Socket clientSocket = serverSocket.accept();
            
            final DataInputStream inStream = new DataInputStream( clientSocket.getInputStream() );
            
            final ChatHandler chatHandler = new ChatHandler( clientSocket );
            chatHandler.start();
        }
        
    }

    public static void main( 
            final String[] args )
    throws IOException
    {
        new ChatServer( Integer.parseInt( args[ 0 ] ) );
    }

}
```


import java.net.Socket;



```
public final class ChatHandler
extends Thread
{
    private final Socket clientSocket;

    /**
     * Konstruktor.
     * @param clientSocket
     */
    public ChatHandler(
            final Socket clientSocket )
    {
        this.clientSocket = clientSocket;
    }

}
```


----------



## xasz (23. Feb 2014)

Ich hab am Client genau einen Socket.
Am Server habe ich einen Serversocket und für jeden Client genau einen Socket.
Aus dem Socket wird dann genau ein InputStream und ein OutputStream erzeugt.

Wenn ich nur ein Stream erzeuge, habe ich ja nur Kommunikation in eine Richtung.


----------



## Barista (24. Feb 2014)

> Ich hab am Client genau einen Socket.
> Am Server habe ich einen Serversocket und für jeden Client genau einen Socket.
> Aus dem Socket wird dann genau ein InputStream und ein OutputStream erzeugt.
> 
> Wenn ich nur ein Stream erzeuge, habe ich ja nur Kommunikation in eine Richtung.



Und die send-Methode von weiter oben, ist die an einem Objekt, das *single* ist?


----------



## xasz (24. Feb 2014)

Das ist die Send-Methode aus dem Clienthandler.java, die wird für jeden Client am Serve einmal instanziert.


----------



## Barista (24. Feb 2014)

Das könntest Du zusätzlich mit

```
if(this.isSending)System.out.println("Das sollte nicht sein" + System.identityHashCode( this ) );
```
absichern.

Wenn dies nachweist, dass es sich immer um das selbe Objekt handelt,
dann würde ich mir mal das Protokoll ansehen.

Ist das Protokoll wirklich eindeutig, lässt keine Bytes übrig?


----------



## xasz (24. Feb 2014)

So ich bins mal wieder.
Ich hab wieder einiges getestet und rumgespielt.

Also ich glaube ich kann den Serverseitigen Fehler jetzt ausschließen. Das  syncronized scheint zu funktionieren, da hab ich einfach irgendwie verkuckt.

Mein Protokoll hab ich überprüft und keinen Fehler gefunden.

Jetzt war mein Ansatz folgender. Um einen Stresstest zu simulieren, habe ich einen Server mit 100K Entites erstellt.
Wenn ich jetzt mit meinem Client gejoint bin, ist er sofort gecrasht, weil er ein falsches Kontrollbyte bekommen hat.
Dann dachte ich mir, ok, ich lasse mir alle meine Bytes, die ich am Client empfange ausgeben und in eine Datei schreiben, dann kann ich später eventuell irgendwie ein Fehler nachvollziehen.
Meine Read Funktion am GameClient.java sieht nun wie folgt aus:


```
public void read(byte[] bytes) {
		try {
			this.inputStream.read(bytes);
			logger.log(java.util.logging.Level.INFO, NetworkParser.bytesToString(bytes));
		} catch (Exception e) {
			logger.log(java.util.logging.Level.WARNING, "Could not Read:"+NetworkParser.bytesToString(bytes));
			e.printStackTrace();
		}
	}
```

Mein Problem ist, es funktioniert einwandfrei. Sobald ich die log-Befehle auskommentiere crashed es wieder.
Für mich bedeutet das in irgendeiner weise.

Die read Funktion kann in meinem Verständnis nicht gleichzeitig aufgerufen werden. Sie wird nur innerhalb der listen-Funktion verwendet und wird doch nacheinander aufgerufen.

Meine Vermutung ist: Der Logger benötigt ja eine Gewisse Zeit zur Ausführung. Ich vermute, dass ich irgendwie schneller Lese, als es der Inputstream auf die Reihe bekommt, bei großen Mengen, mir die Bytes bereitzustellen. Aber wirklich ne Theorie hab ich auch nicht.

Um Meine Theorie zu testen hab ich folgendes gemacht:


```
public void read(byte[] bytes) {
		try {
			this.inputStream.read(bytes);
			Thread.sleep(1);
			//logger.log(java.util.logging.Level.INFO, NetworkParser.bytesToString(bytes));
		} catch (Exception e) {
			//logger.log(java.util.logging.Level.WARNING, "Could not Read:"+NetworkParser.bytesToString(bytes));
			e.printStackTrace();
		}
	}
```

Und siehe da: Es funktioniert.
Allerdings verstehe ich nicht warum.


----------



## Barista (24. Feb 2014)

> this.inputStream.read(bytes);



Dies ist keine korrekte Benutzung der Methode *read*, Du ignorierst einfach die Anzahl zurückgegebener Bytes.


----------



## Barista (24. Feb 2014)

```
int readedByteCount = 0;

while ( readedByteCount < bytes.length )
{
    readedByteCount +=
        this.inputStream.read(
                bytes ,
                readedByteCount ,
                bytes.length - readedByteCount );
}
```

Einfach mal so aus dem Handgelenk hingetippt, ungetestet.


----------



## xasz (24. Feb 2014)

Ich bin bisher davon ausgegangen, dass die read Funktion blocking ist, bis die entsprechenden Bytes ankommen.
Ich werde es später dann mit ner Schleife testen. Danke erstmal dafür.
Melde mich dann zurück.

Edit: Es scheint das Problem wirklich gelöst zu haben. Da hab ich wohl einfach beim Doku-Lesen versagt.
Ich danke euch für all eure Hilfe.


----------

