# Lags bei Netzwerkspiel.



## Digedag (14. Apr 2007)

Hi.

Ich versuche gerade ein Pong - Spiel mit Onlinemodus zu erstellen. Das Problem ist, daß bei dem Spiel ziemlich starke Lags auftreten. Ich wüßte aber nicht an welcher Stelle man die Datenübertragung schneller machen könnte.

Im Optimalfall wäre kein Unterschied bei der Bewegung zwischen den Clienten erkennbar. Bei meinem Programm treten aber deutliche Lags auf. Hat jemand eine Idee wie man diese Lags verkleinern könnte?

Hier die Quellcodes von Client und Server.


```
import java.applet.*;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.awt.*;
import java.awt.event.*;
 
public class Pong extends Applet implements Runnable, KeyListener
{ 
	static final long serialVersionUID = 0;
	Socket socket = null;
	PrintWriter out = null;
	BufferedReader in = null;
	int x[] = new int[3];//X-Koordinate fuer Ball, Schiff 1 und Schiff 2
	int y[] = new int[3];//Y-Koordinate fuer Ball, Schiff 1 und Schiff 2
	int newPos[] = new int[3];//Speichert ueber Netzwerk vermittelte Positionnsveraenderungen
	int move = 0;//fuer Bewegungsrichtung des lokal gesteuertaeen Schiffes
	//boolean up, down, started  = false;
	Thread th = new Thread (this);//Thread fuer implementirte Runnable Funktion run()
	String s;
	
	private void getConnection(int i,int j, int k, int l){
		byte[] addr = new byte[4];
		  addr[0] = new Integer ( i ) .byteValue (  ) ;
		  addr[1] = new Integer ( j ) .byteValue (  ) ;
		  addr[2] = new Integer ( k ) .byteValue (  ) ;
		  addr[3] = new Integer ( l ) .byteValue (  ) ;
		  InetAddress IP;
		     try{
		   	  IP = InetAddress.getByAddress(addr);
		       socket = new Socket(IP, 4444);
		       out = new PrintWriter(socket.getOutputStream(), true);
		       in = new BufferedReader(new 
		InputStreamReader(socket.getInputStream()));
		     } catch (UnknownHostException e) {
		       System.out.println("Unknown host: "+e);
		       System.exit(1);
		     } catch  (IOException e) {
		       System.out.println("No I/O: "+e);
		       System.exit(1);
		     }
	}

	private void bewegen(){newPos[2]=Integer.parseInt(s.substring(1,4))-100;}
	
	//private void vektor(){}
	
	//private void neu(){}
	
	public void init(){
		getConnection(217,230,244,50);//Verbindung mit Server aufbauen (meine IP)
		setBackground(Color.blue);//Hintergrundfarbe :O
		x[0]=445;y[0]=195;//Startpositionen
		x[1]=0;y[1]=165;
		x[2]=885;y[2]=165;
		try{
			  s = in.readLine();//Wartet auf Startsignal (sobald sich zwei Spieler eingeloggt haben gehts los)
			  }
			  catch (Exception e)
			  {
				  System.out.println(e);
			  }
		addKeyListener(this);
		Runnable Empfang = new Runnable() 
		{ 
		  public void run() 
		  { 
			  while(true){//Endlosschleife
				  try{
					  s = in.readLine();//wartet auf Daten...
					  }
					  catch (Exception e)
					  {
						  System.out.println(e);
					  }
					  if(s.substring(0,1).equals("b"))bewegen();
					  //if(s.substring(0,1).equals("v"))vektor();
					  //if(s.substring(0,1).equals("n"))neu();
			  }
		  }
		};
		new Thread( Empfang ).start();//Empfangsthread starten
		th.start();//Zeichenthread starten
	}
	
	public void start(){}

	public void run(){
			while(true){
			if((move==1)&&(y[1]<310))y[1]+=10;//Schiff nach oben bewegen
			if((move==2)&&(y[1]>19))y[1]-=10;//Schiff nach unten bewegen
			if (newPos[1]!=y[1]){out.println(y[1]+100);out.flush();}//falls sich die Position seit letztem Durchlauf veraendert hat
			newPos[1] = y[1];
			try
			{
				repaint(0,y[1]-10,15,91);
				Thread.sleep (12);
				if(y[2]<newPos[2])y[2]+=10;
				if(y[2]>newPos[2])y[2]-=10;
				repaint(885,y[2]-10,15,91);
				Thread.sleep (12);
			}
			catch (InterruptedException e)
			{
			      System.out.println(e);
			}
		}
	}
	
	public void keyPressed(KeyEvent e)
	{
		if(e.getKeyCode()==40){move=1;}//Schiff nach oben bewegen
		if(e.getKeyCode()==38){move=2;}//Schiff nach unten bewegen
	}

	public void keyReleased(KeyEvent e)
	{
			move=0;//Schiff bewegt sich nicht
	}

	public void keyTyped(KeyEvent e){}
	
	public void paint (Graphics g){
		g.fillOval(x[0],y[0],10,10);//Ball
		g.fillRect(x[1],y[1], 15, 71);//Schiff 1
		g.fillRect(x[2],y[2], 15, 71);//Schiff 2 
		g.fillRect(0,0, 900, 15);//oberer Begrenzungsbalken
		g.fillRect(0,385, 900, 15);//unterer Begrenzungsbalken
	}

}
```


```
import java.io.*;
import java.net.*;

class SocketServer{
	
	static final long serialVersionUID = 0;
	ServerSocket server = null;
	Socket client[] = new Socket[2];//2 Sockets fuer 2 Clienten
	BufferedReader in[] = new BufferedReader[2];//Eingabestroeme
	PrintWriter out[] = new PrintWriter[2];//Ausgabestroeme
	
	Runnable Thread_Client1 = new Runnable() 
		{
		public void run()
		{ 
		String s = null;
		while(true){
		try{
			//Daten in Empfang nehmen
			s = in[1].readLine();
			//Daten weiterleiten
			out[0].println("b"+s);
			//Puffer leeren
			out[0].flush();
		}
		catch (Exception e){
			System.out.println(e);
			System.exit(0);
		}
		}}};

		Runnable Thread_Client2 = new Runnable() 
		{
		public void run()
		{ 
		String s = null;
		while(true){
		try{
			//Daten in Empfang nehmen
			s = in[0].readLine();
			//Daten weiterleiten
			out[1].println("b"+s);
			//Puffer leeren
			out[1].flush();
		}
		catch (Exception e){
			System.out.println(e);
			System.exit(0);
		}
		}}};
		  
  public void listenSocket(){
	  //Verbundung mit Clients aufnehmen
	  int i = 0;
    try{
      server = new ServerSocket(4444); 
    } catch (IOException e) {
      System.out.println("Could not listen on port 4444");
      System.exit(-1);
    }
    while(i<2){
    try{
      client[i] = server.accept();
    } catch (IOException e) {
      System.out.println("Accept failed: 4444");
      System.exit(-1);
    }
    try{
      in[i] = new BufferedReader(new InputStreamReader(client[i].getInputStream()));
      out[i] = new PrintWriter(client[i].getOutputStream(), true);
    } catch (IOException e) {
      System.out.println("Accept failed: 4444");
      System.exit(-1);
    }
    i++;
	  }
    //Clients signalisieren, dass das Spiel startet
    out[1].println("");out[0].println("");
    new Thread(Thread_Client1).start();
    new Thread(Thread_Client2).start();
  }

  protected void finalize(){
	  //aufraeumen
     try{
        in[0].close();
        out[0].close();
        in[1].close();
        out[1].close();
        server.close();
    } catch (IOException e) {
        System.out.println("Could not close.");
        System.exit(-1);
    }
  }

  public static void main(String[] args){
        SocketServer frame = new SocketServer();
        frame.listenSocket();
  }
}
```
Zum besseren Verständnis habe ich einige Kommentare eingefügt. Falls jemand die Programme testen will: Das Clientprogramm muß 2x gestartet werden, da der Server 2 Clienten erwartet.

Ich habe das Programm inzwischen so verändert, daß die Clienten die Lags kaum noch spüren. Allerdings treten die Lags trotzdem auf und das kann zu Problemen bei der Synchronisation führen wenn die Clienten nicht wissen, welches die Tatsächliche Position des jeweils anderen Schiffes ist.

Falls jemand sich selbst von den Lags überzeugen will muß er das Serverprogramm starten und das Clientprogramm auf seine eigene IP abgleichen. Die Lags lassen sich sehr gut verfolgen, wenn man das Clientprogramm 2x in zwei verschiedenen Appletviewern ausführt.

Wäre echt super wenn mir jemand helfen könnte. 

P.S.: Die Steuerung hakt momentan etwas. Ich habe sie zu Verständniszwecken etwas vereinfacht.


----------



## schalentier (14. Apr 2007)

Ich will dir ja net die gute Laune verderben, aber so wird das nix.

Hier hab ich vor ner Weile schonmal was bzgl. Spieleprogrammierung gepostet: http://www.java-forum.org/de/viewtopic.php?t=46295&highlight=

In deinem Client-Loop sind 2 sleeps? Wozu? Is doch klar, dass dann irgendwann alles Out-Of-Sync laeuft... das muss weg!

Du hast ein Netzwerk. Das lagt _immer_, denn die Packete brauchen nunmal ein paar Millisekunden von A nach B.

Wozu brauchst du in deinem Fall einen Server? Der ist ueblicherweise dazu da, um die Logik, die Physik, etc zu berechnen und die neuen Positionen von Objekten der Welt an die Clients zu verteilen. Bei dir macht er nix anderes als von A zu empfangen und an B weiterzuleiten. Also koennte A auch direkt an B senden. Damit hast du den Zeitverlust im Netzwerk schonmal halbiert.

Am Rande: Du machst dir im Server 2 Threads, mit 2 Innerclasses, die nahezu gleich sind. Das sollte auf jedenfall zu einer Klasse werden, von der du dir mehrere Instanzen erzeugst. Merke: DRY!! (Dont repeat yourself).

Du verwendest TCP/IP, oder? TCP/IP im LAN is super, uebers Internet fuer ein Spiel... da muss viel mehr Gehirnschmalz rein, denn TCP/IP garantiert dir zwar, dass alles in der richtigen Reihenfolge ankommt (und alles ueberhaupt ankommt), _aber_ es lagt teilweise extrem! Die bessere Wahl sind dabei immer Datagram-Sockets, die ohne Versicherung auskommen, dafuer schnell wie die Sau sind. Da musst du natuerlich selber pruefen, ob die Packete in der korrekten Reihenfolge ankommen und dir genau ueberlegen, was du wie uebertraegst (und wann du Pruefpackete, Syncs, etc verschickst). 

Du siehst, das ist nicht einfach. Mein Tipp: Mach dir zuerstmal irgendwas, was problemlos mit Lags funktioniert und bau dir dort eine Netzwerklib zusammen (schick OO, mehrere Layers (Transport, Checks, Lag-Rausrechnen, etc)). Z.B. ein Malprogramm uebers Netz. Also so, dass man anderen zuguggen kann, was sie in ein JPanel malen. Wenn du das dann lagfrei hinbekommen hast, kannste dich wieder einem Spiel zuwenden (und dabei die Netzwerkroutinen weiterverwenden). 

Viel Spass dabei...


----------



## Digedag (14. Apr 2007)

Also erstmal vorweg: Bei dem Spiel geht es mir weniger um das tolle Spiel sondern mehr um die Netzwerkfunktion. Pong war halt das Billigste was mir eingefallen ist. 



> Hier hab ich vor ner Weile schonmal was bzgl. Spieleprogrammierung gepostet: http://www.java-forum.org/de/viewtopic.php?t=46295&highlight=


Naja, ich habe das auch ungefähr so gemacht wie du es in dem Thema beschrieben hast. Bloß mit ein paar weniger Prozessen.



> In deinem Client-Loop sind 2 sleeps? Wozu? Is doch klar, dass dann irgendwann alles Out-Of-Sync laeuft... das muss weg!


Ich habe die sleeps jetzt mal verkleinert und eine Zählschleife in die Client-Loops eingebaut. Meinem Gefühl nach läuft das jetzt schon etwas flüssiger.
Ich verstehe allerdings nicht so ganz wie du das meinst. Soll ich das Spiel ganz ohne sleep schreiben oder sind sie nur an der besagten Stelle unpassend?



> Du hast ein Netzwerk. Das lagt immer, denn die Packete brauchen nunmal ein paar Millisekunden von A nach B.


Das Problem ist ja nicht, daß es laggt. Sondern wieviel es laggt (>150ms definitiv).



> Wozu brauchst du in deinem Fall einen Server?


Da es sich bei meinem Spiel um ein unsigniertes Applet handelt, können die Clienten nicht direkt miteinander kommunizieren. Sie können nur mit dem Heimserver eine Verbindung aufbauen.
Außerdem möchte ich später vielleicht noch Eigenschaften einbauen, für die ein Server unabwendbar ist.



> Am Rande: Du machst dir im Server 2 Threads, mit 2 Innerclasses, die nahezu gleich sind. Das sollte auf jedenfall zu einer Klasse werden, von der du dir mehrere Instanzen erzeugst. Merke: DRY!! (Dont repeat yourself).


Das war auch mehr so quick and dirty.  Damit haste natürlich Recht.



> Du verwendest TCP/IP, oder? TCP/IP im LAN is super, uebers Internet fuer ein Spiel... da muss viel mehr Gehirnschmalz rein, denn TCP/IP garantiert dir zwar, dass alles in der richtigen Reihenfolge ankommt (und alles ueberhaupt ankommt), aber es lagt teilweise extrem! Die bessere Wahl sind dabei immer Datagram-Sockets, die ohne Versicherung auskommen, dafuer schnell wie die Sau sind. Da musst du natuerlich selber pruefen, ob die Packete in der korrekten Reihenfolge ankommen und dir genau ueberlegen, was du wie uebertraegst (und wann du Pruefpackete, Syncs, etc verschickst).


Hmm ok. Gut zu wissen. Ich werde mich mal über Datagram Sockets informieren.



> Du siehst, das ist nicht einfach. Mein Tipp: Mach dir zuerstmal irgendwas, was problemlos mit Lags funktioniert und bau dir dort eine Netzwerklib zusammen (schick OO, mehrere Layers (Transport, Checks, Lag-Rausrechnen, etc)). Z.B. ein Malprogramm uebers Netz. Also so, dass man anderen zuguggen kann, was sie in ein JPanel malen. Wenn du das dann lagfrei hinbekommen hast, kannste dich wieder einem Spiel zuwenden (und dabei die Netzwerkroutinen weiterverwenden).


Ja, aber Malprogramme sind langweilig.  
Ich gebe zu, daß dieses Pong Programm schon ordentlich Zeit in Anspruch genommen hat. Trotzdem bleibe ich vorerst dabei, glaube ich. Wenn auf Anhieb so gar nichts funktioniert ist das kein Problem. Ich hab Geduld. 
Was noch ganz interessant zu realisieren wäre ist ein Brettspiel oder ein Chat. Halt etwas bei dem es nicht so sehr auf Synchronisation ankommt.

@schalentier: Ich bedanke mich für die ausführliche Antwort. Du hast ja schon einiges erläutert was man an dem Spiel verbessern kann. Ich hoffe damit klappt es dann besser. 

EDIT: Thema kann übrigens abgehakt werden. schalentier dürfte alles Nennenswerte gesagt haben.


----------

