Pour faire suite à une question récente , je me demande pourquoi il est impossible en Java, sans tenter de lire / écrire sur une socket TCP, de détecter que la socket a été gracieusement fermée par le pair? Cela semble être le cas, que l'on utilise le pré-NIO Socket
ou le NIO SocketChannel
.
Lorsqu'un homologue ferme en douceur une connexion TCP, les piles TCP des deux côtés de la connexion sont au courant du fait. Le côté serveur (celui qui initie l'arrêt) se retrouve dans l'état FIN_WAIT2
, tandis que le côté client (celui qui ne répond pas explicitement à l'arrêt) finit dans l'état CLOSE_WAIT
. Pourquoi n'y a-t-il pas de méthode dans Socket
ou SocketChannel
qui puisse interroger la pile TCP pour voir si la connexion TCP sous-jacente a été interrompue? Est-ce que la pile TCP ne fournit pas de telles informations d'état? Ou est-ce une décision de conception pour éviter un appel coûteux dans le noyau?
Avec l'aide des utilisateurs qui ont déjà publié des réponses à cette question, je pense voir d'où vient le problème. Le côté qui ne ferme pas explicitement la connexion se retrouve dans l'état TCP, CLOSE_WAIT
ce qui signifie que la connexion est en train de s'arrêter et attend que le côté émette sa propre CLOSE
opération. Je suppose que c'est assez juste que les isConnected
retours true
et les isClosed
retours false
, mais pourquoi n'y a-t-il pas quelque chose comme ça isClosing
?
Vous trouverez ci-dessous les classes de test qui utilisent des sockets pré-NIO. Mais des résultats identiques sont obtenus en utilisant 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 client de test se connecte au serveur de test, la sortie reste inchangée même après que le serveur a initié l'arrêt de la connexion:
connected: true, closed: false
connected: true, closed: false
...
la source
Socket.close
n'est pas une fin gracieuse.Réponses:
J'utilise souvent Sockets, principalement avec des sélecteurs, et bien que ce ne soit pas un expert en OSI réseau, d'après ce que je comprends, appeler
shutdownOutput()
un socket envoie en fait quelque chose sur le réseau (FIN) qui réveille mon sélecteur de l'autre côté (même comportement en C Langue). Ici, vous avez la détection : détecter réellement une opération de lecture qui échouera lorsque vous l'essayez.Dans le code que vous donnez, la fermeture du socket arrêtera les flux d'entrée et de sortie, sans possibilité de lire les données qui pourraient être disponibles, donc de les perdre. La
Socket.close()
méthode Java effectue une déconnexion «gracieuse» (contrairement à ce que je pensais au départ) en ce que les données laissées dans le flux de sortie seront envoyées suivies d'un FIN pour signaler sa fermeture. Le FIN sera ACK par l'autre côté, comme n'importe quel paquet régulier 1 .Si vous devez attendre que l'autre côté ferme son socket, vous devez attendre son FIN. Et pour cela, vous devez détecter
Socket.getInputStream().read() < 0
, ce qui signifie que vous ne devriez pas fermer votre prise, car il fermer sonInputStream
.D'après ce que j'ai fait en C, et maintenant en Java, la réalisation d'une telle fermeture synchronisée devrait se faire comme ceci:
read()
et détecter la télécommandeclose()
InputStream
jusqu'à ce que nous recevions la réponse-FIN de l'autre extrémité (comme il détectera le FIN, il passera par le même processus de déconnexion gracieux). Ceci est important sur certains OS car ils ne ferment pas réellement le socket tant que l'un de ses tampons contient encore des données. Ils sont appelés socket "fantôme" et utilisent des numéros de descripteurs dans le système d'exploitation (ce qui pourrait ne plus être un problème avec le système d'exploitation moderne)Socket.close()
ou en fermant sonInputStream
ouOutputStream
)Comme indiqué dans l'extrait de code Java suivant:
Bien sûr, les deux côtés doivent utiliser la même méthode de fermeture, ou la partie émettrice peut toujours envoyer suffisamment de données pour garder la
while
boucle occupée (par exemple si la partie émettrice envoie uniquement des données et ne lit jamais pour détecter la fin de la connexion. Ce qui est maladroit, mais vous pourriez ne pas avoir le contrôle sur cela).Comme @WarrenDew l'a souligné dans son commentaire, la suppression des données dans le programme (couche application) induit une déconnexion non gracieuse au niveau de la couche application: bien que toutes les données aient été reçues au niveau de la couche TCP (la
while
boucle), elles sont rejetées.1 : De " Fundamental Networking in Java ": voir fig. 3.3 p.45, et l'ensemble §3.7, pp 43-48
la source
socket.close()
est "brutal" dans la mesure où il ne respecte pas cette signalisation Client / Serveur. Le serveur serait averti d'une déconnexion client uniquement lorsque son propre tampon de sortie de socket est plein (car aucune donnée n'est acquittée par l'autre côté, qui a été fermé).read()
renvoie -1 à la fin du flux, et continue de le faire aussi souvent que vous l'appelez. ACread()
ourecv()
renvoie zéro à la fin du flux, et continue de le faire aussi souvent que vous l'appelez.Je pense que c'est plus une question de programmation de socket. Java suit simplement la tradition de programmation de socket.
De Wikipedia :
Une fois la négociation effectuée, TCP ne fait aucune distinction entre deux points d'extrémité (client et serveur). Les termes «client» et «serveur» sont principalement utilisés pour des raisons de commodité. Ainsi, le "serveur" pourrait envoyer des données et le "client" pourrait s'envoyer d'autres données simultanément.
Le terme «Fermer» est également trompeur. Il n'y a que la déclaration FIN, ce qui signifie "Je ne vais plus vous envoyer de trucs." Mais cela ne veut pas dire qu'il n'y a pas de paquets en vol, ou que l'autre n'a plus rien à dire. Si vous implémentez le courrier postal comme couche de liaison de données, ou si votre paquet a parcouru des itinéraires différents, il est possible que le destinataire reçoive les paquets dans le mauvais ordre. TCP sait comment résoudre ce problème pour vous.
De plus, en tant que programme, vous n'aurez peut-être pas le temps de vérifier ce qui se trouve dans la mémoire tampon. Ainsi, à votre convenance, vous pouvez vérifier ce qu'il y a dans la mémoire tampon. Dans l'ensemble, l'implémentation actuelle des sockets n'est pas si mauvaise. S'il y avait réellement isPeerClosed (), c'est un appel supplémentaire que vous devez faire chaque fois que vous voulez appeler read.
la source
L'API sockets sous-jacente n'a pas une telle notification.
De toute façon, la pile TCP émettrice n'enverra pas le bit FIN avant le dernier paquet, il peut donc y avoir beaucoup de données mises en mémoire tampon à partir du moment où l'application d'envoi a fermé logiquement son socket avant même que ces données ne soient envoyées. De même, les données mises en mémoire tampon parce que le réseau est plus rapide que l'application réceptrice (je ne sais pas, peut-être que vous les relayez via une connexion plus lente) pourraient être importantes pour le récepteur et vous ne voudriez pas que l'application réceptrice la rejette simplement parce que le bit FIN a été reçu par la pile.
la source
Étant donné qu'aucune des réponses à ce jour ne répond entièrement à la question, je résume ma compréhension actuelle du problème.
Lorsqu'une connexion TCP est établie et qu'un pair appelle
close()
oushutdownOutput()
sur son socket, le socket de l'autre côté de la connexion passe à l'CLOSE_WAIT
état. En principe, il est possible de savoir à partir de la pile TCP si un socket est enCLOSE_WAIT
état sans appelerread/recv
(par exemple,getsockopt()
sous Linux: http://www.developerweb.net/forum/showthread.php?t=4395 ), mais ce n'est pas portable.Java
Socket
classe semble être conçue pour fournir une abstraction comparable à une socket BSD TCP, probablement parce que c'est le niveau d'abstraction auquel les gens sont habitués lors de la programmation d'applications TCP / IP. Les sockets BSD sont une généralisation prenant en charge les sockets autres que les sockets INET (par exemple, TCP), donc ils ne fournissent pas un moyen portable de découvrir l'état TCP d'un socket.Il n'y a pas de méthode comme
isCloseWait()
parce que les gens habitués à programmer des applications TCP au niveau d'abstraction offert par les sockets BSD ne s'attendent pas à ce que Java fournisse des méthodes supplémentaires.la source
isCloseWait()
car elle n'est pas prise en charge sur toutes les plates-formes.Détecter si le côté distant d'une connexion de socket (TCP) est fermé peut être effectué avec la méthode java.net.Socket.sendUrgentData (int) et intercepter l'exception IOException qu'elle émet si le côté distant est en panne. Cela a été testé entre Java-Java et Java-C.
Cela évite le problème de la conception du protocole de communication pour utiliser une sorte de mécanisme de ping. En désactivant OOBInline sur un socket (setOOBInline (false), toutes les données OOB reçues sont ignorées en silence, mais les données OOB peuvent toujours être envoyées. Si le côté distant est fermé, une réinitialisation de la connexion est tentée, échoue et provoque l'émission d'une exception IOException. .
Si vous utilisez réellement des données OOB dans votre protocole, votre kilométrage peut varier.
la source
la pile Java IO envoie définitivement FIN lorsqu'elle est détruite lors d'un démontage brutal. Cela n'a aucun sens que vous ne puissiez pas détecter cela, car la plupart des clients n'envoient le FIN que s'ils arrêtent la connexion.
... une autre raison pour laquelle je commence vraiment à détester les classes NIO Java. On dirait que tout est un petit demi-cul.
la source
C'est un sujet intéressant. Je viens de fouiller dans le code java pour vérifier. D'après ma conclusion, il y a deux problèmes distincts: le premier est le TCP RFC lui-même, qui permet à un socket fermé à distance de transmettre des données en semi-duplex, de sorte qu'un socket fermé à distance est toujours à moitié ouvert. Conformément à la RFC, RST ne ferme pas la connexion, vous devez envoyer une commande ABORT explicite; donc Java permet d'envoyer des données via un socket à moitié fermé
(Il existe deux méthodes pour lire l'état de fermeture des deux points de terminaison.)
L'autre problème est que l'implémentation dit que ce comportement est facultatif. Comme Java s'efforce d'être portable, ils ont implémenté la meilleure fonctionnalité commune. Le maintien d'une carte de (OS, mise en œuvre du semi-duplex) aurait été un problème, je suppose.
la source
C'est une faille des classes de socket OO de Java (et de toutes les autres que j'ai examinées) - pas d'accès à l'appel système select.
Bonne réponse en C:
la source
getsocketop(... SOL_SOCKET, SO_ERROR, ...)
.Voici une solution de contournement boiteuse. Utilisez SSL;) et SSL établit une poignée de main étroite lors du démontage afin que vous soyez averti de la fermeture du socket (la plupart des implémentations semblent faire un démontage de poignée de main proprement dit).
la source
La raison de ce comportement (qui n'est pas spécifique à Java) est le fait que vous n'obtenez aucune information d'état de la pile TCP. Après tout, une socket n'est qu'un autre descripteur de fichier et vous ne pouvez pas savoir s'il y a des données réelles à lire sans essayer de (
select(2)
cela ne vous aidera pas, cela signale seulement que vous pouvez essayer sans bloquer).Pour plus d'informations, consultez la FAQ sur les sockets Unix .
la source
select()
vous indique si des données ou EOS sont disponibles pour être lus sans blocage. «Les signaux que vous pouvez essayer sans bloquer» n'a aucun sens. Si vous êtes en mode non bloquant, vous pouvez toujours essayer sans bloquer.select()
est piloté par des données dans le tampon de réception du socket ou un FIN en attente ou un espace dans le tampon d'envoi du socket.getsockopt(SO_ERROR)
? En fait, mêmegetpeername
vous dira si la prise est toujours connectée.Seules les écritures nécessitent l'échange de paquets, ce qui permet de déterminer la perte de connexion. Une solution courante consiste à utiliser l'option KEEP ALIVE.
la source
Quand il s'agit de traiter les sockets Java semi-ouverts, on peut vouloir jeter un œil à isInputShutdown () et isOutputShutdown () .
la source