Socket NIO-Server/Client-Abgeschnittene Nachrichten (Strings)

Braumeister

Mitglied
Hallo Forum!

Ich habe ein Problem mit meinem NIO-Server bzw. Client.
Ich nutze momentan eine Echo-Server-Beispiel-Implementierung zur
Übertragung von XML-Dokumenten zwischen Client und Server.

Bisher hatte ich keine Probleme damit. Als jedoch die Dokumente ETWAS größer wurden
ist die Übertragung fehlerhaft geworden, sodass der Client sehr oft nur abgeschnittene
XMLs eingelesen hat, was dann natürlich zu Parse-Exceptions führte.
Ich rede hier von Dokumentgrößen von 4 KB, was nicht wirklich groß ist 😉.
Dennoch scheint dieses Problem bei kleinen Dokumenten nicht aufzutauchen.

Ich habe mit entspr. auch schon die Anz. der übertragenen Bytes ausgeben lassen, was das Problem
bestätigte. Es scheint so, als ob die Übertragung durch etwas unterbrochen wird o.ä. .

Auch lag es nicht an Buffer-Größen, die ich auch mal nach oben geschraubt habe.

Das merkwürdige ist halt, dass es "nur" zu 90% passiert, also nicht IMMER.

Hat jemand dafür eine Erklärung?
Bei Bedarf kann ich den Source-Code auch schicken.

Gruß,
Braumeister
 
Nun, der Fehler liegt im Example selber...

Die Daten werden in Datenpacketen uebers Netz verschickt. Bei grossen Datenmengen werden diese in mehrere Packete aufgeteilt.
Code:
socketChannel.read()
liest wahrscheinlich nur ein Packet nach dem andern, somit muessen die Daten ueber eine Schleife eingelesen werden.
Kurze Strings wie im Beispiel werden natuerlich nicht unterteilt und in einem Zug uebermittelt, deshalb funktioniert es da.

Mit der folgenden Client sollte dein Program funktionieren. Im Server solltest du die entsprechende Methode auch fixen, wenn du die Daten vom Client in einem Stueck verarbeiten willst.

Java:
	private void read(SelectionKey key) throws IOException {
		SocketChannel socketChannel = (SocketChannel) key.channel();

		// Clear out our read buffer so it's ready for new data
		this.readBuffer.clear();

		// Attempt to read off the channel
		int numRead, length = 0;
		try {
			while ((numRead = socketChannel.read(this.readBuffer)) > 0) {
				System.out.println("read: " + numRead);
				length += numRead;
			}

		} catch (IOException e) {
			// The remote forcibly closed the connection, cancel
			// the selection key and close the channel.
			key.cancel();
			socketChannel.close();
			return;
		}

		if (numRead == -1) {
			// Remote entity shut the socket down cleanly. Do the
			// same from our end and cancel the channel.
			key.channel().close();
			key.cancel();
			return;
		}

		// Handle the response
		this.handleResponse(socketChannel, this.readBuffer.array(), length);
	}
 
Danke für die Antwort!

In der Tat denke ich, dass das sinnig ist.
Allerdings trat der Fehler auch dabei auf.

Der Knackpunkt ist halt, dass es ab einer gewissen Datenmenge oft passiert,
aber eben nicht IMMER. Läge es daran, dass ich nur das erste Packet einlesen würde,
so hätte ich den Fehler jedes Mal.

Ich habe jedoch offenbar einen kleinen Workaround hinbekommen.

Für den Fall, dass der Fehler vorkommen würde, sieht der Ablauf womöglich so aus:
1) Einlesen des ersten Packetes
2) Keine Bytes mehr zum Einlesen -> Beenden
3) Etwas später wären aber die restlichen Bytes zum Einlesen bereit, die er aber nicht mehr
einliest.

Mein Workaround liegt nun einfach darin, dass ich den Thread 50 ms warten lasse, bevor er mit
der nächsten Leseoperation beginnt.
Das führt mich zwar nicht auf den Grund des Problems, reicht aber evtl. , damit ich mit der eigentlichen App weitermachen kann.

Ich bin aber weiterhin an der Lösung des Problems interessiert, da solche "Lösungen" oft nur kurze Gültigkeit haben.

Gruß,
Braumeister
 
Habe mit nio noch nicht direkt gearbeitet, sondern nur mit der socket api aber:
Ruft der Client flush oder sowas auf, sonst könnte das erklären warum ein teil der daten teilweise erst verzögert kommt?
In dieser Verbindung könnte das nur sporadische auftreten mit dem Nagles algorithmus zusammenhängen.
Zur Lösung:

Versuch mal das lesen so zu gestalten, das der ließt, bis die Gegenseite den stream schließt, bzw ein Übertragung beendet sendet (zb eine Kette in xml nicht zugelassener Sonderzeichen).
Es kann ja durchaus vorkommen, das noch keine neuen Daten da sind, weil die noch auffen Weg im netz sind und somit keine im lesebuffer sind sprich verfügbar, aber es halt nicht beended ist.
 
Ich hab mir das Program nochmal angesehen und den 'anderen' Fehler gefunden.

Die Daten werden uebers Netz in mehreren Packete verteilt verschickt (haengt vom Netzwerk, Latency,usw. ab). Im guenstigsten Fall kann er es in einem Packet verschicken, dann brauchst du auch nur einmal zu lesen. Das Lesen ist aber auf jeden Fall auf die Groesse des Buffers beschraenkt. Wenn deine Datei groesser als der 8192 ByteBuffer wirst du immer mehrmals lesen muessen.

Das 'Problem' im Beispiel ist, dass in der Methode
Code:
handleResponse(...)
nach dem ersten Lesen der Channel geschlossen wird, somit werden die restlichen Daten nie aus dem Channel ausgelesen. Wenn du
Code:
socketChannel.close();
und
Code:
socketChannel.keyFor(this.selector).cancel();
auskommentierst funktioniert das Original ohne Probleme.

Der EchoWorker gibt einfach zurueck was er gelesen hat. Hier spielt es keine Rolle ob die Uebertragung vollstaendig war oder nicht. Es schickt einfach die Datenfetzen welche er erhalten hat zurueck an den Client. Somit wird auch nicht festgestellt wann de Uebertragung abgeschlossen wurde. Das brauchst du natuerlich wenn du wissen willst ob dein File nun vollstaendig uebertragen wurde.


PS. Die 'queues' zwischen Server und Worker sehen komisch aus. In einer Queue kann meiner Meinung nach immer nur 1 ServerDataEvent drin stehen, da der Code in einem
Code:
synchronized
Block ausgefuehrt wird. Sinnvoller waere hier eine BlockingQueue. Dasselbe mit der 'pendingChanges' List..
 

Zurück
Oben