PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Java: Objekte im Netzwerk schicken


Sliver21
2007-06-29, 09:05:45
Hallo Jungs,
mittlerweile sind wir bei unserem Spiel in der Endphase angelangt. Es fehlt nur noch die Server und Client Applikation. Dabei haben wir gerade noch ziemliche Probleme. Wir müssen von der Client Seite aus Objekte schicken, die der Server empfängt, auswertet und je nach dem, was bei ihm ankam, ein anderes Objekt schicken.
Kennt jemand von euch ein einfaches Beispiel, wo das realisiert wurde? Der Client und der Server müssen Objekte senden und empfangen können.
Wir saßen an diesem Problem gestern stundenlang.

Ich freue mich über eine Antwort!

Monger
2007-06-29, 09:31:11
Schau dir doch mal dieses Sun Tutorial (http://java.sun.com/docs/books/tutorial/networking/datagrams/index.html) an. Das Beispiel was die da machen dreht sich zwar erstmal um Strings, aber du kannst DatagramPackets im Prinzip mit allem möglichen befüllen. Du musst halt nur erstmal ein Objekt serialisieren (z.B. über einen passenden Input Stream), um dein Byte Array zu bekommen.

Du musst auf der Gegenseite wieder genauso dein Objekt aus dem Byte Stream zusammenbasteln wie du es reingesteckt hast. Sowohl dein Client als auch dein Server müssen also auf jeden Fall über die selbe Klasse verfügen.

Eine etwas intelligentere Lösung ist RMI (http://java.sun.com/docs/books/tutorial/rmi/index.html), aber das kommt darauf an, was du wirklich brauchst.

Sliver21
2007-06-30, 14:23:01
Danke schon mal für die Antwort. Datagramme wollen wir aus folgendem Grund nicht verwenden: "A datagram is an independent, self-contained message sent over the network whose arrival, arrival time, and content are not guaranteed." - Sun -

Im Internet habe ich folgendes Beispiel gefunden:


/*
* Created on 09.01.2005@13:22:14
*
* TODO Licence info
*/
package de.tutorials;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;

/**
* @author Administrator
*
* TODO Explain me...
*/
public class ObjectStreamExample {

public static void main(String[] args) {
new ObjectStreamExample().doIt();
}

/**
*
*/
private void doIt() {

Thread server = new Thread() {
public void run() {
try {
ServerSocket ss = new ServerSocket(2017);
Socket s = ss.accept();

ObjectInputStream ois = new ObjectInputStream(
new BufferedInputStream(s.getInputStream()));

Object o = ois.readObject();
System.out.println(o);

ois.close();

s.close();

ss.close();

} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

}
};

Thread client = new Thread() {
public void run() {
try {
Socket s = new Socket("localhost", 2017);
Serializable mto = new MyTransferObject();

ObjectOutputStream oos = new ObjectOutputStream(
new BufferedOutputStream(s.getOutputStream()));

oos.writeObject(mto);
oos.flush();
oos.close();

s.close();

} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}

}
};

server.start();
client.start();

}

}


Da werden ein Server- und ein Client-Thread erzeugt und dann ein Objekt vom Client zum Server geschickt. Wie muss man dieses Beispiel umschreiben, damit beide Objekte verschicken und empfangen können? Beispiel: Der Client sendet eine Anfrage auf bestimmte Daten und wartet dann, bis der Server ihm dieses rüberschickt. So in der Art soll die Kommunikation stattfinden.
Bisher haben wir es so gemacht. Zuerst wird der Server gestartet und für jeden Client, der sich beim Server anmeldet, startet der Server einen neuen Thread, der nur mit diesem Client kommuniziert. Gerade bei dieser Kommunikation haben wir Probleme.

Monger
2007-06-30, 15:39:30
Du musst deinen Thread ein kleines bißchen später anfangen. Das sieht sinngemäß ungefähr so aus:


ServerSocket ss = new ServerSocket(2017);
while(true){
s = ss.accept();
new WorkerThread(s);
}

Deine Main macht also nichts anderes, als Anfragen auf diesem Port aufzufangen, und dafür jeweils eigene Threads aufzumachen.

Damit beide senden um empfangen können, müssen beide Server und Client sein - und natürlich auf unterschiedliche Ports hören. Ich frage mich allerdings, wozu du das brauchst. Normalerweise sind Spiele reine Server/Client Architekturen: die Clients fragen, der Server antwortet. Was muss denn den Server unbedingt von den Clients wissen?

Im übrigen: Datagramme sind derzeit bei fast allen Spielen Standard. Da wird fast ausschließlich das verbindungslose UDP Protokoll verwendet, was aufgrund des niedrigeren Overheads auch geringfügig fixer ist. Du darfst dann halt keine Zustandsänderungen, sondern immer nur den Zustand an sich übermitteln. Bei Spielfiguren also nicht die Laufgeschwindigkeit, sondern die aktuelle Position. Das kann im schlimmsten Fall mal zu Rucklern führen, vermeidet aber gleichzeitig Synchronisationsprobleme.

Shink
2007-06-30, 16:01:03
Ich frage mich allerdings, wozu du das brauchst. Normalerweise sind Spiele reine Server/Client Architekturen: die Clients fragen, der Server antwortet. Was muss denn den Server unbedingt von den Clients wissen?
Hmm... hast du jetzt wirklich nachgedacht?

Client muss seine Anfrage senden, Server seine Antwort und umgekehrt müssen sie auf sich hören.
Und das geht ja auch über einen Port, wenn man ihn nicht schließt.
Soll heißen: Der "Server"-Thread kann ein s.getOutputStream() machen und auf diesen dann senden, bevor er ihn schließt.

Monger
2007-06-30, 16:10:17
Ich glaub, ich bin hier nur wiedermal sprachlich gestolpert, deshalb jetzt nochmal.

Server/Client heißt ja: Der Client will irgendwas wissen, schickt also an den Server eine Anfrage. Der akzeptiert dann die Verbindung, verarbeitet die Anfrage, schickt die Antwort zurück, und schließt die Verbindung wieder (sofern verbindungsorientiert).

Die Frage ist also: warum sollte denn der Server selbstständig Anfragen an den Client stellen können? Normalerweise ist das nicht sein Job.

Shink
2007-06-30, 18:04:28
Die Frage ist also: warum sollte denn der Server selbstständig Anfragen an den Client stellen können? Normalerweise ist das nicht sein Job.
Ich denke auch nicht, das das jemand wollte, aber egal.

@Sliver21: Der Ansatz klingt ja nicht so falsch. Im Vergleich zum Beispiel dürft ihr natürlich die Sockets des Servers nicht schließen, wenn ihr später noch etwas wollt und immer abwechselnd blockierend lesen und dann schreiben, aber ich denke das ist klar, oder?
Welches konkrete Problem gibt es denn?

Sliver21
2007-06-30, 21:11:23
Unser Spiel ist ein Autorennspiel. Wir müssen es nun so erweitern, dass netzwerkfähig ist, das heißt, jeder Client steuert ein Fahrzeug.

Erstmal zu unserem Aufbau:

Auf dem Server finden alle Berechnungen statt. Er weiß immer, wo sich jedes Fahrzeug befindet. Die Clients schicken dem Server die KeyEvents, zum Beispiel: das Fahrzeug geht vom Zustand 'nicht beschleunigend' in den Zustand 'beschleunigend', weil der Spieler am Client die W-Taste betätigt. Auf dem Server befindet sich demnach auch die GameLoop.
Bei jedem neuen Frame schickt der Client dem Server eine Anfrage auf alle Koordinaten der Spielobjekte.

Zu Beginn muss der Client dem Server auch mitteilen, welches Fahrzeug er steuern will. Er kann zwischen 2 Typen wählen.

Für die Kommunikation muss auf der Client wie auch auf der Server Seite ein ObjectOutputStream (out) und ein ObjectInputStream (in) haben, oder??

Beispiel: Der Client sendet eine Anfrage auf ein Koordinatenpacket und wartet dann auf dieses. Wie setzt man das um? Etwa so?(Pseudocode):

out.writeObject(Anfrage);
Koordinatenpaket paket = in.readObject();

Ist das so richtig?

Edti:
Jetzt habe ich schon die erste konkrete Frage. Ist das hier korrekt?
Server:

public class Server implements Runnable{

private ServerSocket ss;
private Socket s;
private ObjectInputStream ois;
private ObjectOutputStream oos;

private boolean laufend = true;

public void run() {

try
{
ss = new ServerSocket(2017);
s = ss.accept();

ois = new ObjectInputStream(new BufferedInputStream(s.getInputStream()));
oos = new ObjectOutputStream(new BufferedOutputStream(s.getOutputStream()));
...


Client:

public class Client implements Runnable{

private Socket s;
private ObjectOutputStream oos;
private ObjectInputStream ois;

private int zaehler=0;
private boolean laufend = true;

public void run() {
try {
s = new Socket("localhost", 2017);
oos = new ObjectOutputStream(new BufferedOutputStream(s.getOutputStream()));
ois = new ObjectInputStream(new BufferedInputStream(s.getInputStream()));
...


Wenn ich es so ausprobiere, dann bleibt es bei der Instanzierung von 'ois' beim Client bzw von 'oos' beim Server stecken.

Sliver21
2007-07-02, 14:42:00
Wie siehts aus, Jungs? Ist dieser Ansatz in Ordnung, um vom Client wie auch vom Server aus Objekte empfangen und senden zu können?

Shink
2007-07-02, 16:17:37
Wie siehts aus, Jungs? Ist dieser Ansatz in Ordnung, um vom Client wie auch vom Server aus Objekte empfangen und senden zu können?
Nun ja, nicht ganz:
Der Server könnte da nur einen Client bedienen, also z.B.:

do {
Socket s=s.accept();
new AutorennserverThread(s).start();
} while (up=true);

wobei AutorennserverThread ein Thread-Objekt ist und dann von seinem Stream liest und dann schreibt. (Wenn es ein ObjectStream ist, muss das Objekt serializable sein. Wenn man hingegen etwas anderes versendet als Objects, z.B. byte[] könnte man später Server und Clients in unterschiedlichen Programmiersprachen schreiben, wenn man denn wollte.)

Somit bekommt jeder Client einen eigenen, unabhängigen Thread am Server und der Server kann alles gleichzeitig abarbeiten.

So gings schon mal, wobei Datagramsockets viel schneller wären. Da bräuchte man dann zwar ein eigenes Protokoll und wär somit (Design-)fehleranfälliger (man muss auch einplanen, dass etwas manchmal nicht oder in falscher Reihenfolge ankommen kann), aber lohnen tät sichs für so etwas schnelles wie ein Autorennspiel bestimmt.

Man kann ja mal TCP (normale) Sockets probieren und schaun wies in der angedachten Umgebung läuft.

Sliver21
2007-07-02, 17:03:50
Danke für deine Antwort. Was ich da oben geschrieben hatte, sollte nur ein Beispiel sein, wo ein Server Objekte senden und empfangen kann und der Client ebenso.

Hierfür benötigt der Client einen ObjectOutputStream und einen ObjectInputStream und der der Server genauso, oder?

Schaut mal:


public class Client implements Runnable{

private Socket s;
private ObjectOutputStream oos;
private ObjectInputStream ois;

public void run() {
try {
s = new Socket("localhost", 22229);

oos = new ObjectOutputStream(new BufferedOutputStream(s.getOutputStream()));
ois = new ObjectInputStream(new BufferedInputStream(s.getInputStream()));


oos.writeObject("von client zu server");

System.out.println(ois.readObject());


} catch (Exception e) {
System.out.println("fehler " + e.getMessage());
}
finally{
try{
oos.close();

s.close();
}catch(Exception e) { System.out.println("Client: Fehler beim Schließen."); }
}
}
}





public class Server implements Runnable{

private ServerSocket ss;
private Socket s;

private ObjectInputStream ois;
private ObjectOutputStream oos;

public void run() {
try {
ss = new ServerSocket(22229);
s = ss.accept();

ois = new ObjectInputStream(new BufferedInputStream(s.getInputStream()));
oos = new ObjectOutputStream(new BufferedOutputStream(s.getOutputStream()));


System.out.println("Server liest:" + ois.readObject());

oos.writeObject("von server zu client");





}catch (Exception e) {
System.out.println("Fehler");
// TODO Auto-generated catch block
e.printStackTrace();
} finally{
try{
oos.flush();
oos.close();

s.close();
ss.close();

}catch(Exception e) { System.out.println("Client: Fehler beim Schli156eßen."); }


}

}
}



Folgenden Ablauf möchte ich hinkriegen: Der Client soll senden, der Server empfangen. Dann soll der Server senden und der Client empfangen.

Shink
2007-07-02, 17:51:41
Wenn man das nur einmal haben will, sollte das so eigentlich funktionieren (der Server muss halt laufen, während man den Client startet). Gibt es denn ein Problem damit.

Sliver21
2007-07-02, 18:07:12
Ja, es gibt ein Problem. Es bleibt immer hängen. Ich habe eben den BufferedInputStream und den BufferedOutputStream überall rausgenommen und dann funktioniert es. Dann sendet er und empfängt auch was. Ich verstehe aber gar nicht, wieso es mit dem BufferedInputStream und BufferedOutputStream nicht lief.

Hier ist die Testklasse zum Starten:



public class Test {

public static void main(String[] args) {

new Thread(new Server()).start();
new Thread(new Client()).start();

}

}



Edit:

Sooo, jetzt funktioniert es endlich. Nachdem ich die Buffered-Dinger rausgenommen hatte, läuft es wunderbar.