Socketexception connection reset - nach längerer Wartezeit

Hallo Community,

zuerst ein mal ein kurzer Abriss des Programm ablaufes:

  1. Der Client wird gestartet und baut eine TCP-Verbindung mit dem Server auf
    new Socket(tcpIp, tcpPort)
    diese Verbindung wird erst Beendet, wenn auch das Programm beendet wird.

  2. Client und Server tauschen Nachrichten zur initialisierung aus

  3. Der Client sendet Anfragen an den Server und wartet auf Antwort. Anfrage und Antwort sind über entsprechende ID’s zuordne bar. Dadurch ist die Reihenfolge der Antworten egal und die Anfrageoperation kann zeitgleich mit der Verarbeitung einer Antwort erfolgen (Stichwort: Threads)

Darum habe ich mir einen Thread geschrieben, der permanent horcht, ob der Server eine Antwort sendet:

			bufferedReader 					= new BufferedReader(new InputStreamReader(socket.getInputStream()));
			do {
				char[] buffer				= new char[4096];
				int anzahlZeichen			= bufferedReader.read(buffer); //wartet an dieser Stelle, bis eine Nachricht empfangen wird
				nachricht 					= nachricht+new String(buffer, 0, anzahlZeichen);			
			} while (bufferedReader.ready());
//...```
(wurde eine Nachricht empfangen, wird diese Methode wieder gestartet um auf weitere Nachrichten horchen zu können)

Ich hoffe das ist an und für sich verständlich.
nun habe ich derzeit folgendes Problem:

Es kann vor kommen, das mal eine halbe Stunde keine Anfrage raus geht und keine Antwort kommt.
D.h. es kommt vor, dass der Thread über eine halbe Stunde an folgender Stelle steht:
```int anzahlZeichen			= bufferedReader.read(buffer); //wartet an dieser Stelle, bis eine Nachricht empfangen wird```

Ist dies der Fall, erhalte ich folgende Exception:
```java.net.SocketException: Connection reset
	at java.net.SocketInputStream.read(SocketInputStream.java:179)
	at sun.nio.cs.StreamDecoder$CharsetSD.readBytes(StreamDecoder.java:464)
	at sun.nio.cs.StreamDecoder$CharsetSD.implRead(StreamDecoder.java:506)
	at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:234)
	at java.io.InputStreamReader.read(InputStreamReader.java:188)
	at java.io.BufferedReader.fill(BufferedReader.java:147)
	at java.io.BufferedReader.read1(BufferedReader.java:198)
	at java.io.BufferedReader.read(BufferedReader.java:272)
	at java.io.Reader.read(Reader.java:134)
	at xxx.xxx.MyListener.leseNachricht(MyListener.java:78)
	at xxx.xxx.MyListener.run(MyListener.java:41)```

Der Server hat kein Timeout. Also muss das Problem ja bei meinem Client liegen.
(Der Server ist für mich eine BlackBox)

Solange in kurzen intervallen Nachrichten an den Server gesendet werden / vom Server kommen, wird die Exception nicht geworfen.

Kann mir hier jemand weiter helfen?

Danke im voraus.

Mfg
casi91

“Möglicherweise” ein Reset wegen Timeout. Wobei 30 Minuten etwas kurz sind (normal sind eher 2 oder 4 oder sogar noch mehr Stunden). Du kannst das Socket anweisen, sogenannte Keepalives zu senden, wenn keine Daten fließen. Schau Dir mal die entspr. Methode an: Socket#setKeepAlive(boolean)
Wenn die nicht hilft, weil die Keepalives zu selten gesendet werden, hast Du zwei Möglichkeiten:

  • Programmiere eigene Keepalives.
  • Fange die SocketException und öffne ein neues.

Danke für die schnelle Antwort.
setKeepAlive(true) hat leider nicht funktioniert.
Also muss ich wohl die beiden anderen Möglichkeiten in betracht ziehen.

die Antwort von nillehammer ist bestenfalls knapp über eine halbe Stunde vor deinem Posting,
hast du in der Zeit genau einen Test hineinbekommen?
wann ist der abgebrochen, wieder nach ca. 30 Min. oder variiert das messbar?


wenn ich nicht falsch liege, bitte ggfs. korrigieren, ist Timeout in Java etwas spezieller als vielleicht im allgemeinen Sprachgebrauch,
kein völliges Ende der Verbindung,
sondern eine einzelne Anfrage kann damit zeitlich begrenzt werden

API: If the timeout expires, a java.net.SocketTimeoutException is raised, though the Socket is still valid.

die Connection funktioniert dabei weiterhin, und die fehlende Antwort kommt vielleicht später auch noch…,
letztlich kein guter Verwendungszustand (genauso wie Annahme dass der Server sich Antwort einspart)

das Problem ist Beendigung der Verbindung nach Inaktivität, bist du sicher dass der Server da nicht tätig wird?

ein Test wäre, den Client auf einen anderen, z.B. eigenen Java-Server loszulassen,
wobei mit nur einem Rechner über localhost verbunden, vielleicht nicht so aussagekräftig gegenüber echtem Internet


aber Bestätigung Server als Ursache würde dir sicherlich letztlich auch nicht wirklich weiterhelfen,
außer vielleicht kleine Zweifel im bisherigen Client,
eigene aktive (Dummy-?)Nachrichten scheinen nötig

was immer ein Automatismus wie setKeepAlive(true) macht, hilft vielleicht bei Problemen im wortwörtlichen Zwischennetz,
gewiss aber nicht bei der Anwendung an sich:
so wie dein Client keine zusätzlichen nachrichten-Strings erhält, wird auch der Server im eigentlichen Anwendungscode nur einen inaktiven Client sehen,
wenn dort Code-Zeile if zeit > 30 Min -> tschüss steht, dann musst du dagegen angehen, mit realer (Dummy-?)Aktivität

der Versucht hier 4096 Zeichen aus dem Netzwerk zu lesen. Ist Deine Nachricht kürzer (was sehr wahrscheinlich ist), dann wird er warten bis die 4096 Zeichen voll sind. Das wird vermutlich ein paar Nachrichten später sein. Gleichzeitig hast Du das Problem das die letzte Nachricht „verstümmelt ist“. Da ein Teil der Nachricht in den 4096 Bytes enthalten ist, um sie „voll“ zu machen. Und der Rest dann in den nächsten 4096 Bytes.

Das Ding ist Schrott - einen KeepAlive musst Du selber implementieren. Sende einfach vom Server oder vom Client im X Sekunden Takt eine eigene Keep-Alive-Nachricht. Über den Keep-Alive-Mechanismus vom TCP habe ich noch nie einen Kabelbruch mitbekommen. Grundsätzlich nur über eine eigene Implementierung, weil dann nämlich nix ankam und eine Timeout-Exception mein Programm aus seinem Tiefschlaf geholt hat.

wie ich feststellen musste, wird die Exception auch früher geworfen. Bisher konnte ich noch nicht genau messen, wie lange keine Konversation stattfinden kann, ohne eine Exception.

das Problem ist Beendigung der Verbindung nach Inaktivität, bist du sicher dass der Server da nicht tätig wird?

Wie erwähnt ist der Server eine BlackBox für mich. Laut Schnittstellenbeschreibung und erneuter Nachfrage, beendet der Server die Verbindung nicht. Sondern lässt diese nach initialisierung permanent bestehen.

wenn dort Code-Zeile if zeit > 30 Min → tschüss steht, dann musst du dagegen angehen, mit realer (Dummy-?)Aktivität

Da bin ich gerade dabei. Ein kleiner unscheinbarer Absatz in der Schnittstellenbeschreibung gibt mir die Möglichkeit, eine Anfrage an den Server zu senden, ob die Verbindung noch sauber ist. Hier kommt dann auch ein Lebenszeichen seitens des Servers.
Derzeit sende ich alle 2 Minuten diese Anfrage und seitdem ist auch keine Exception (bei egal welcher Kommunikation) mehr aufgetreten.

Ich werde nun nach und nach versuchen, die Minuten hoch zu schrauben um mal fest zu stellen, wie lange es dauert, bis eine Exception geworfen wird und ob der Wert gleich bleibt oder variiert.

*** Edit ***

dann wird er warten bis die 4096 Zeichen voll sind.

nach meinen Tests ist dies nicht der Fall.
Auch eine Nachricht mit nur 200 Zeichen wird gelesen und in „anzahlZeichen“ steht dann eben 200 drin.

[QUOTE=mogel]der Versucht hier 4096 Zeichen aus dem Netzwerk zu lesen. Ist Deine Nachricht kürzer (was sehr wahrscheinlich ist), dann wird er warten bis die 4096 Zeichen voll sind. Das wird vermutlich ein paar Nachrichten später sein. Gleichzeitig hast Du das Problem das die letzte Nachricht “verstümmelt ist”. Da ein Teil der Nachricht in den 4096 Bytes enthalten ist, um sie “voll” zu machen. Und der Rest dann in den nächsten 4096 Bytes.
[/QUOTE]
wirklich sehr kühne Aussage, sieht doch ganz so aus als hätte casi91 damit schon einiges erfolgreich gelesen,
was sollte auch der Rückgabewert der Methode geben wenn immer voll gelesen wird, nur für -1 zu gebrauchen?
welche Alternative hättest du zum Lesen einer aktuellen Nachricht im Sinn


die Methode passt schon, sogar so gut, dass man dasselbe Array wiederverwenden könnte:
beim String-Konstruktor wird 0 bis anzahlZeichen eingefügt, genau diese sind auch garantiert richtig im Array,
ob vom früheren Lesen noch alte chars weiter hinten stehen ist egal,
kein neues Array in jedem Schleifendurchlauf nötig

das Anfügen an nachricht-String sieht allerdings komisch aus, wobei sicher nötig bei Teil-Nachrichten,
wächst der String im Programmverlauf ohne Gegenwirkung? bei MB irgendwann durchaus relevant,
oder schneidet jemand anders vorne Teile ab? sollte dann synchronisiert werden

edit: mit ready() als Schleifenbedingung geht es aber vielleicht dahinter noch weiter, da kann das dann ja passieren

das Anfügen an nachricht-String sieht allerdings komisch aus, wobei sicher nötig bei Teil-Nachrichten,
wächst der String im Programmverlauf immer größer? bei MB irgendwann durchaus relevant,
oder schneidet jemand anders vorne Teile ab? sollte dann synchronisiert werden

Ich habe mal meine lese Routine komplett unten beschrieben. Dann wird es wohl eindeutiger.
Ich lese so lange, bis im bufferedReader nichts mehr steht.
Anschließend wird das gelesene durch einen neuen Thread interpretiert (hier werden dann die Antwort(en) bearbeitet).
Zeitgleich wird „leseNachricht“ neu aufgerufen um erneut auf den Socket zu horchen.
Und die Antworten können ggf. recht groß werden. Zu vergleichen ist das prozedere mit einer SQL anfrage.
Bei „Select * from personen where name like ‚m%‘“ kommen i.d.R. mehr Sätze (Antworten) wie bei „Select * from personen where name like ‚meier%‘“

	public void run() {
		MyConnection con;
		try {
			con = MyConnection.getConnection(); //Singelton
			while(true) {
				if (!con.getSocket().isClosed()) {
					leseNachricht(con.getSocket());
				} else if (!con.isClosedRight()) {
					//...
				} else {
					break;
				}
			}
		} catch (UnknownHostException e) {
			//...
		}
	}

	private static void leseNachricht(Socket socket) throws IOException {
		BufferedReader bufferedReader;
		String nachricht					= "";
		try {
			bufferedReader 					= new BufferedReader(new InputStreamReader(socket.getInputStream()));
			do {
				char[] buffer				= new char[4096];
				int anzahlZeichen			= bufferedReader.read(buffer); //wartet an dieser Stelle, bis eine Nachricht empfangen wird
				nachricht 					= nachricht+new String(buffer, 0, anzahlZeichen);			
			} while (bufferedReader.ready());	
			new Thread(new MsgInterpreter(nachricht)).start(); 				
		} catch (IOException e) {
			//...
		}
	}	```

[quote=SlaterB]wirklich sehr kühne Aussage, sieht doch ganz so aus als hätte casi91 damit schon einiges erfolgreich gelesen,
was sollte auch der Rückgabewert der Methode geben wenn immer voll gelesen wird, nur für -1 zu gebrauchen?
welche Alternative hättest du zum Lesen einer aktuellen Nachricht im Sinn[/quote]

hmmm - mag sein das ich mich da getäuscht habe und das mit meiner eigenen Implementierung verwechselt habe. Dort lese ich aber auch direkt die Bytes aus dem Stream und weis wieviel da vorher kommt.

also, nehme alles zurück und behaupte das gegenteil (type mitläufer halt)

Standardhinweis sollte natürlich noch sein, dass statt String+String in einer Schleife lieber StringBuilder mit append() zu verwenden ist

unten ein Testprogramm mit einem 3000-Zeichen String, der 400x aneinander gefügt wird zu ~1.2 MB,
StringBuilder kaum messbar, String+ dauert 1.5 sec

Anstieg quadratisch, mehr oder weniger zu messen, bei 800x = 2.5 MB dauerts bei mir 4.1 sec mit String+,
bei jedem + muss der alte String kopiert werden, da summiert sich die Arbeit zu real Verschiebung von hunderten MB an Zeichen im Speicher

wenn man Zeichen einzeln hinzufügt, dann von deren Anzahl zu quadrierten: für 1 MB sind Größenordnung 1 TB zu bearbeiten,

zum Glück liest du nicht einzeln sondern mit char, was der Verwendung eines BufferedReaders eigentlich etwas widerspricht,
(vielleicht besonders wenn du auf 8192 wechselst) müsste es ohne BuffererReader auch gehen, sparst dort unnötige Zwischen-char mit Herumkopieren

public class Test2 {
    public static void main(String[] args)  {
        test(true);
        test(false);
        test(true);
        test(false);
    }

    static void test(boolean build)  {
        String st = "abc";
        for (int i = 0; i < 10; i++)
            st = st + st;
        // 3072 Zeichen

        long time = System.currentTimeMillis();
        String all = null;
        int k = 400;
        if (build)   {
            StringBuilder b = new StringBuilder();
            for (int i = 0; i < k; i++)
                b.append(st);
            all = b.toString();
        }   else   {
            all = st;
            for (int i = 0; i < k - 1; i++)
                all += st;
        }
        System.out.println("length: " + all.length() + ", " + (System.currentTimeMillis() - time) + ", " + build);
    }
}

Vielen Dank für den Hinweis mit StringBuilder! Das kommt davon wenn man manchmal nicht noch 2 Sekunden überlegt bevor man etwas programmiert :smiley:

Das mit dem BufferedReader hatte ich aus einem Beispiel, dass ich gefunden habe.
http://de.wikibooks.org/wiki/Java_Standard:Socket_ServerSocket%28java.net%29_UDP_und_TCP_IP

sollte ich dann in meinem Code komplett ohne BufferedReader arbeiten? Oder macht es schon Sinn, diesen an der Stelle (nur anders) zu verwenden?

ein BufferedReader liest standardmäßig 8192 ein, kann man auch modifizieren,
beim Lesen von 200er-Arrays macht das Verhältnis etwas mehr Sinn, wobei in dem Beispiel auch nicht davon ausgegangen wird dass wirklich viel mehr kommt, es gibt keine Schleife

der Aufruf bufferedReader.read(buffer, 0, 200); ist gar bedenklich unnütz, was den ganzen Link etwas in schlechtes Licht stellt, du hast es ja schon besser ohne int-Parameter

mein Wissen dazu ist so theoretisch wie ich es aufgeschrieben habe, kannst du umbauen oder ausprobieren oder was auch immer, auf eigene Gefahr :wink:
viel bringen wird es eh nicht, kannst es auch einfach so lassen,

ich selber verwende häufig BufferedReader, aber quasi immer für die readLine()-Methode um ganze Zeilen lesen,
diese Methode geht zwangsläufig zeichenweise vor, und benutzt dann zum Glück StringBuilder bzw. ältere Version StringBuffer

falls du
als Trenner für Nachrichten hast, wäre das hier auch nützlich,
aber auf den Server hast du sicher keinen Einfluss und für MB-Nachrichten auch nicht zu empfehlen, schätze ich

Danke für die Erklärung.
Ich werde es nun vorläufig bei meiner Version belassen, da ich hier auch noch keine Performanceprobleme hatte. (Da gibt es nun noch andere Baustellen, die ich zuerst angehen muss :-D)

Ein "
" habe ich nicht als Trenner. Dadurch wird also eine readLine() unbrauchbar.

P.s.
habe im Laufe des Morgens nun ein Wenig meine Socket-Connection getestet und dabei festgestellt, dass wenn mehr als 5 Minuten keine Kommunikation stattgefunden hat, eine “Connection reset”-Exception geworfen wird.
Warum, wieso, weshalb…habe ich aber leider immer noch nicht rausfinden können.
Solange ich aber alle 5 Minuten eine Dummy-Nachricht sende (und die dazugehörige Antwort empfange) funktioniert alles.

*** Edit ***

…schrieb er und wurde ca. im selben Moment eines besseren belehrt…
5 Minuten waren gerade auch zu lang…
habe es nun auf 2 Minuten gestellt…

da gestern 5 Minuten jedoch funktionierten…habe ich ein wenig Bedenken, dass die 2 Minuten nun immer funktionieren. Oder ob es hier nicht doch noch irgendwelche Faktoren gibt, die ich beachten muss…

Nun, zunächst muss man sich erstmal klar machen was ein “Connection reset” überhaupt ist und was er bedeutet.
Einer schnellen Google-Suche zu folge bedeutet ein “Connection reset” speziell in Java das unerwartet das Ende des Streams gelesen wurde (EndOfFile/EndOfStream) ohne das von der Gegenseite vorher ein sauberer Verbindungs-Abbau eingeleitet wurde.
Mit anderen Worten : der Stream wird während des read() geschlossen, in diesem Fall vermutlich vom Server.

Wie komme ich darauf ? Nun, dein Code ist mal wieder ein gutes Beispiel für semantische Fehler, also Fehler die Auftreten weil der Code vom logischen her falsch ist.

Sehen wir uns deinen Code doch noch mal genauer an :

Du übergibst der Methode einen Socket (alleine hier schrillen schon alle Alarm-Glocken), baust jedes mal wieder von neuem einen BufferedReader um einen InputStreamReader rum (hier kann es zu Datenverlust kommen !), liest dann alles was DIESER BufferedReader noch vom Stream kratzen konnte und gibst das Ergebnis weiter. Anschließend überlässt du die Stream-Objekte ihrer selbst und damit dem GC.
Seit Java7 mit der Einführung von AutoCloseable verhalten sich alle Stream-Klassen so das sie sich nach dem Ende des Blocks automatisch schließen, was auch try-with-resources erst möglich macht.

Was passiert jetzt genau ? Nun, das ist nach der Erklärung relativ simpel : die immer wieder neu erzeugten BufferedReader werden nach dem verlassen des Blocks nicht mehr erreichbar und wandern damit in den GC-pool. Irgendwann läuft dann der GC los, räumt die Streams auf und callt dabei close(). Jetzt ist es aber so das in Java ein Stream in der Regel so implementiert wird das ein close() den eigenen Cache bereinigt und letztendes close() des gekapselten Streams aufruft.
Ergo : wenn der GC durchläuft und dabei tote Streams aufräumt wird einmal die close()-Kette durchlaufen was dazu führt das du von deiner Seite aus einen Verbindungsabbau auslöst der dann vom Server korrekt mit dem terminieren der Streams beantwortet wird.

Warum passiert das so sporadisch : liegt in der Natur des GC. Er läuft halt durch wenn er es für richtig hält. Auch ein System.gc() ändert daran nur wenig, denn damit kann man den GC höchstens bitten : “hier, ich hab Arbeit für dich, könntest du bitte mal …”.

Was wäre nun eine Lösung ?
Nun, eine Lösung wäre das du irgendwo im Konstruktor der Klasse oder eben in der Methode die die Verbindung aufbaut EINMAL den Stream vom Socket holst und auch nur EINMAL einen BufferedReader drumbaust, und dieses EINE Objekt dann wiederverwendest.
Das sieht dann z.B. so aus :

import java.net.*;
class ClientRunnable implements Runnable
{
	private Server server=null;
	private Socket socket=null;
	private BufferedReader in=null;
	private PrintStream out=null;
	protected ClientRunnable(Server server, Socket socket)
	{
		this.server=server;
		this.socket=socket;
	}
	public void run()
	{
		try
		{
			in=new BufferedReader(new InputStreamReader(socket.getInputStream()));
			out=new PrintStream(socket.getOutputStream());
		}
		catch(Exception e)
		{
			System.out.println("cant get client-streams");
			e.printStackTrace();
			return;
		}
		String line="";
		while(true)
		{
			try
			{
				line=in.readLine();
				if(line==null)
				{
					break;
				}
				if(line.equals(""))
				{
					continue;
				}
				if(line.equals("DC"))
				{
					System.out.println("DC DETECTED");
					break;
				}
				server.broadcast(line);
			}
			catch(Exception e)
			{
				System.out.println("failed to read message");
				e.printStackTrace();
			}
		}
		try
		{
			in.close();
			out.close();
			socket.close();
		}
		catch(Exception e)
		{
			System.out.println("cant close streams/socket");
			e.printStackTrace();
		}
		server.disconnect(this);
	}
	protected void send(String msg)
	{
		try
		{
			out.println(msg);
			out.flush();
		}
		catch(Exception e)
		{
			System.out.println("cant send message");
			e.printStackTrace();
		}
	}
}```
Du solltest auf jeden Fall einen konstanten loop haben in dem du read() callst und nicht aus diesem mit einem do-while und ready() raus und immer wieder von vorne da dabei wie gesagt Datenverlust droht.
Das kann man auf zwei Arten umsetzen :
1.) Protokoll : die Nachricht erhält als erste Information wie lang sie denn ist, dann weis man wie viel man lesen muss
2.) feste Buffer-Größe : man sucht sich irgendeinen recht großen Wert (so 8192) und prüft halt bei jedem loop ob diese voll gelesen wurde, dann ist von auszugehen das noch mehr kommt, oder halt ob weniger gelesen wurde und damit die Nachricht komplett ist

Die 2. Möglichkeit würde dann so in die Richtung gehen :
```private byte[] crypt(byte[] data, Cipher cipher) throws Exception
	{
		ByteArrayInputStream bais=new ByteArrayInputStream(data);
		ByteArrayOutputStream baos=new ByteArrayOutputStream();
		int blockSize=cipher.getBlockSize();
		int outputSize=cipher.getOutputSize(blockSize);
		byte[] input=new byte[blockSize];
		byte[] output=new byte[outputSize];
		int inLength=0;
		boolean finished=false;
		while(!finished)
		{
			inLength=bais.read(input);
			if(inLength==blockSize)
			{
				int outLength=cipher.update(input, 0, blockSize, output);
				baos.write(output, 0, outLength);
			}
			else
			{
				finished=true;
			}
		}
		if(inLength>0)
		{
			output=cipher.doFinal(input, 0, inLength);
		}
		else
		{
			output=cipher.doFinal();
		}
		baos.write(output);
		return baos.toByteArray();
	}```
Auf die Aussage das der Server laut DOC von seiner Seite die Verbindung nicht beenden würde kannst du dich nicht drauf verlassen, aber wenn du schon von deiner Seite semantische Fehler bei der Verwendung einer Socket-Verbindung machst musst du dich über eine korrekte Antwort des Servers auf das Fehlerverhalten deines Codes nicht wundern.


Versuche mal die entsprechenden Punkte auszubessern und dann noch mal erneut zu testen. Sollte das Problem weiterhin auftreten ist zu vermuten das noch irgendwas im Server nicht stimmt und dieser dann tatsächlich von sich aus die Verbindung trennt.

auf Erkennen zu vieler Streams habe ich eigentlich auch ein Copyright :wink:
aber ich war noch von der Anfangsschleife so eingestellt dass die einmalig ist und dauerhaft läuft, obwohl ja zwischendurch Änderung auch erkannt

umso mehr Schande auf
http://de.wikibooks.org/wiki/Java_Standard:Socket_ServerSocket%28java.net%29_UDP_und_TCP_IP
was ist das nur für ein Buch?

allerdings wird das auch ziemlich häufig so gemacht, glaube ich,
@Sen-Mithrarin
schon real einen Socket-Close damit beobachtet?

der Verlust an Daten durch Cache im alten Stream ist häufigeres Problem, auch wichtig

Erst ein mal Danke für diese Ausführliche Erläuterung.
Ich bin nun dabei, die Änderungen ein zu bauen.

Aber das ein oder andere ist mir noch nicht klar.

oder eben in der Methode die die Verbindung aufbaut EINMAL den Stream vom Socket holst

Der Socket wird nur ein einziges mal aufgebaut.
Ich habe ein Singelton-Objekt, welches im Konstruktur die Verbindung aufbaut. Alle Methoden die nun den Socket benötigen, nehmen sich diesen von meinem Singelton-Objekt (das ist doch i.O. oder?)

Du solltest auf jeden Fall einen konstanten loop haben in dem du read() callst und nicht aus diesem mit einem do-while und ready() raus und immer wieder von vorne da dabei wie gesagt Datenverlust droht.

Warum kann es durch meine do-while-Schleife bzw. durch das ready() zu Datenverlust kommen?

lt. Dokumentaton kann ich durch ready doch feststellen, ob im Buffer noch Daten zum lesen vorhanden sind.

Tells whether this stream is ready to be read. A buffered character stream is ready if the buffer is not empty, or if the underlying character stream is ready.

oder verstehe ich da was falsch?

Da ich nun alles anzweifle, was ich bisher mit den Streams und den Sockets gemacht habe, noch eine Frage.
Ich habe einen „WriterThread“ der immer gestartet wird, wenn ich etwas an den Server senden möchte.
Dieser WriterThread lebt nur so lange, wie es dauert die Nachricht an den Server zu senden.

innerhalb dieses Threads greife ich auf das oben genannte Singelton-Objekt zu und nehmen mir den Socket um eben an den Server eine Nachricht zu versenden:

			Socket socket			= con.getSocket();
			printWriter 			= new PrintWriter(new OutputStreamWriter(socket.getOutputStream()));
			printWriter.print(msg);
			printWriter.flush();
//...

Verstehe ich es richtig, dass es hier nun ebenfalls zu einem Problem kommt, da ja die Streams die innerhalb dieses Threads aufgebaut werden, nach kurzer Zeit geschlossen werden?

@SlaterB
Naja, einen Stream- oder gar kompletten Socket-close habe ich auf Grund des AutoCloseable und GC so selbst noch nicht mitbekommen, aber das war so der erste Ansatz der mir spontan eingefallen ist nach dem ich den zweiten Code gesehen habe.
Auch ist es sicher immer implementierungsabhängig was beim finalize() überhaupt passiert. Callt der GC denn überhaupt selbst sauber close() ? Gibt es intern in den natives etwas was dieses AutoCloseable quasi “von außen durch einen Thread” prüft ? Was passiert wenn nach einem GC ein Objekt wirklich vom Heap geräumt wird ohne das dieses überhaupt korrekt abgearbeitet wird ? Ist es möglich das ein Socket zwar auf Level Java “weg” ist aber intern in der VM durch unsaubere arbeit eigentlich doch noch irgendwie halb im RAM liegt ?

Alles möglich und gerade im Zusammenhang mit der Arbeitsweise von TCP über eine externe Leitung durchaus Grund das von einer der beiden Seiten die Verbindung getrennt oder abgebrochen wird.
Auch würde ich bei sowas immer mit Wireshark prüfen was denn da wirklich über das NIC läuft anstatt mich auf eine saubere Implementierung zu lassen.

Trotzdem natürlich interessante Gegenfrage ob denn durch das von mir beschriebene “mögliche Inkonsistenz” wirklich schon mal ein Verbindungsabbruch passiert ist.

— EDIT —
gnarfl - sowas passiert halt wenn zwei Leute gleichzeitig schreiben

  1. Du hast hier in Punkto Datenverlust etwas komplett falsch verstanden. Es mag zwar durchaus so sein das du den Socket in ein Singleton drückst (mal von abgesehen das Singleton an sich unschön ist sollte man das nie direkt mit Sockets oder Streams sondern immer ein Wraper-Objekt nutzen), du erzeugst aber in deinen Methoden von diesem Socket immer wieder neu die Streams und Buffer.

Es geht also nicht darum das du den Socket wiederverwendest, sondern das du unsachgemäß in jeder Methode immer wieder neue Buffer baust die halt gerade beim Lesen dazu führen können das so ein BufferedStream/Reader gerne mal mehr selbst in seinen internen Buffer liest als du eigentlich aus diesem Buffer wieder rauslesen willst. Die Daten sind dann nach dem Block unwiederuflich verloren und fehlen beim nächsten read() da hier wieder ein neuer Buffer erzeugt wird.

Korrekt also : anstatt den Socket durchzugeben lieber ein Wraper bauen dem der Socket übergeben wird und der dann intern einmal die Streams erzeugt und diese dann wiederverwendet werden. Dadurch umgehst du auch das Problem das möglicherweise der GC zuschlagen könnte da du das Wraper-Objekt und damit dessen Inhalt ja immer irgendwo noch haben musst und somit der GC diese nicht als “frei” erkennt.

  1. ready() ist deshalb eher sub-optimal da lediglich festgestellt wird ob der nächste read() -call erfolgreich sein wird, hat auch was mit available() zu tun. Das Problem ist das diese Methoden pessimistisch ausgelegt sind, also wirklich nur dann true liefern wenn auch garantiert werden kann das read() erfolgreich sein wird. Andersrum ist es aber nicht garantiert das available() oder ready() true liefern obwohl Daten zum lesen bereit sind.
    Das kann dann dazu führen das dein loop abbricht weil z.B. ready() fehlerhaft false liefert obwohl Daten vorhanden sind und du dann nur eine unvollständige Nachricht liest.
    Um wirklich sicherstellen zu können das man auch wirklich immer alles liest braucht man schon die Standard-Zeile : while((count=in.read(buffer))!=-1) , denn -1 wird nur dann geliefert wenn der Stream wirklich zu Ende ist und abgebaut wird. Sicher, sowas setzt vorraus das man ein Protokoll hat oder mindestens eine Längen-Angabe wie lang halt die zu lesende Nachricht ist, oder halt irgendwo ein Trennzeichen wie ’
    ’ drin hat. Vor allem dann auch noch wenn man statt einzelner Bytes mit Char arbeitet und daher nicht nur 8Bit sondern 16Bit liest, und das auch noch Java-untypisch unsigned obwohl eingentlich alle anderen Primitiven signed sind.

  2. Gewöhne dir lieber mal halbwegs vernünftige Conventions an. Es mag durch aus so sein das es für dich persönlich leichter ist alles so über-extrem zu formatieren, mich persönlich stört es eher weniger da auch ich eine leichte abwandlung der Standard-Conventions nutze (jedoch nur in Bezug auf Klammer-Setzung sowie Leerzeichen zwischen Operatoren), anderen jedoch kann es extrem auf die Augen schlagen wenn man Code hat der voll von Whitespaces ist. Das unterbricht auch den Lesefluss und man läuft eher gefahr gerade bei sehr langen Zeilen zu verrutschen. Bei zwei oder drei Zeilen geht es noch, aber bei so nem 100er Block Variablen-Deklaration mit teilweise überlangen Namen kann man ganz schnell ganz wo anders landen.
    Auch kann es dadurch sehr schnell zu weiteren Fehlern kommen. Dem Compiler sind zwar Whitespaces im Source völlig egal, man läuft aber gerade bei Klammersetzung oder “loser Formatierung” wie “Einzeiler-IF ohne Klammern” gerne in die Falle das man irgendwo dann doch das eine oder andere Statement falsch stehen hat.

Sicher, jeder hat seinen Stil, aber gerade sowas wie Conventions in Java helfen dabei Code von anderen einfach zu lesen und zu verstehen da man weis wie der Code aufgebaut ist.

@casi91
durch die ready-Schleife wird es in diesem Fall wohl schwer werden mit Datenverlust, Glück im Unglück

ob doppelte Streams closen bleibt eine theoretische Frage, deren Problematik ich aber auch zustimme,
und ja, das betrifft PrintWriter genauso

insgesamt kann es sein, dass sich bei deinem Programm real nichts ändert, dennoch wichtig auf die Dinge zu achen


deutlich wird es in jedem Fall in Beispielen mit ObjectStreams,
jeder ObjectOutputStream sendet ein Anfangstoken, danach die Objekte,

gibt es auf einer Seite zwei ObjectOutputStreams für zwei Nachrichten, mag es vielleicht funktionieren, wenn der Empfänger genauso ständig neue ObjectInputStreams eröffnet…,
mit korrekt nur einen ObjectInputStream auf Gegenseite gibt es jedenfalls klare Exception, das zweite empfangene Anfangstoken unverständlich

Ich schreibs lieber noch mal drunter statt noch mal zu editieren um den Zusammenhang nicht wie ein Flummy springen zu lassen :

Das Beispiel mit den Object-Streams verdeutlich es sogar soweit das man es reproduzieren kann. Klar, wie SlaterB schon sagte ist meine Idee sehr theoretisch, beim Object-Stream wird es auf Grund der Token jedoch wirklich Praxis-relevant.
Der “Klassiker” ist die falsch Reihenfolge. Während man “normalerweise” erst Input und dann Output erstellt tritt hier bei den Object-Streams das Problem auf das die Inputs auf das Token der Outputs warten. Baut man nun Server und Client so das beide erst den Input erstellen wollen und dann den Output warten beide Seiten auf ein Token was jedoch niemals kommen wird > Deadlock.

Ich bin auch schon öfter in diese Falle geraten und habe mir daher angewöhnt immer erst den Output zu erzeugen und dann den Input, weil Daten rausschicken kann ich ja erstmal, aber wenn ich auf welche warte wird es auch mit Multi-Threading interessant.

Es mag durch aus so sein das es für dich persönlich leichter ist alles so über-extrem zu formatiere

für mich ist es schöner einen Code zu haben, bei dem die Gleichzeichen weitestgehend untereinander stehen. (Habe das damals in meiner Ausbildung allerdings in einer anderen Programmiersprache so gelernt ^.^)

ich muss gestehen, das ich gerade mit dem Wrapper auf dem Schlauch stehe.
Werde mich da nochmal einlesen und es dann testen und mich dann mit meinen Erkenntnissen melden.
Wenn jemand gerade ein schnelles Beispiel hätte, wäre ich darum aber auch nicht böse :wink: