Netzwerkkomponente für Online-Spiel entwickeln

screaper

Mitglied
Hallo Jungs und Mädls,

ich muss ein Spiel programmieren, dass übers Netzwerk gespielt wird.
Und zwar ist der Ablauf so, dass abwechselnd gespielt wird. D.h. Spieler 1 macht einen Zug, dann Spieler 2, oder umgekehrt.

Wie implementiere ich das am besten?

Szenario:

Spieler 1 macht eine Eingabe. Diese wird erst auf der GUI umgesetzt und anschließend übers Netzwerk übertragen. Ab hier muss das gesamte Spiel blockieren und es dürfen keine Eingaben mehr gemacht werden.

Nun ist Spieler 2 am Zug. Der hat die Daten von Spieler 1 erhalten und diese wurden auf seiner GUI aktualisiert. Nun macht Spieler 2 eine Eingabe und dioese wird an Spieler 1 gesendet. Die GUI von Spieler 1 wird nun aktualisiert und er kann wieder eine Eingabe machen. usw.

Meine Frage nun, wie implemntiere ich die Netzwerkkomponente?
Muss ich die als Thread zur SpieleLogik laufen lassen? Oder kann sie einfach so mitlaufen, da die Netzwerkaufrufe ja "Synchron" sind, also request - block- replay.


Ich hab sowas noch nie gemacht. Gebt mir einfach mal eine Tipp.
Vielen Dank
Marco
 
Hallo Jungs und Mädls,

ich muss ein Spiel programmieren, dass übers Netzwerk gespielt wird.
Und zwar ist der Ablauf so, dass abwechselnd gespielt wird. D.h. Spieler 1 macht einen Zug, dann Spieler 2, oder umgekehrt.

Wie implementiere ich das am besten?

Szenario:

Spieler 1 macht eine Eingabe. Diese wird erst auf der GUI umgesetzt und anschließend übers Netzwerk übertragen. Ab hier muss das gesamte Spiel blockieren und es dürfen keine Eingaben mehr gemacht werden.

Nun ist Spieler 2 am Zug. Der hat die Daten von Spieler 1 erhalten und diese wurden auf seiner GUI aktualisiert. Nun macht Spieler 2 eine Eingabe und dioese wird an Spieler 1 gesendet. Die GUI von Spieler 1 wird nun aktualisiert und er kann wieder eine Eingabe machen. usw.

Meine Frage nun, wie implemntiere ich die Netzwerkkomponente?
Muss ich die als Thread zur SpieleLogik laufen lassen? Oder kann sie einfach so mitlaufen, da die Netzwerkaufrufe ja "Synchron" sind, also request - block- replay.


Ich hab sowas noch nie gemacht. Gebt mir einfach mal eine Tipp.
Vielen Dank
Marco

Da sich bei dir nur 2 Leute gleichzeitig verbinden, reicht das primitivste TCP Client/Server modell überhaupt.

Über die Klasse Socket kannst du eine Verbindung zu einem anderen Gerät herstellen (besser gesagt versuchen).

Bei dir reicht es wenn sich ein Spieler direkt zu einem anderen verbindet.

(Besser wäre ein drittes Programm welches als Server fungiert und das Spiel verwaltet/züge auswertet und dann die beiden Clients informiert....)

Ein Gerät kann einen ServerSocket erstellen und an einem Port horchen.
Die Methode accept() blockiert so lange bis eine TCP Verbindung eingeht und liefert wiederum ein Socket objekt.

Das andere Gerät verbindet sich wiederum mit einem Socket zu einem Server,...

Wenn die Verbindung steht musst du dir ein Konzept überlegen.

Weist du was Streams sind?

Das Internet, und vor allem dieses Forum ist voll mit Beispielen etc.
Onkel Google findet sofort nutzbares Material (natürlich in Englisch)

Erwähnenswert wäre, dass du sogar ganze Objekte (wenn man serialisiert als 'ganz' bezeichnen kann) übers Netz schicken kannst (ObjectInputStream, ObjectOutputStream)

Sehr lückenhaft, hat aber mir anfangs etwas geholfen:

http://www.informatik.uni-koeln.de/ls_speckenmeyer/teaching/ss07/progprak/ClientServerTut.pdf
 
Hihi,
danke für die Antwort,
aber das ist alles soweit klar. Ich weiß auch wie ich Sockets erstelle und wie ich mich mit anderen verbinde.

Mir geht es darum, wie ich es designtechnisch elegant lösen kann. Einen Server brauch ich nicht extra, der muss mit rein. Es wird dann gecheckt, ob ein Server existiert oder nicht, wenn nciht, fungiert der erste Spieler als Server.

Ich weiß nur nicht, ob die Netzwerkkomponente als Thread mitlaufen muss, oder nicht.
 
Hihi,
danke für die Antwort,
aber das ist alles soweit klar. Ich weiß auch wie ich Sockets erstelle und wie ich mich mit anderen verbinde.

Mir geht es darum, wie ich es designtechnisch elegant lösen kann. Einen Server brauch ich nicht extra, der muss mit rein. Es wird dann gecheckt, ob ein Server existiert oder nicht, wenn nciht, fungiert der erste Spieler als Server.

Ich weiß nur nicht, ob die Netzwerkkomponente als Thread mitlaufen muss, oder nicht.

Du wirst zu gar nichts gezwungen.
Wenn du willst kannst du auch den Netzwerk Code direkt in deine Spiele-logic integrieren.

-Spieler macht Zug-
-Sende Befehl/String/Zahl/Objekt/whatever an den Partner-
-Warte auf Antwort vom Partner-
-Verarbeite Antwort vom Partnet (seinen Zug)-
-Mache naechsten Zug-
-Sende an Partner-

usw und sofort.

Wenn du das ganze also direkt in den GUI code schreibst friert die GUI bis der Netzwerk-kram fertig ist, ein.

Wenn du jetzt auf die Antwort wartest, kann das lange bis ewig dauern (wenn es kein Timeout gibt).

Du hast auch keinen Einfluss darauf. Du kannst nicht eingreifen, weil ja alles eingefroren ist.

Quick and dirty arbeiten solltest du dir nicht angewöhnen.
Natürlich kannst du es so machen, aber ich rate es dir nicht.

Du könntest einen Netzwerk-handler schreiben der einzelne Threads startet, die Sende und Empfangs-schleifen haben und je nach dem reagieren.

Wie gesagt, es gibt im Internet genug Literatur.

mfg
Filip (Martin C. Caesar)
 
Hallo ich nochmal...

ich komme nicht vorwärts!


Ich kann immer noch kein Zwiegespräch führen...


Java:
public void run(){
	
		try{
			
			socket = new Socket(adresse,port);
			socket.setSoTimeout(0);
				
			output = new DataOutputStream(socket.getOutputStream());
			input = new BufferedReader(new InputtStreamReader(socket.getInputStream()));
			inputUser = new BufferedReader(new InputStreamReader(System.in));
			isConnected=true;
			String s;
			

                while(isConnected){
													
			s = inputUser.readLine();
			output.writeBytes(s+"\n");	
			lineInput=(input.readLine());
			System.out.println(lineInput);
				
		}
		}
		catch (IOException e){
			System.out.println(e);
		}
		finally {
			try{
				if(input!=null) input.close();
				if(output!=null)output.close();
				if(socket!=null)socket.close();
			}
			catch(IOException ex) {}
			}
			
	}

Der Server gibt auf unterschieldiche Anfrage unterschiedliche Längen aus. Also nicht nur eine Zeile sondern mal 1 mal 4 oder gar 20.

Ich will: Komando hin -> Komplette Antwort erhalten -> Nächster Befehl hin usw.
Das ganze in einer Endlosschleife, bis Connected=false.
Die SendeBefehle werden von einer anderen Klasse aufgerufen und die Recveives werden an eine andere Klasse weitergereicht und dort ausgewertet.

Die Manuelle eingabe des Strings der gesenedet werden soll, wird dann noch durch eine Methode ersetzt.

Ich habe schon mehrere Stunden gegoogelt aber nichts gefunden. Nur Chat und einmalabfragen, die einzeilig waren und die Verbindung danach beendet wurde. Was aber hier nciht der Fall sein darf.

Bitte nochmal ein zwei Tipps. Gerne auch Links mit entsprechenedm Code-Beispiel!


Danke
Screaper
 
Damit müsste dir eine Kommunikation gelingen. Habe gerade keinen Editor an der Hand und deshalb könnte man die Klasse hier und da noch verbessern. Könnte also auch sein, dass der eine oder andere Fehler drin ist.

Java:
import java.net.*;
import java.io.*;

TCPConnection{
  private Socket mySocket = null;
  private InputStream in = null;
  private OutputStream out = null;
  private boolean isConnected = false;

  TCPConnection (Socket s) throws IOException {
    this.mySocket = s;
    this.in = this.mySocket.getInputStream();
    this.out = this.mySocket.getOutputStream();
    isConnected = true;
  }

  public void write(String text) {
    try {
      if(!isConnected)
        return; //oder mach etwas anderes
      out.write(text.getBytes());
      out.write(10); // out.write("\n".getBytes());
      out.fush();
    } catch (IOException e) {
      //Meldung an Spieler und Spiel eventuell beenden, da keine Verbindung mehr besteht
    }
  }

  public String read() {
    if(!isConnected)
      return null;
    String sDaten = "";
    try {
      for(int i; (i = in.read()) != 10; ) {
        if(i == -1)
          throw new IOException("Connection reset.");
        sDaten += (char)i;
      }
    } catch (IOException e) {
      //Meldung an Spieler und Spiel eventuell beenden, da keine Verbindung mehr besteht
    }
    return sDaten;
  }
}
 
Zuletzt bearbeitet:
Danke dir für den Code,

aber leider macht der exakt das was meiner auch macht. Nämlich nur eine Zeile vom Server einlesen.
Ich muss aber beliebig viele (vorher nicht vorhersagbar viele) Zeilen vom Server zurückbekommen. UNd auch beliebig viele an ihn senden.

Bei Connection sendet der Server z.B: einen Begrüßungstext. Der ist 5 Zeilen lang.
dann bekomme ich je nach Anfrage unerschiedlich viele Zeilen gesendet.

Wie kann ich nun den Server "ausreden" lassen, ohne dass ich irgendeine Taste drücken muss (dass er mir die nächste Zeile anzeigt), oder ich die Anzahl der Antwortzeilen im voraus kenne muss?

Ich hatte es mal mit einer While-Schleife versucht, aber das ging in die Hose....



PS: Der Server läuft bereits und ich will nur eine Clientanwendung dazu entwickeln.
 
Hm ich weiß auch nicht. Dann muss der Fehler irgendwo in deinem Code sein. Rufst du irgendwo die close() Methode eines Input-/Outputstreams oder eines Sockets auf, bevor die Verbindung beendet werden soll.
Ich habe hier im Moment leider keinen PC mit Java + Editor zur verfügung. Bin aber übers Wochenede wieder zuhause. Ich kann dir dann mal morgen ne simple Client/Server-Anwundung schreiben. Um es uns leichter zu machen kannst du uns auch mal deinen bisherigen Code (oder den Code, wo du den Fehler vermutest) postest.

Was mir gerade noch dazu einfällt:
Wenn du einen mehrzeiligen Text als ein Datensatz senden willst, dann musst du dir eine eigene Datenstruktur überlegen. Der BufferedReader würde einen Datensatz mit mehreren Zeilen mit der "readLine()"-Methode bei jedem Absatz (\n) teilen. Heiß ja auch "lese Zeile". Der Absatz (\n) kann aber unter Umständen sogar aus 2 Bytes (0x0A und 0x0D) bestehn. Um mit meiner oben geposteten Klasse Datensätze an einem Stück zu halten könntest du alle Absätze (\n) des Strings, der der write()-Methode übergeben wird durch den Byte 0x0D ersetzen (String.replace("","")) und nach dem Einlesen in der read()-Methode das ganze wieder rückgängig machen. Somit hättest du ein eindeutiges Byte für einen Absatz und ein eindeutiges Byte für das Ende eines Datensatzes.

Könnte also so aussehen:

Java:
import java.net.*;
import java.io.*;

TCPConnection{
  private Socket mySocket = null;
  private InputStream in = null;
  private OutputStream out = null;
  private boolean isConnected = false;

  TCPConnection (Socket s) throws IOException {
    this.mySocket = s;
    this.in = this.mySocket.getInputStream();
    this.out = this.mySocket.getOutputStream();
    isConnected = true;
  }

  public void write(String text) {
    String sAbsatz = String.valueOf((char)0x0d));
    text = text.replace("\n", sAbsatz);
    try {
      if(!isConnected)
        return; //oder mach etwas anderes
      out.write(text.getBytes());
      out.write(10); // out.write("\n".getBytes());
      out.fush();
    } catch (IOException e) {
      //Meldung an Spieler und Spiel eventuell beenden, da keine Verbindung mehr besteht
    }
  }

  public String read() {
    if(!isConnected)
      return null;
    String sDaten = "";
    try {
      for(int i; (i = in.read()) != 10; ) {
        if(i == -1)
          throw new IOException("Connection reset.");
        sDaten += (char)i;
      }
      String sAbsatz = String.valueOf((char)0x0d));
      text = text.replace(sAbsatz, "\n");
    } catch (IOException e) {
      //Meldung an Spieler und Spiel eventuell beenden, da keine Verbindung mehr besteht
    }
    return sDaten;
  }
}
 
Hi Super,

danke für Deine Hilfe. Dann warte ich mal aufs Wochenende!

Ich hab einfach deinen Code zu 100% übernommen und ausprobiert, ob er das macht was ich will. Und er liest eben auch nur eine Zeile vom Server. UNd fertig.
Ich muss quasi 5 x Readline aufrufen um alles zu erhalten.

Der Server beendet jede Zeile mit "\r\n". Dann kommt die nächste usw.
Irgendwann ist die Ausgabe des Kammeraden beendet. Aber wie kann ich feststellen, wann?
Er gibt also Zeilenweise aus.
Wenn ich jetzt Zeilenweise einlese mit Readline, dann muss ich dies ja mehrmals ausführen. UNd das muss ja quasi in einer Whileschleife passieren, oder

while (noch nicht fertig) readline......

Nur wie definiere ich fertig!?

Wenn ich while (true) realdine mache, erhalte ich logischerweise alle zeilen, aber ich kann nichts mehr machen, weil das Programm blockiert.


Wenn du mir ein funktionfsähiges dingen schicken könntest wäre cool.
Ich geb dir mal die Serverdaten, damit du es ausprobieren kannst.

IP: "sirius.fernuni-hagen.de" Port: 4713

Wenn du Verbunden bist, sagt er Welcome, blablabl,.... und gibt dir 5 oder 6 Zeilen Willkommensgesülze aus.

Dann kannst du ja mal "help" eingeben, bzw. senden lassen und dann müsstest du eine auflistung (ca: 20 - 30 Zeilen von allen funktionen bekommen. (Die dir zwar nichts sagen, aber das is ja für den Versuch wurscht).

Gruß Screaper
 
Hi. Ich habe jetzt den Code einer Server-Client-Anwendung fertig.
Damit kann ich problemlos mehrzeiligen Text(mehrzeilige Daten) an einem Stück senden bzw. lesen (nur ein mal "read()" aufrufen). Das "Signal" für das Ende eines Datenblocks gibt die Hex-Zahl 0x03 an (um auf deine Frage zu antworten, wie man das Ende eines Spielzugs mitteilen kann). D.h. dass in deinem Programm dieses Byte nicht als Daten vorkommen darf.

Ich denke es wird dir bei deinem Serer weiterhelfen. Als beispiel habe ich einen Server programmiert, der zunächst einen mehrzeiligen Text als begrüßung sendet und danach wechseln sich Server und Client mit der Kommunikation ab.

Java:
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;


public class TCPConnection {
	private Socket mySocket = null;
	private InputStream in = null;
	private OutputStream out = null;
	private boolean isConnected = false;
	
	public TCPConnection(Socket s) throws IOException {
		this.mySocket = s;
		this.in = this.mySocket.getInputStream();
		this.out = this.mySocket.getOutputStream();
		this.isConnected = true;
	}
	
	public void close() {
		if(!this.isConnected)
			return;
		this.isConnected = false;
		try { this.in.close(); } catch (IOException e) {}
		try { this.out.close(); } catch (IOException e) {}
		try { this.mySocket.close(); } catch (IOException e) {}
	}
	
	public void write(String data) throws IOException {
		out.write(data.getBytes());
		out.write(0x03);
		out.flush();
	}
	
	public String read() throws IOException {
		StringBuffer buffer = new StringBuffer();
		for(int i; (i = in.read()) != 3; ) {
			if(i == -1) {
				this.close();
				throw new IOException("Connection refused.");
			}
			else {
				buffer.append((char)i);
			}
		}
		
		return new String(buffer);
	}
	
	public Socket getSocket() {
		return this.mySocket;
	}
}


Java:
import java.io.IOException;
import java.net.ServerSocket;

public class Server {
	public static void main(String[] args) throws IOException {
		ServerSocket serverSocket = new ServerSocket(65000);
		String wellcomeMessage = "Hallo!\nDies ist ein mehrzeiliger Text.\nEs folgt nun eine wechselseitige Kommunikation:\n\n";
		while(true) {
			TCPConnection tcpConnection = new TCPConnection(serverSocket.accept());
			tcpConnection.write(wellcomeMessage);
			for(int i = 1; i <= 10; i++) {
				tcpConnection.write("Server counter: " + i);
				System.out.println(tcpConnection.read());
			}
			tcpConnection.write("Machs gut Client '" + tcpConnection.getSocket().getInetAddress().getHostAddress() + "'.");
			tcpConnection.close();
		}
	}
}


Java:
import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;

public class Client {
	public static void main(String[] args) throws UnknownHostException, IOException {
		TCPConnection tcpConnection = new TCPConnection(new Socket("127.0.0.1", 65000));
		System.out.println(tcpConnection.read());
		int counter = 0;
		while(true) {
			String sInput = tcpConnection.read();
			System.out.println(sInput);
			if(sInput.startsWith("Machs"))
				break;
			counter++;
			tcpConnection.write("Client counter: " + counter);
		}
		tcpConnection.close();
	}
}


Grüße d4rkY89
 
Zurück