95 votes

Pourquoi est-il impossible, sans tentative d'E / S, de détecter que le socket TCP a été fermé avec élégance par un homologue?

Comme suite à une récente question, je me demande pourquoi il est impossible en Java, sans tenter de lecture/écriture sur un socket TCP, afin de détecter que le socket a été fermée gracieusement par les pairs? Cela semble être le cas indépendamment du fait que l'on utilise le pré-NIO Socket ou le NIO SocketChannel.

Quand un pair gracieusement ferme une connexion TCP, les piles TCP sur les deux côtés de la connexion savoir sur le fait. Le côté serveur (celui qui lance la mise à l'arrêt) se termine dans l'état FIN_WAIT2, tandis que le côté client (celui qui n'est pas explicitement de répondre à l'arrêt) se termine dans l'état CLOSE_WAIT. Pourquoi n'est-il pas une méthode en Socket ou SocketChannel qui permet d'interroger la pile TCP pour voir si la connexion TCP sous-jacente a été résilié? Est-ce que la pile TCP n'a pas à fournir de telles informations d'état? Ou est-ce une décision de conception afin d'éviter un coûteux appel dans le noyau?

Avec l'aide des utilisateurs qui ont déjà posté quelques réponses à cette question, je crois que je vois où le problème peut venir de la. Le côté qui n'est pas explicitement fermer la connexion se termine dans l'état TCP CLOSE_WAIT ce qui signifie que la connexion est en cours de fermeture et attend du côté de délivrer son propre CLOSE de l'opération. Je suppose que c'est assez juste qu' isConnected retours true et isClosed retours false, mais pourquoi n'est-il pas quelque chose comme isClosing?

Ci-dessous sont les classes de test que l'utilisation de pré-NIO sockets. Mais des résultats identiques sont obtenus à l'aide de NIO.

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");
    Thread.sleep(5000);
    cs.close();
    System.out.println("Closed connection");
    ss.close();
    Thread.sleep(100000);
  }
}


import java.net.Socket;

public class MyClient {
  public static void main(String[] args) throws Exception {
    final Socket s = new Socket("localhost", 12345);
    for (int i = 0; i < 10; i++) {
      System.out.println("connected: " + s.isConnected() + 
        ", closed: " + s.isClosed());
      Thread.sleep(1000);
    }
    Thread.sleep(100000);
  }
}

Lorsque le test client se connecte au serveur de test, la sortie reste inchangée, même après que le serveur lance la fermeture de la connexion:

connected: true, closed: false
connected: true, closed: false
...

29voto

Matthieu Points 1603

J'ai été en utilisant les Sockets souvent, surtout avec les Sélecteurs, et bien que pas un Réseau OSI expert, de ma compréhension, appelant shutdownOutput() sur un Socket envoie quelque chose sur le réseau (FIN) qui réveille mon Sélecteur de l'autre côté (même comportement en langage C). Ici vous avez de détection: la détection d'une opération de lecture qui va échouer lorsque vous essayez-le.

Dans le code que vous donnez, il n'y a pas de gracieux déconnexion: il suffit de clôture à la fois d'entrée et de sortie des flux, sans possibilités de lecture des données qui pourraient être disponibles (et donc de perdre de la mer). Je ne crois pas que le Java Socket.close() méthode effectue gracieux déconnexion. De ce que j'ai fait en C, et maintenant en Java, gracieusement equipés de sectionneur un Socket doit être fait comme ceci:

  1. L'arrêt de prise de sortie (envoie FIN à l'autre bout, c'est la dernière chose qui ne sera jamais envoyé par cette prise)
  2. Vide de la réception de la mémoire tampon jusqu'à ce que nous recevons la réponse-FIN de l'autre extrémité (comme il permet de détecter la FIN, il faudra passer par le même gracieux diconnection processus). Ceci est important sur certains OS comme ils n'ont pas fait fermer le socket tant que l'un de ses tampon contient encore des données. On les appelle des "fantômes" socket et l'utilisation de la descripteur de nombres dans l'OS (qui ne pourrait pas être plus un problème avec le système d'exploitation moderne ;))
  3. Fermer le socket

Comme le montre le fragment de code Java suivant:

public void gracefulDisconnect(Socket sok) {
    InputStream is = sok.getInputStream();
    sok.shutdownOutput(); // Sends the 'FIN' on the network
    while (is.read() >= 0) ; // "read()" returns '-1' when the 'FIN' is reached
    sok.close(); // Now we can close the Socket
}

Bien sûr que les deux parties doivent utiliser le même gracieux déconnexion, ou en envoyant une partie peut toujours être l'envoi de suffisamment de données pour garder l' while boucle occupé...

@WarrenDew l'a souligné dans son commentaire, en écartant les données dans le programme (couche application) induit une non-gracieux déconnexion à la couche application: bien que toutes les données ont été reçues à la couche TCP ( while boucle), elles sont ignorées.

18voto

Eugene Yokota Points 43213

Je pense que c'est plus une programmation socket question. Java est juste après la prise de la programmation de la tradition.

De Wikipedia:

TCP fournit fiable, ordonné un flux d'octets à partir d'un programme sur un ordinateur à l'autre programme sur un autre ordinateur.

Une fois la poignée de main est fait, TCP ne fait aucune distinction entre les deux points d'extrémité (client et serveur). Le terme de "client" et "serveur" est principalement pour des raisons de commodité. Ainsi, le "serveur" pourrait être l'envoi de données et le "client" pourrait être l'envoi de certaines autres données à l'autre simultanément.

Le terme "Proche" est également trompeuse. Il n'y a qu'FIN de la déclaration, ce qui signifie "je ne vais pas vous envoyer plus de choses." Mais cela ne signifie pas qu'il n'y a pas de paquets en vol, ou de l'autre n'a plus rien à dire. Si vous implémentez un courrier escargot que la couche liaison de données, ou si votre paquet a voyagé à différents parcours, il est possible que le récepteur reçoit les paquets dans le bon ordre. TCP sait comment résoudre ce problème pour vous.

Vous aussi, comme un programme, peut ne pas avoir le temps de garder le contrôle de ce qui est dans la mémoire tampon. Donc, à votre convenance, vous pouvez vérifier ce qui est dans la mémoire tampon. Dans l'ensemble, prise de courant mise en œuvre n'est pas si mal. Si fait, il y avait isPeerClosed(), c'est extra appel que vous avez à faire chaque fois que vous voulez l'appeler à lire.

11voto

Mike Dimmick Points 6112

Le sous-jacent API sockets ne dispose pas d'une telle notification.

L'envoi de pile TCP de ne pas envoyer la FIN peu jusqu'à ce que le dernier paquet de toute façon, donc il pourrait y avoir beaucoup de données mises en mémoire tampon à partir de quand l'envoi de la demande logiquement fermé sa prise avant que les données sont encore envoyés. De même, les données du tampon parce que le réseau est plus rapide que la réception de la demande (je ne sais pas, peut-être vous êtes le relais sur une connexion plus lente) pourrait être important pour le récepteur et vous ne voudriez pas la réception de la demande de la jeter tout simplement parce que la FIN de bits a été reçu par la pile.

8voto

Alexander Points 4298

Depuis aucune réponse jusqu'à présent répondre pleinement à la question, je suis résumant ma compréhension actuelle de la question.

Lorsqu'une connexion TCP est établie et un peer appels close() ou shutdownOutput() sur son support, le support de l'autre côté de la connexion des transitions en CLOSE_WAIT de l'état. En principe, il est possible de trouver de la pile TCP si une prise en CLOSE_WAIT état sans appel read/recv (par exemple, getsockopt() sous Linux: http://www.developerweb.net/forum/showthread.php?t=4395), mais ce n'est pas portable.

Java Socket de la classe semble être conçu pour fournir une abstraction comparable à un BSD sockets TCP, probablement parce que c'est le niveau d'abstraction auquel les gens sont habitués à la programmation d'applications TCP/IP. Sockets BSD sont une généralisation de soutien sockets autres que simplement de l'INET (par exemple, TCP), de sorte qu'ils ne fournissent pas un portable moyen de recherche de la TCP état d'un socket.

Il n'y a pas de méthode comme isCloseWait() parce que les gens utilisés pour la programmation d'applications TCP au niveau de l'abstraction offerte par sockets BSD ne pas s'attendre à Java à fournir toutes les méthodes supplémentaires.

7voto

Uncle Per Points 41

Détecter si le côté distant de un (TCP) de connexion de socket a fermé peut être fait avec le java.net.Socket.sendUrgentData(int) de la méthode et de la capture de la IOException il jette si la distance est en baisse. Cela a été testé entre Java-Java, et Java-C.

Cela évite le problème de la conception du protocole de communication à utiliser une sorte de ping mécanisme. En désactivant OOBInline sur un socket (setOOBInline(faux), toutes LES données reçues sont ignorés, mais LES données peuvent toujours être envoyés. Si la distance est fermé, une réinitialisation de la connexion est tentée, échoue et provoque des IOException à être jetés.

Si vous utilisez LES données de votre protocole, votre kilométrage peut varier.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X