Pourquoi SocketChannel écrit-il toujours pour le montant total, même sur des sockets non bloquants?

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

Question

À l'aide de Sun Java VM 1.5 ou 1.6 sous Windows, je connecte un socket non bloquant. Je remplis ensuite un ByteBuffer avec un message à la sortie et tente de write () sur le SocketChannel.

Je m'attends à ce que l'écriture ne se termine que partiellement si la quantité à écrire est supérieure à la quantité d'espace dans la mémoire tampon de sortie TCP du socket (c'est ce que j'attends intuitivement, c'est aussi à peu près ma compréhension de la docs ), mais ce n'est pas ce qui se passe. Le write () toujours semble renvoyer le montant total écrit, même s'il s'agit de plusieurs mégaoctets (SO_SNDBUF du socket est de 8 Ko, bien moins que mon résultat de plusieurs mégaoctets message).

Un problème ici est que je ne peux pas tester le code qui gère le cas où la sortie est partiellement écrite (enregistrement d'un ensemble d'intérêts de WRITE dans un sélecteur et exécution d'un select ( ) d'attendre que le reste puisse être écrit), car ce cas ne semble jamais se produire. Qu'est-ce que je ne comprends pas?

Était-ce utile?

La solution

J'ai réussi à reproduire une situation qui pourrait être similaire à la vôtre. Ironiquement, votre destinataire consomme les données plus rapidement que vous ne les écrivez.

import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class MyServer {
  public static void main(String[] args) throws Exception {
    final ServerSocket ss = new ServerSocket(12345);
    final Socket cs = ss.accept();
    System.out.println("Accepted connection");

    final InputStream in = cs.getInputStream();
    final byte[] tmp = new byte[64 * 1024];
    while (in.read(tmp) != -1);

    Thread.sleep(100000);
  }
}



import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

public class MyNioClient {
  public static void main(String[] args) throws Exception {
    final SocketChannel s = SocketChannel.open();
    s.configureBlocking(false);
    s.connect(new InetSocketAddress("localhost", 12345));
    s.finishConnect();

    final ByteBuffer buf = ByteBuffer.allocate(128 * 1024);
    for (int i = 0; i < 10; i++) {
      System.out.println("to write: " + buf.remaining() + ", written: " + s.write(buf));
      buf.position(0);
    }
    Thread.sleep(100000);
  }
}

Si vous exécutez le serveur ci-dessus, puis tentez le client ci-dessus d'écrire 10 fragments de 128 ko de données, vous verrez que chaque opération d'écriture écrit le tampon dans son intégralité sans blocage. Cependant, si vous modifiez le serveur ci-dessus pour qu'il ne lise rien de la connexion, vous verrez que seule la première opération d'écriture sur le client écrit 128 ko, alors que toutes les écritures suivantes renverront 0 .

Sortie lorsque le serveur lit la connexion:

to write: 131072, written:  131072
to write: 131072, written:  131072
to write: 131072, written:  131072
...

Sortie lorsque le serveur ne lit pas la connexion:

to write: 131072, written:  131072
to write: 131072, written:  0
to write: 131072, written:  0
...  

Autres conseils

Je travaille avec UDP en Java et en ai vu de très "intéressants". et comportement complètement non documenté dans les affaires Java NIO en général. Le meilleur moyen de déterminer ce qui se passe est de consulter la source fournie avec Java.

Je parierais également fortement que vous pourriez trouver une meilleure implémentation de ce que vous recherchez dans n'importe quelle implémentation de machine virtuelle Java, telle que celle d'IBM, mais je ne peux pas le garantir sans regarder moi-même.

Je ne le trouve pas documenté nulle part, mais IIRC [1], send () est garanti pour a) envoyer complètement le tampon fourni ou b) échouer. Il ne terminera jamais partiellement l'envoi.

[1] J'ai écrit plusieurs implémentations de Winsock (pour Win 3.0, Win 95, Windows NT, etc.), il peut donc s'agir d'un comportement spécifique à Winsock (plutôt que des sockets génériques).

Je vais faire un grand acte de foi et supposer que le fournisseur de réseau sous-jacent pour Java est le même que pour C ... le système d’exploitation n’attribue pas seulement SO_SNDBUF pour chaque socket. Je parie que si vous placez votre code d’envoi dans une boucle for (1 100 000), vous obtiendrez éventuellement une écriture dont la valeur est inférieure à celle demandée.

Vous devriez vraiment regarder un framework NIO comme MINA ou Grizzly . J'ai utilisé MINA avec un grand succès dans un serveur de discussion en entreprise. Il est également utilisé sur le serveur de discussion Openfire . Grizzly est utilisé dans la mise en oeuvre JavaEE de Sun.

Où envoyez-vous les données? N'oubliez pas que le réseau agit en tant que tampon d'une taille au moins égale à votre SO_SNDBUF plus le SO_RCVBUF du destinataire. Ajoutez ceci à l'activité de lecture du récepteur mentionnée par Alexander et vous obtiendrez beaucoup de données.

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