Java NIO: l'envoi rapide de messages volumineux entraîne des paquets tronqués et une perte de données

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

Question

J'ai ce problème désagréable: l'envoi de plusieurs messages volumineux en succession rapide d'un serveur Java (NIO) (sous Linux) à un client entraîne des paquets tronqués. Les messages doivent être volumineux et envoyés très rapidement pour que le problème se produise. Voici en gros ce que fait mon code (pas le code proprement dit, mais plus ou moins ce qui se passe):

//-- 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();
}

Donc, si les paquets sont assez longs et envoyés aussi rapidement que possible (c'est-à-dire dans une boucle comme celle-ci sans délai), alors, à l'autre extrémité, cela donne souvent un résultat similaire:

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

Il y a une perte de données et les paquets commencent à s'exécuter ensemble, de sorte que le début du paquet 5 (dans cet exemple) arrive dans le même message que le début du paquet 4. Il ne s'agit pas seulement de tronquer, mais également d'exécuter les messages ensemble. .

J'imagine que cela est lié au tampon TCP ou à la " taille de la fenêtre " ;, ou que le serveur ici fournit simplement des données plus rapidement que le système d'exploitation, la carte réseau ou autre, peut gérer il. Mais comment puis-je vérifier et empêcher que cela se produise? Si je réduis la longueur du message par utilisation de sc.write (), mais que j'augmente le nombre de répétitions, le même problème persiste. Cela semble simplement être un problème avec la quantité de données dans un court laps de temps. Je ne vois pas que sc.write () lève des exceptions non plus (je sais que dans l'exemple ci-dessus, je ne vérifie pas, mais j'ai dans mes tests).

Je serais heureux si je pouvais vérifier par programme s'il n'est pas prêt pour plus de données, mettre un délai et attendre qu'il soit prêt. Je ne suis pas sûr non plus que & "; Sc.socket (). SetSendBufferSize (1024 * 1024); &"; a un effet, ou si je devrais ajuster cela du côté de Linux. Existe-t-il un moyen de vraiment & "Purger &"; un SocketChannel? En guise de solution de contournement, je pourrais essayer de forcer explicitement l'envoi complet de tout ce qui est mis en mémoire tampon chaque fois que j'essaie d'envoyer un message de plus de 10 Ko, par exemple (ce qui n'est pas souvent le cas dans ma candidature). Mais je ne connais aucun moyen de forcer un envoi du tampon (ou d'attendre qu'il soit envoyé). Merci pour toute aide!

Était-ce utile?

La solution

Il existe de nombreuses raisons pour lesquelles sc.write () n'envoie pas tout ou partie des données. Vous devez vérifier la valeur de retour et / ou le nombre d'octets restant dans la mémoire tampon.

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();
}

Autres conseils

Vous ne vérifiez pas la valeur de retour de:

sc.write(bb);

Ceci renvoie le nombre d'octets écrits, qui peuvent être inférieurs aux données disponibles dans votre tampon. En raison de la façon dont fonctionne nio, vous pouvez probablement appeler le paramètre restant () sur votre octet tampon pour voir s’il en reste.

Je n'ai pas fait de programmation NIO, mais selon les Javadocs, sc.write () n'écrira pas tout le ByteBuffer si le SocketChannel est en mode non bloquant (comme le vôtre) et le tampon de sortie du socket est saturé. .

Parce que vous écrivez si rapidement, il est très probable que vous inondiez votre connexion et que votre réseau ou votre récepteur ne puisse pas vous suivre.

  
    

Je serais heureux si je pouvais vérifier par programme s'il n'est pas encore prêt pour plus de données

  

Vous devez vérifier la valeur de retour de sc.write () pour savoir si votre tampon de sortie est plein.

Ne supposez pas que vous avez le contrôle sur les données qui finissent dans quel paquet.

Plus ici: Quel est le meilleur moyen de surveiller les données d'un socket, puis de les traiter?

Vous utilisez le mode non bloquant: sc.configureBlocking ( false );

Définissez le blocage sur true et votre code devrait fonctionner tel quel. Les suggestions faites par d’autres ici pour vérifier le nombre d’envois et la boucle fonctionneront également.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top