Socket Sporadisch auftretende EOFException

Pathor

Mitglied
Hallo,

ich bin gerade dabei ein Spiel netzwerkfähig zu machen. Im Spiel sind Server und Client implementiert. Ohne Netzwerk läuft es wunderbar.

Das Spiel lautet "Schiffe versenken" und ich versuche gerade ein Point-Objekt von einem Spiel zum anderen zu bekommen.

Das funktioniert auch meistens, doch hin und wieder schleicht sich eine EOFException ein.

Hier der Stacktrace:

Code:
java.io.EOFException
	at java.io.ObjectInputStream$BlockDataInputStream.peekByte(ObjectInputStream.java:2571)
	at java.io.ObjectInputStream.skipCustomData(ObjectInputStream.java:1917)
	at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1599)
	at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1514)
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1750)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1347)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:369)
	at de.oop.xxx.min.netzwerk.Server.<init>(Server.java:27)
	at de.oop.xxx.min.SchiffeVersenkenController.<init>(SchiffeVersenkenController.java:26)
	at de.oop.xxx.min.SchiffeVersenken.main(SchiffeVersenken.java:14)

Das Point-Objekt wird erzeugt, wenn innerhalb der GUI ein Button angeklickt wird, der wiederrum in einem 2D Button-Array abgelegt ist. Der Actionhandler leitet diese Eingabe (mit den x,y Koordinaten) an den Controller und der ruft dann die Client-Klasse auf, die das Point-Objekt erwartet und an den Server der Gegenstelle weiterschickt.

Hier der Client-Code:
Java:
public class Client {

    public Client(Point aktuelleSchussKoordinate) {
        
        try {
            Socket socket = new Socket("127.0.0.1", 3333);
            try (ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream())) {
                oos.writeObject(aktuelleSchussKoordinate);
                oos.flush();
            }
        } catch (UnknownHostException ex) {
            System.out.println("Der Host ist unbekannt! " + ex);
        } catch (IOException ex) {
            System.out.println("Es gab folgenden Fehler: " + ex);
        }
    }
}

Und hier der Servercode:

Java:
public class Server {
    
    @SuppressWarnings("CallToThreadDumpStack")
    public Server() {
        try {
            ServerSocket serverSocket = new ServerSocket(4444);
            while(true) {
                Socket socket = serverSocket.accept();
                try (ObjectInputStream ois = new ObjectInputStream(socket.getInputStream())) {
                    Point so = (Point) ois.readObject();
                    System.out.println("Schusskoordinaten X: " + so.x);
                    System.out.println("Schusskoordinaten Y: " + so.y);
                } catch(EOFException ex) {
                    System.out.println("Mööpp!!! (EOFException)");
                    ex.printStackTrace();
                }
            }
        } catch( IOException | ClassNotFoundException ex) {
            System.out.println("Es gab folgenden Fehler: " + ex);
        }
    }
}

Hier die Ausgabe in netbeans:

netbeans_eofexception.png

In netbeans gehe ich so vor: Ich starte das Programm mit den Einstellungen oben (Client Port 3333, Server Port 4444)
Danach ändere ich die Ports so: (Client Port 4444, Server Port 3333) und starte das Programm zu zweiten Mal. Komischerweise wird nur in (run) Programm 1 die Ausgabe erzeugt, die im Server-Code steht (Schusskoordinaten). In (run) #2 Programm 2 steht nichts, auch wenn ich von (run) aus schieße.
Woran liegt das? Gehe ich hier richtig vor, um die Netzwerkfähigkeit des Spiels zu testen?

Warum erfolgt nur hin und wieder die EOFException? :bahnhof:

Ich hoffe, ihr könnt mir helfen... :(
 
O

ObjectStream

Gast
Warum versuchen eigentlich immer alle auch das einfachste wie Chats oder einfach Commandos über ObjectStreams laufen zu lassen.

Wo ist es denn bitte das Problem einfach dem Gegenüber einen String zu senden wie "feuer/x/y" und diesen dann einfach zu parsen und entsprechend der Logik eine Antwort (true|false) zurück zu schicken ?

Ich glaube du machst es dir deutlich umständlicher als es sein müsste. Versuch das ganze mal etwas "kleiner" mit PrintStream und BufferedReader, habe ich damals auch so bei meinem "Schiffe Versenken" gemacht. Hat zwar selbst im 100MBit/s LAN zu kurzen "Laggs" geführt, war aber deutlich einfacher zu implementieren.
 

Pathor

Mitglied
Danke für deine Antwort!

Bei so einfachen Objekten verstehe ich ja deine Aufregung, aber eigentlich möchte ich in Zukunft größere Objekte über den OOS und OIS laufen lassen. Wenn jetzt z.B. ein Schiff auf der Gegenseite versenkt wurde, möchte ich einfach in einem Transaktionsobjekt ein Schiff mit rüberschicken (daraus kann dann die Gegenstelle den Schiffsnamen ermitteln und die Koordinaten des versenkten Schiffes).

Es kann doch nicht sein, dass Java bei solch simplen Aktionen solche Probleme macht.
 
O

ObjectStream

Gast
Java selbst ist hier auch nicht das Problem sondern der Weg wie du versuchst das Objekt zu übertragen und zu verarbeiten.
Klick dich mal im Sun-Tut durch den Abschnitt Serializeable und vielleicht auch noch mal in der Java-Insel nachschauen.

Außerdem macht es sehr wenig Sinn wenn du einen ServerSocket auf TCP/4444 aufmachst und dann versuchst auf TCP/3333 zu connecten. Es hört sich nämlich so an als wenn du beide Klassen aus dem selben Stack startest (also in EINER Instanz beide Klassen verwendest). Das ist schon mal totaler Mist, denn wenn [c]new Socket()[/c] das Timeout erreicht bevor der passende Server überhaupt richtig läuft hast du keinen Loop der versucht sich erneut zu verbinden.

Alles in allem ziemlich schlecht überlegt und fast kein Konzept hinter.

Anstatt dich gleich mit Objekt-Serialisierung rumzuschlagen solltest du vielleicht erstmal die Grundlagen der Netzwerkkommunikation in Java lernen, weil es scheint das du da noch nicht ganz so fit bist.
 
O

ObjectStream

Gast
Grundlegend gilt für Server-Client-Architekturen das der "Server" erstmal vollständig laufen und im ServerSocket.accept()-Loop sein muss bevor du überhaupt den Client startest. (Oder komplizierter ausgedrückt : zeitlich muss ServerSocket.accept() vor Socket.connect() passieren.)

Das was du da machst hört sich eher danach an das du mal eben beide Teile des Programms von einer main aus startest und zum Testen das ganze zwei mal mit lediglich verstauschten Ports machst. Dass das schiefgeht liegt auf der Hand da in der ersten Instanz der Punkt an dem sich der Client zum (noch nicht laufenden) Server verbinden soll zeitlich vorher erreicht wird als in der zweiten Instanz überhaupt der Punkt an dem ServerSocket.accept() gecallt und damit überhaupt auf die Verbindung gewartet wird. Dadurch läuft Socket.connect() ins Timeout und es wird überhaupt gar keine Verbindung aufgebaut.

Wenn du sowas richtig machen willst und auch noch neu auf dem Gebiet bist dann nimm dir zwei Projekte : eines nur Server und das andere nur Client. Und dann halt immer schön darauf achten das erst der Server voll läuft bevor der Client gestartet wird. So wie du es versuchst wird das nie was werden (es sei denn du schaffst es beide Instanzen "gleichzeitig" zu starten dass das connect()-Timeout nicht überschritten wird).
 

Pathor

Mitglied
Wenn du sowas richtig machen willst und auch noch neu auf dem Gebiet bist dann nimm dir zwei Projekte : eines nur Server und das andere nur Client. Und dann halt immer schön darauf achten das erst der Server voll läuft bevor der Client gestartet wird. So wie du es versuchst wird das nie was werden (es sei denn du schaffst es beide Instanzen "gleichzeitig" zu starten dass das connect()-Timeout nicht überschritten wird).
Genau das wollte ich eigentlich vermeiden. :shock:

Habe jetzt das Problem mit dem "Server zuerst starten" verstanden. Ich suche auch schon ne Weile im Netz rum, um das zu finden, was ich vorhabe. Client und Server jeweils in einem Programm.
Wie programmiert man das aber?
Wenn ich den ersten Zug mache (Spieler 1) und auf das Spielfeld des Gegners (Spieler 2) schieße, mache ich ja eine Anfrage zum Server gegenüber. Der verarbeitet die Anfrage (nachschauen, ob getroffen) und dann das Ergebnis zurück zum Spieler 1. Anhand des Ergebnisses erkennt dann die Logik von Spieler 1, ob noch einmal geschossen werden kann (also noch mal die gleiche Transaktion vom Client zum Server + abwarten auf Ergebnis). Wenn nun aber daneben geschossen wurde und Spieler 2 an der Reihe ist, bleibt dann die Verbindung zum Spieler 1 offen und ich kann von meiner Serverseite aus einen Schuss auf das Spielfeld von Spieler 1 abgeben (Server fragt Client)?

Mit dieser herangehensweise macht der aktuell "schießende" den Gegenüber zum Server. Aber dann muss auf beiden Seite der Server von Anfang an laufen, oder? Das hört sich nach nem Henne-Ei-Problem an.

Schade, dass es keine richtig gute Lektüre über genau diese Situation gibt. Es heißt immer nur "der eine ist Client (Client-Programm) und der andere Server (Server-Programm)". Ich finde nie Beispielcode zu genau meinem Problem (Client und Server in einem Programm). Vielleicht gibt es das ja doch, nur habe ich es nicht erkannt. ;(

Ich bin echt froh, dass zumindest du mir ein paar gute Tipps geben kannst. :toll: Ich hatte vor der Netzwerkprogrammierung gedacht, dass das schnell erledigt ist. Jetzt ist aber weitaus umständlicher, als knapp 1500 Zeilen Code zum Spiel selbst.
 
S

Spacerat

Gast
Bevor man Netzwerke programmiert, sollte man evtl. erstmal die Grundzüge von Java verstehen. Demnach lassen sich Server und Client nämlich auch in Klassen kapseln, welche dann über ein Programm hinaus weiterverwenden lässt. Beispiele lassen sich deswegen auch mischen, im einfachsten Fall so:
Java:
class Server {
  public static void main(String[] args) {
    // Tutorialserver
  }
}

class Client {
  public static void main(String[] args) {
    // Tutorialclient
  }
}

class ServerClient {
  public static void main(String[] args) {
    Server.main(args);
    Client.main(args);
  }
}
Natürlich ist das Beispiel für ernstzunehmende Programme völliger Blödsinn, das macht man halt ein wenig anders. Man instanziert den Serverteil, wartet bis die entsprechende Methode returned und dann looped man die Instanzierung des Clienten solange, bis mindestens eine Gegenstation erreichbar ist oder abgebrochen wird. Nun hat man eine Verbindung.
Die Streams der Sockets sollten nun, bevor sie in andere Geschachtelt werden zunächst erstmal gepiped werden, das hält die Verbindung aufrecht. Ob nun Daten im Stream vorhanden sind prüft man bei zustandegekommener Verbindung nicht mehr mit "accept()" sondern mit "read()" auf den gepipeten Inputstream des Sockets. "read()" blockiert ähnlich wie "accept()" und zwar solange, wie der Stream keine Daten enthält;

Commands lassen sich im übrigen auch verkürzen, wenn man dafür enums benutzt. Man muss dann das Netzwerk nicht mehr mit Strings belasten, sondern kommt mit Integern (CommandID - im einfachsten Fall "<Enum>.ordinal()") aus. Auf der anderen Seite lassen sich diese IDs erstens mit switch behandeln und zweitens sogar recht einfach auch wieder in ihre Enum-Klasse zurück mappen.
 
O

ObjectStream

Gast
Mit dieser herangehensweise macht der aktuell "schießende" den Gegenüber zum Server. Aber dann muss auf beiden Seite der Server von Anfang an laufen, oder? Das hört sich nach nem Henne-Ei-Problem an.

Nein, das ist so schon mal totaler Unsinn.

Eine TCP-Verbindung ist Bi-Direktional, das heißt das Nachrichten in beiden Richtungen übertragen werden können OHNE das die Rollen von Server und Client getauscht werden müssen.

Wenn du keine gute Lektüre findest versuch ich es dir mal zu erklären. Ich hoffe das du es wenigstens etwas verstehst da ich im Erklären von für mich mitlerweile "normalen Kram" ziemlich schlecht bin. Ich werde das ganze mal an meine eigene Implementierung anlehnen.


Zu Beginn des Programmstarts hast du erstmal die Auswahl ob du einen Server "aufmachen" oder dich nur als Client zu einem Server verbinden willst.
Wenn du "Server" wählst wird der Server gestartet und auf eine Verbindung vom Client gewartet.
Wenn du "Client" wählst wird eine Verbindung zum Server aufgebaut. (Die IP kann man bei mir in einem JTextField eingeben.)
Wenn nun eine Verbindung beim Server eingeht ist das Spiel eröffnet und es folgt das platzieren der Schiffe.
Dabei läuft es so ab das der Client dem Server ein "ready" schickt so bald alle Schiffe platziert wurden. Wenn der Server seine Schiffe vorher platziert hat dann wartet er auf dieses "ready", und wenn nicht dann wort beim Empfang dessen ein Flag gesetzt das dann geprüft wird. Natürlich muss dann der Server dem Client ebenfalls ein "ready" schicken. Ich habe es so implementiert das dies grundsätzlich erst nach dem "ready" des Clienten gesendet wird. Wenn also der Client schneller ist wartet dieser, ansonsten wartet der Server auf das "ready" und sendet dann seinerseits "ready" an den Client.
Nun haben beide ihre Schiffe verteilt und wissen vom jeweils anderen das dieser ebenfalls fertig damit ist. Dann geht erst das eigentliche Spiel los.
Ich habe es der einfachheit so implementiert das immer der "Server" den ersten Zug macht, aber das kann man auch noch erweitern.
Die gesamte Logik wird lediglich im Server implementiert, der Client-Part dient nur zur Anzeige der Infos die vom Server kommen (bzw an diesen gesendet wurden).
Nun beginnt also der Server : dieser sendet an den Client "feuer/x/y". Der Client guckt ob auf X:Y ein Schiff ist oder nicht und sendet entsprechend "true" oder "false". Wenn "true" kommt darf der Server noch mal, wenn "false" kommt wird bei deinen ein Flag getoggled und der Client ist dran.
Und so geht das bis zum Ende. Natürlich muss hier dann noch eingebaut werden das beim Versenken des letzten Schiffes gleich die Info mitkommt "allDestroyed".
Und alles geht über die eine Verbindung die am Anfang aufgebaut wurde.
Nur das halt derjenige der halt gerade nicht dran ist auf ein Event vom anderen erwartet der gerade dran ist.

Schiffe versenken ist nicht gerade das einfachste Projekt um mit Netzwerk in Java anzufangen da man sich auch mit der GUI und der Spiel-Logik beschäftigen muss.

Das einfachste Projekt was mir in Punkto Java und Netzwerk einfällt ist erstmal ein ganz normaler Chat. Dort lernt man schon sehr viele Grundlagen und muss sich bis auf ein paar Zeilen nicht wirklich um die GUI kümmern.
Dort kann man dann lernen wie man auf bestimmte Nachrichten gezielt wartet und reagiert, mit verschiedenen Status-Werte umgeht (Flags) und auch wie Bi-Direktionale Verbindungen funktionieren.


Es ist nicht so das ich dir meinen Code nicht gerne zur verfügung stellen würde damit du von diesem lernen kannst, und ich habe auch mal ein sehr langes (3 Wochen) Tutorial über die Entwicklung geschrieben (halt jeden Tag ein speziellen Teil, relativ complexe GUI, mehrere 10'000de Zeilen Code), aber leider ist diese Arbeit vor einiger Zeit (irgendwan letzten November) auf Grund eines Fehlers auf meinem USB-Stick verloren gegangen.

Ob ich jetzt ein "einfaches" Schiffe versenken mal eben so aus dem Hut zaubern kann, naja vielleicht. Aber graphisch schön wird es nicht. Bei dir geht es ja zum Glück nur um den Net-Layer.

Mal gucken ob ich dir nach dem Mittag ein "sinnvolles" Beispiel zusammenbasteln kann an dem du dann die ungefähre Arbeitsweise erkennen kannst wie du deinen Net-Layer umsetzen könntest.
 

Pathor

Mitglied
Mal gucken ob ich dir nach dem Mittag ein "sinnvolles" Beispiel zusammenbasteln kann an dem du dann die ungefähre Arbeitsweise erkennen kannst wie du deinen Net-Layer umsetzen könntest.
Das wäre super! :)

Das Spiel an sich mit einer GUI und Logik zum platzieren der Schiffe und Trefferermittlung samt Schiffserkennung (wenn versenkt), habe ich schon. Bin nur ein absoluter Anfänger in Netzwerkprogrammierung. :autsch:

Was ich in deinem Post herauslesen konnte ist, dass die Verbindung stehen bleibt.
Meine erste Überlegung war (vor diesem Thread): Client baut Verbindung zum Server auf und übermittelt einen Schuss auf's gegnerische Feld. Der Server verarbeitet das und schickt das Ergebnis zurück. Die Verbindung wird getrennt (Ströme und Sockets geschlossen) und von der Seite neu aufgebaut, die jetzt am Zug ist.
 

Pathor

Mitglied
@Spacerat

Hab deinen Post vollkommen übersehen. Danke dafür! :)

@ObjectStream

Ich mache morgen doch eine Client und Server Version des Spiels. Ich will es erst einmal auf diese Weise zum Laufen bekommen. Wenn das dann funktioniert, versuche ich noch mal die "andere" Methode. ;) Hab halt nicht viel Zeit zum Ausprobieren, da die Deadline immer näher rückt.
Ich setze die Instanz des Server(Client)-Objekts in die Klasse, in der ich das ganze Model des Spiels abfragen/verändern kann. Das Model sagt es dann der View (Observer) und schon läuft das Spiel samt Angaben und evtl. gesperrtem rechten Spielfeld.

Im Detail:

- Server-Version wird gestartet
- Client-Version wird gestartet

- Beide Spieler platzieren ihre Schiffe auf dem linken Spielfeld. Wenn Client schneller ist, als der Server, wird es an den Server übermittelt und beim Server im Statusfeld angezeigt. Wenn der Server auch fertig ist, wird es an den Client übermittel und auch da im Statusfeld angezeigt.

- Es fängt derenige an, der vom Client per Zufallszahl bestimmt wurde.

- Klick auf einen Button des rechten Spielfelds --> Controller übermittelt diese Daten an das Model, das wiederrum den Schuss Richtung Output des Sockets schickt (Transaktionsobjekt mit den Schusskoordinaten).

- Die Daten werden per Input beim Gegenüber empfangen und ausgewertet (Feld markieren) und die Ergebnisse (Schusskoordinaten, Treffer, Versenkt, versenktes Schiff) zurückgeschickt. Wenn getroffen, dann rechtes Feld gesperrt lassen, ansonsten rechtes Feld entsperren und Status "Du bist dran" ausgeben.

- Auf der anderen Seite wertet man diese Daten aus und aktualisiert die View (auch hier die Aktualisierung des Statusfeldes etc).


In der Theorie sollte das so funktionieren. Ich werde euch auf dem Laufenden halten. :)
 
O

ObjectStream

Gast
Was ich in deinem Post herauslesen konnte ist, dass die Verbindung stehen bleibt.
Richtig, denn eine TCP-Verbindung ist wie gesagt Bi-Direktional.
Meine erste Überlegung war (vor diesem Thread): Client baut Verbindung zum Server auf und übermittelt einen Schuss auf's gegnerische Feld. Der Server verarbeitet das und schickt das Ergebnis zurück. Die Verbindung wird getrennt (Ströme und Sockets geschlossen) und von der Seite neu aufgebaut, die jetzt am Zug ist.
Nein, so ist das Murks. Das macht man so nicht. Auch wenn in dem Sinne beide Instanzen für die Spieler an sich nur die GUI und damit der Client sind so ist dann einer aber der der das Spiel eröffnet hat (Server) und der andere der daran Teilnimmt (Client). Und das bleibt auch so lange bestehen bis sich einer der Beiden ausklinkt.
Außerdem würdest du mit deiner Variante schnell in NAT-Fallen rennen, denn wenn du das z.B. übers Internet spielen willst müssten nach deiner Variante beide in ihren Router Port-Forwarding einrichten. Nach dem normalen ablauf (also bestehender Verbindung) muss dies nur der Server tun.

Wie gesagt : Schiffe versenken ist vielleicht nicht gerade so das allerbeste Projekt für jemanden der sich mit der ganzen Materie "Netzwerk" erst vertraut machen will und für den Streams höchstens mal beim Umgang mit Files interessant war. Weil es gibt hier halt die Schwierigkeit Client und Server klar zu trennen. Client ist in diesem Falle eigentlich alles was an der GUI hängt. Der Server ist lediglich ein kleines Zusatzmodul was nur läuft wenn man auch selbst das Spiel eröffnet hat.
Man könnte sogar soweit gehen und den "Host" auch lokal verbinden. So wird vielleicht die Trennung deutlicher, aber dann müsste man einen großteil der Logik redundant im Server und im Client unterbringen, und ich denke das wäre dann jetzt doch noch ein wenig viel für dich (will dir damit nicht zu nahe treten, aber damit solltest du wirklich lieber noch warten bis du dein Schiffe versenken richtig zum laufen bekommen hast).

Ich habe mich heute Nachmittag schon mal rangesetzt und angefangen ein kleines "Demo" zu basteln, war aber leider totaler Murks und hab viel mit Dingen gearbeitet die du vielleicht erst noch lernen wirst. Werde daher veruschen das ganze noch mal von Grund auf etwas "sauberer" und "Einsteigerfreundlicher" zu designen. Wie gesagt : für mich ist sowas mitlerweile selbstverständlich und ich arbeite in meinen eigenen Projekte auf ganz anderen Leveln als bloß einen String oder meinetwegen auch ganz groß gleich ein Objekt zu senden.

Ich weis nicht wie schnell ich das jetzt noch hinbekomme, sollte aber über die Nacht noch fertig werden. Frage ist dann nur ob ichs halbwegs zusammenhängend hier posten kann.
Ich werde versuchen alles möglichst gut zu kommentieren.
 
Ähnliche Java Themen

Ähnliche Java Themen

Neue Themen


Oben