API Java socket: comment savoir si une connexion a été fermée?

92

Je rencontre des problèmes avec l'API de socket Java. J'essaye d'afficher le nombre de joueurs actuellement connectés à mon jeu. Il est facile de déterminer quand un joueur s'est connecté. Cependant, il semble inutilement difficile de déterminer quand un joueur s'est déconnecté à l'aide de l'API socket.

L'appel isConnected()sur une socket qui a été déconnectée à distance semble toujours revenir true. De même, appeler isClosed()une socket qui a été fermée à distance semble toujours revenir false. J'ai lu que pour déterminer réellement si une socket a été fermée ou non, les données doivent être écrites dans le flux de sortie et une exception doit être interceptée. Cela semble être une manière vraiment impure de gérer cette situation. Nous aurions juste constamment à spammer un message indésirable sur le réseau pour savoir quand un socket s'est fermé.

N 'y a-t-il pas une autre solution?

Dan Brouwer
la source

Réponses:

183

Il n'y a pas d'API TCP qui vous indiquera l'état actuel de la connexion. isConnected()et isClosed()vous indiquer l'état actuel de votre socket . Pas la même chose.

  1. isConnected()vous indique si vous avez connecté cette prise . Vous avez, donc il retourne vrai.

  2. isClosed()vous indique si vous avez fermé ce socket. Jusqu'à ce que vous ayez, il renvoie faux.

  3. Si le pair a fermé la connexion de manière ordonnée

    • read() renvoie -1
    • readLine() Retour null
    • readXXX()jette EOFExceptionpour tout autre XXX.

    • Une écriture lèvera un IOException: 'connexion réinitialisée par l'homologue', éventuellement, sous réserve de délais de mise en mémoire tampon.

  4. Si la connexion a été interrompue pour une autre raison, une écriture lèvera IOExceptionéventuellement un, comme ci-dessus, et une lecture peut faire la même chose.

  5. Si l'homologue est toujours connecté mais n'utilise pas la connexion, un délai de lecture peut être utilisé.

  6. Contrairement à ce que vous pouvez lire ailleurs, ClosedChannelExceptionne vous dit pas cela. [Ni l'un ni l'autre SocketException: socket closed.] Cela vous indique seulement que vous avez fermé le canal, puis que vous avez continué à l'utiliser. En d'autres termes, une erreur de programmation de votre part. Cela n'indique pas une connexion fermée .

  7. À la suite de certaines expériences avec Java 7 sur Windows XP, il apparaît également que si:

    • vous sélectionnez sur OP_READ
    • select() renvoie une valeur supérieure à zéro
    • l'associé SelectionKeyest déjà invalide ( key.isValid() == false)

    cela signifie que le pair a réinitialisé la connexion. Cependant, cela peut être propre à la version ou à la plate-forme JRE.

Marquis de Lorne
la source
18
Il est difficile de croire que le protocole TCP, qui est orienté connexion, ne peut même pas connaître le statut de sa connexion ... Est-ce que les gars qui proposent ce protocole conduisent leurs voitures les yeux fermés?
PedroD
31
@PedroD Au contraire: c'était délibéré. Les suites de protocoles précédentes telles que SNA avaient une «tonalité». TCP a été conçu pour survivre à une guerre nucléaire et, plus trivialement, à des baisses et des hausses de routeur: d'où l'absence totale de quelque chose comme une tonalité de numérotation, l'état de la connexion, etc. et c'est aussi pourquoi TCP keepalive est décrit dans les RFC comme une fonctionnalité controversée, et pourquoi il est toujours désactivé par défaut. TCP est toujours avec nous. SNA? IPX? ISO? Ne pas. Ils ont bien compris.
Marquis de Lorne
3
Je ne pense pas que ce soit une bonne excuse pour nous cacher ces informations. Savoir que la connexion a été perdue ne signifie pas nécessairement que le protocole est moins résistant aux pannes, cela dépend toujours de ce que nous faisons avec ces connaissances ... Pour moi, la méthode isBound et isConnected de java sont de pures méthodes simulées, elles n'ont aucune utilité , mais expriment le besoin d'un écouteur d'événement de connexion ... Mais je le répète: savoir que la connexion a été perdue n'empire pas le protocole. Maintenant, si ces protocoles que vous dites ont tué la connexion dès qu'ils ont détecté qu'elle était perdue, c'est une autre histoire.
PedroD
21
@Pedro Vous ne comprenez pas. Ce n'est pas une «excuse». Il n'y a aucune information à retenir. Il n'y a pas de tonalité. TCP ne sait pas si la connexion a échoué jusqu'à ce que vous essayiez d'y faire quelque chose. C'était le critère de conception fondamental.
Marquis de Lorne
2
@EJP merci pour votre réponse. Savez-vous peut-être comment vérifier si un socket s'est fermé sans "blocage"? L'utilisation de read ou readLine va bloquer. Existe-t-il un moyen de se rendre compte que si l'autre socket s'est fermée avec la méthode disponible, elle semble retourner 0au lieu de -1.
insumity
9

Il est de pratique générale dans divers protocoles de messagerie de continuer à battre les uns les autres (continuer à envoyer des paquets ping), le paquet n'a pas besoin d'être très volumineux. Le mécanisme de sondage vous permettra de détecter le client déconnecté avant même que TCP ne le comprenne en général (le délai d'expiration TCP est beaucoup plus élevé) Envoyez une sonde et attendez par exemple 5 secondes pour une réponse, si vous ne voyez pas la réponse pour disons 2-3 sondes suivantes, votre lecteur est déconnecté.

Aussi, question connexe

Kalpak Gadre
la source
6
C'est une «pratique générale» dans certains protocoles. Je note que HTTP, le protocole d'application le plus utilisé sur la planète, n'a pas d'opération PING.
Marquis de Lorne
2
@ user207421 car HTTP / 1 est un protocole one-shot. Vous envoyez une demande, vous recevez une réponse. Et vous avez terminé. Le socket est fermé. L'extension Websocket a une opération PING, tout comme HTTP / 2.
Michał Zabielski
J'ai recommandé de battre le cœur avec un seul octet si possible :)
Stefan Reich
@ MichałZabielski C'est parce que les concepteurs de HTTP ne l'ont pas spécifié. Vous ne savez pas pourquoi. HTTP / 1.1 n'est pas un protocole unique. Le socket n'est pas fermé: les connexions HTTP sont persistantes par défaut. FTP, SMTP, POP3, IMAP, TLS, ... n'ont pas de pulsations.
Marquis of Lorne
2

Je vois l'autre réponse qui vient d'être publiée, mais je pense que vous êtes interactif avec les clients qui jouent à votre jeu, donc je peux proposer une autre approche (alors que BufferedReader est certainement valable dans certains cas).

Si vous le souhaitez ... vous pouvez déléguer la responsabilité de «l'enregistrement» au client. C'est-à-dire que vous auriez une collection d'utilisateurs connectés avec un horodatage sur le dernier message reçu de chacun ... si un client expire, vous forceriez une réinscription du client, mais cela conduit au devis et à l'idée ci-dessous.

J'ai lu cela pour déterminer si une socket a été fermée ou non, des données doivent être écrites dans le flux de sortie et une exception doit être interceptée. Cela semble être une manière vraiment impure de gérer cette situation.

Si votre code Java n'a pas fermé / déconnecté le socket, comment seriez-vous averti que l'hôte distant a fermé votre connexion? En fin de compte, votre try / catch fait à peu près la même chose qu'un poller écoutant les événements sur le socket ACTUAL. Considérer ce qui suit:

  • votre système local pourrait fermer votre socket sans vous avertir ... c'est juste l'implémentation de Socket (c'est-à-dire qu'il n'interroge pas le matériel / pilote / firmware / quoi que ce soit pour le changement d'état).
  • new Socket (Proxy p) ... il y a plusieurs parties (6 points de terminaison vraiment) qui pourraient fermer la connexion sur vous ...

Je pense que l'une des caractéristiques des langages abstraits est que vous êtes abstraits de la minutie. Pensez au mot clé using en C # (try / finally) pour SqlConnection ou autre ... c'est juste le coût de faire des affaires ... Je pense que try / catch /

Scottley
la source
Votre système local ne peut pas «fermer votre socket», avec ou sans notification. Votre question commençant par "si votre code Java n'a pas fermé / déconnecté le socket ...?" n'a aucun sens non plus.
Marquis of Lorne
1
Qu'est-ce qui empêche exactement le système (Linux par exemple) de forcer la fermeture du socket? Est-il toujours possible de le faire à partir de 'gdb' en utilisant la commande 'call close'?
Andrey Lebedenko
@AndreyLebedenko Rien «ne l'empêche exactement», mais il ne le fait pas.
Marquis of Lorne
@EJB qui fait alors?
Andrey Lebedenko
@AndreyLebedenko Personne ne le fait. Seule l'application peut fermer ses propres sockets, sauf si elle se ferme sans le faire, auquel cas le système d'exploitation nettoie.
Marquis of Lorne
1

Je pense que c'est la nature des connexions TCP, en ce sens qu'il faut environ 6 minutes de silence en transmission avant de conclure que la connexion est terminée! Je ne pense donc pas que vous puissiez trouver une solution exacte à ce problème. La meilleure façon est peut-être d'écrire du code pratique pour deviner quand le serveur doit supposer qu'une connexion utilisateur est fermée.

Ehsan Khodarahmi
la source
2
Cela peut prendre beaucoup plus que cela, jusqu'à deux heures.
Marquis of Lorne
0

Comme le dit @ user207421, il n'y a aucun moyen de connaître l'état actuel de la connexion en raison du modèle d'architecture de protocole TCP / IP. Le serveur doit donc vous remarquer avant de fermer la connexion ou vous la vérifiez vous-même.
Voici un exemple simple qui montre comment savoir que le socket est fermé par le serveur:

sockAdr = new InetSocketAddress(SERVER_HOSTNAME, SERVER_PORT);
socket = new Socket();
timeout = 5000;
socket.connect(sockAdr, timeout);
reader = new BufferedReader(new InputStreamReader(socket.getInputStream());
while ((data = reader.readLine())!=null) 
      log.e(TAG, "received -> " + data);
log.e(TAG, "Socket closed !");
ucMedia
la source
0

J'ai fait face à un problème similaire. Dans mon cas, le client doit envoyer des données périodiquement. J'espère que vous avez la même exigence. Ensuite, j'ai défini SO_TIMEOUT socket.setSoTimeout(1000 * 60 * 5);qui est lancé java.net.SocketTimeoutExceptionlorsque le temps spécifié est expiré. Ensuite, je peux détecter facilement un client mort.

hurelhuyag
la source
-2

C'est comme ça que je gère ça

 while(true) {
        if((receiveMessage = receiveRead.readLine()) != null ) {  

        System.out.println("first message same :"+receiveMessage);
        System.out.println(receiveMessage);      

        }
        else if(receiveRead.readLine()==null)
        {

        System.out.println("Client has disconected: "+sock.isClosed()); 
        System.exit(1);
         }    } 

si le result.code == null

Petar Ceho
la source
Vous n'avez pas besoin d'appeler readLine()deux fois. Vous savez déjà qu'il était nul dans le elsebloc.
Marquis of Lorne
-4

Sous Linux, lors de l'écriture () dans un socket que l'autre côté, inconnu de vous, fermé provoquera un signal / exception SIGPIPE comme vous voulez l'appeler. Cependant, si vous ne voulez pas être attrapé par le SIGPIPE, vous pouvez utiliser send () avec le drapeau MSG_NOSIGNAL. L'appel send () retournera avec -1 et dans ce cas vous pouvez vérifier errno qui vous dira que vous avez essayé d'écrire un tube cassé (dans ce cas une socket) avec la valeur EPIPE qui selon errno.h équivaut à 32. En réaction à l'EPIPE, vous pouvez revenir en arrière et essayer de rouvrir le socket et essayer de renvoyer vos informations.

MikeK
la source
L' send()appel renverra -1 uniquement si les données sortantes ont été mises en mémoire tampon suffisamment longtemps pour que les temporisations d'envoi expirent. Cela ne se produira presque certainement pas lors du premier envoi après la déconnexion, en raison de la mise en mémoire tampon aux deux extrémités et de la nature asynchrone de send()sous le capot.
Marquis of Lorne