Java NIO:Das schnelle Versenden großer Nachrichten führt zu verkürzten Paketen und Datenverlust

StackOverflow https://stackoverflow.com/questions/499070

Frage

Ich habe dieses schlimme Problem, bei dem das Senden mehrerer großer Nachrichten in schneller Folge von einem Java-Server (NIO) (unter Linux) an einen Client zu abgeschnittenen Paketen führt.Damit das Problem auftritt, müssen die Nachrichten groß sein und sehr schnell gesendet werden.Hier ist im Grunde, was mein Code tut (kein tatsächlicher Code, aber mehr oder weniger, was passiert):

//-- setup stuff: --
Charset charset = Charset.forName("UTF-8");
CharsetEncoder encoder = charset.newEncoder();
String msg = "A very long message (let's say 20KB)...";

//-- inside loop to handle incoming connections: --
ServerSocketChannel ssc = (ServerSocketChannel)key.channel();
SocketChannel sc = ssc.accept();
sc.configureBlocking(false);
sc.socket().setTcpNoDelay(true);
sc.socket().setSendBufferSize(1024*1024);

//-- later, actual sending of messages: --
for (int n=0; n<20; n++){
  ByteBuffer bb = encoder.encode(CharBuffer.wrap(msg+'\0'));
  sc.write(bb);
  bb.rewind();
}

Wenn die Pakete also lang genug sind und so schnell wie möglich gesendet werden (d. h.in einer Schleife wie dieser ohne Verzögerung), dann kommt es am anderen Ende oft so heraus:

[COMPLETE PACKET 1]
[COMPLETE PACKET 2]
[COMPLETE PACKET 3]
[START OF PACKET 4][SOME OR ALL OF PACKET 5]

Es kommt zu einem Datenverlust und die Pakete beginnen gemeinsam zu laufen, sodass der Anfang von Paket 5 (in diesem Beispiel) in derselben Nachricht ankommt wie der Anfang von Paket 4.Dabei werden die Nachrichten nicht nur abgeschnitten, sondern zusammengeführt.

Ich stelle mir vor, dass dies mit dem TCP-Puffer oder der „Fenstergröße“ zusammenhängt oder dass der Server hier Daten einfach schneller bereitstellt, als das Betriebssystem, der Netzwerkadapter oder ähnliches damit umgehen können.Aber wie kann ich das überprüfen und verhindern, dass es passiert?Wenn ich die Länge der Nachricht pro Verwendung von sc.write() reduziere, dann aber die Wiederholungen erhöhe, tritt immer noch das gleiche Problem auf.Es scheint einfach ein Problem mit der Datenmenge in kurzer Zeit zu sein.Ich sehe auch nicht, dass sc.write() Ausnahmen auslöst (ich weiß, dass ich in meinem obigen Beispiel keine Ausnahmen überprüfe, aber in meinen Tests habe).

Ich würde mich freuen, wenn ich programmgesteuert prüfen könnte, ob es noch nicht für weitere Daten bereit ist, eine Verzögerung einbauen und warten könnte, bis es bereit ist.Ich bin mir auch nicht sicher, ob "sc.socket (). SetSendBufferSize (1024*1024);" hat einen Effekt oder wenn ich dies auf der Linux -Seite der Dinge einstellen müsste.Gibt es eine Möglichkeit, einen SocketChannel wirklich „auszuspülen“?Um dieses Problem zu umgehen, könnte ich versuchen, zum Beispiel jedes Mal, wenn ich versuche, eine Nachricht von mehr als 10 KB zu senden (was in meiner Anwendung nicht so oft vorkommt), explizit ein vollständiges Senden von allem zu erzwingen, was gepuffert ist.Aber ich kenne keine Möglichkeit, das Senden des Puffers zu erzwingen (oder zu warten, bis er gesendet wurde).Vielen Dank für jede Hilfe!

War es hilfreich?

Lösung

Es gibt viele Gründe, warum sc.write () nicht einige oder alle Daten senden. Sie haben den Rückgabewert und / oder die Anzahl von Bytes im Puffer verbleibenden zu überprüfen.

for (int n=0; n<20; n++){
  ByteBuffer bb = encoder.encode(CharBuffer.wrap(msg+'\0'));
  if(sc.write(bb) > 0 && bb.remaining() == 0) {
    // all data sent
  } else {
    // could not send all data.
  }
  bb.rewind();
}

Andere Tipps

Du bist gerade nicht den Rückgabewert:

sc.write(bb);

Dies gibt die Anzahl der geschriebenen Bytes, die als die Daten in Ihrem Puffer verfügbar weniger sein könnten. Weil, wie nio Werke können Sie wahrscheinlich nur nennen verbleibende () auf Ihrem ByteBuffer zu sehen, ob es irgendwelche links.

Ich habe keine NIO Programmierung getan, aber nach dem Javadocs, sc.write () wird der gesamte ByteBuffer nicht schreiben, wenn die Socket in nicht-blockierenden Modus ist (wie bei Ihnen ist) und der Ausgabepuffer des Sockets ist voll .

Weil du so schnell schreiben, es ist sehr wahrscheinlich, dass Sie Ihre Verbindung schwemmen und Ihr Netzwerk oder Empfänger nicht mithalten kann.

  
    

würde ich glücklich sein, wenn ich programmatisch überprüfen könnte, wenn es für mehr Daten noch nicht bereit ist,

  

Sie müssen den Rückgabewert von sc.write überprüfen (), um herauszufinden, ob Ihr Ausgabepuffer voll ist.

Gehen Sie nicht davon aus, dass Sie die Kontrolle darüber haben, welche Daten in welchem ​​Paket landen.

Mehr hier: Was ist der beste Weg, einen Socket auf neue Daten zu überwachen und diese Daten dann zu verarbeiten?

Sie verwenden den Non-Blocking-Modus: sc.configureBlocking ( false );

Stellen zu blockieren true und Ihr Code sollte funktionieren, wie es ist. Anregungen von anderen hier gemacht zu überprüfen Zählung senden und Schleife wird auch funktionieren.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top