Existe-t-il un moyen de détecter que le socket TCP a été fermé par l’homologue distant, sans le lire?

Tout d’abord, un peu d’arrière-plan pour expliquer la motivation: je travaille sur un «proxy miroir» TCP très simple basé sur select (), qui permet à deux clients protégés par un pare-feu de se parler indirectement. Les deux clients se connectent à ce serveur et, dès que les deux clients sont connectés, tous les octets TCP envoyés au serveur par le client A sont transférés au client B, et inversement.

Cela fonctionne plus ou moins, avec une légère erreur: si le client A se connecte au serveur et commence à envoyer des données avant que le client B ne soit connecté, le serveur n’a nulle part où placer les données. Je ne veux pas le mettre en mémoire tampon dans la RAM, car cela pourrait utiliser beaucoup de RAM; et je ne veux pas simplement supprimer les données non plus, car le client B pourrait en avoir besoin. Donc, je vais pour la troisième option, qui est de ne pas sélectionner () – prêt pour la lecture sur le socket du client A jusqu’à ce que le client B soit également connecté. De cette façon, le client A bloque jusqu’à ce que tout soit prêt à partir.

Cela fonctionne plus ou moins aussi, mais le fait que le client A décide de fermer sa connexion TCP au serveur, le serveur ne soit pas averti à ce sujet – – du moins, pas avant que le client B n’arrive et que le serveur sélectionne enfin prêt pour la lecture sur le socket du client A, lise les données en attente, puis obtient la notification de fermeture du socket (c’est-à-dire que recv () renvoie 0).

Je préférerais que le serveur ait un moyen de savoir (en temps voulu) quand le client A a fermé sa connexion TCP. Y a-t-il un moyen de savoir cela? Une interrogation serait acceptable dans ce cas (par exemple, j’aurais pu select () se réveiller une fois par minute et appeler IsSocketStillConnected (chaussette) sur tous les sockets, si une telle fonction existait).

Si vous voulez vérifier si le socket est réellement fermé au lieu de données, vous pouvez append l’indicateur MSG_PEEK sur recv() pour voir si des données sont arrivées ou si vous obtenez 0 ou une erreur.

 /* handle readable on A */ if (B_is_not_connected) { char c; ssize_t x = recv(A_sock, &c, 1, MSG_PEEK); if (x > 0) { /* ...have data, leave it in socket buffer until B connects */ } else if (x == 0) { /* ...handle FIN from A */ } else { /* ...handle errors */ } } 

Même si A ferme après l’envoi de données, votre mandataire voudra probablement transmettre ces données à B avant de transmettre FIN à B, il est donc inutile de savoir que A a envoyé FIN sur la connexion plus tôt qu’après avoir lu toutes les données. il a envoyé.

Une connexion TCP n’est considérée comme fermée qu’après que les deux côtés ont envoyé FIN. Toutefois, si A a arrêté de force son sharepoint terminaison, vous ne le saurez qu’après avoir tenté d’envoyer des données et reçu un EPIPE (en supposant que vous ayez supprimé SIGPIPE ).


Après avoir lu votre application proxy miroir un peu plus, puisqu’il s’agit d’une application de traversée de pare-feu, il semble que vous ayez réellement besoin d’un petit protocole de contrôle vous permettant de vérifier que ces pairs sont réellement autorisés à se parler. Si vous avez un protocole de contrôle, vous avez plusieurs solutions à votre disposition, mais celle que je préconiserais serait que l’une des connexions se décrive en tant que serveur et l’autre en tant que client. Ensuite, vous pouvez réinitialiser la connexion du client s’il n’y a pas de serveur présent pour établir sa connexion. Vous pouvez laisser les serveurs attendre une connexion client jusqu’à un certain délai. Un serveur ne doit pas initialiser de données et s’il ne le fait pas, vous pouvez réinitialiser la connexion au serveur. Ceci élimine le problème de mise en mémoire tampon des données pour une connexion inactive.

Il semble que la réponse à ma question soit “non, sauf si vous êtes disposé et en mesure de modifier votre stack TCP pour avoir access aux informations nécessaires sur l’état de la prise privée”.

Comme je ne pouvais pas le faire, ma solution consistait à redéfinir le serveur proxy pour qu’il lise toujours les données de tous les clients et jette toutes les données provenant d’un client dont le partenaire ne s’est pas encore connecté. Ceci n’est pas optimal, car cela signifie que les stream TCP passant par le proxy ne possèdent plus la propriété de transmission fiable dans l’ordre que les programmes utilisant TCP s’attendent, mais cela suffira à mon propos.

Je ne vois pas le problème comme vous le voyez. Disons que A se connecte au serveur envoie des données et ferme, il n’a pas besoin de message en retour. Le serveur ne lira pas ses données tant que B ne se connectera pas. Une fois que le serveur lira le socket A et enverra les données à B. La première lecture renverra les données envoyées par A et le second renverra soit 0, soit -1 dans les deux cas. closed, server close B. Supposons que nous envoyions une grande quantité de données, la méthode send () de l’A bloquera jusqu’à ce que le serveur commence à lire et consum la mémoire tampon.

Je voudrais utiliser une fonction avec une sélection qui renvoie 0, 1, 2, 11, 22 ou -1, où;

  • 0 = aucune donnée dans aucun socket (délai d’attente)
  • 1 = A a des données à lire
  • 2 = B a des données à lire
  • 11 = Une socket a une erreur (déconnectée)
  • 22 = la prise B a une erreur (déconnectée)
  • -1: Un / les deux sockets est / ne sont pas valides

     int WhichSocket(int sd1, int sd2, int seconds, int microsecs) { fd_set sfds, efds; struct timeval timeout={0, 0}; int bigger; int ret; FD_ZERO(&sfds); FD_SET(sd1, &sfds); FD_SET(sd2, &sfds); FD_SET(sd1, &efds); FD_SET(sd2, &efds); timeout.tv_sec=seconds; timeout.tv_usec=microsecs; if (sd1 > sd2) bigger=sd1; else bigger=sd2; // bigger is necessary to be Berkeley compatible, Microsoft ignore this param. ret = select(bigger+1, &sfds, NULL, &efds, &timeout); if (ret > 0) { if (FD_ISSET(sd1, &sfds)) return(1); // sd1 has data if (FD_ISSET(sd2, &sfds)) return(2); // sd2 has data if (FD_ISSET(sd1, &efds)) return(11); // sd1 has an error if (FD_ISSET(sd2, &efds)) return(22); // sd2 has an error } else if (ret < 0) return -1; // one of the socket is not valid return(0); // timeout } 

Pour moi, la solution consistait à interroger l’état de la prise.

Sous Windows 10, le code suivant semblait fonctionner (mais des implémentations équivalentes semblent exister pour d’autres systèmes):

 WSAPOLLFD polledSocket; polledSocket.fd = socketItf; polledSocket.events = POLLRDNORM | POLLWRNORM; if (WSAPoll(&polledSocket, 1, 0) > 0) { if (polledSocket.revents &= (POLLERR | POLLHUP)) { // socket closed return FALSE; } } 

Si votre proxy doit être un proxy d’usage général pour n’importe quel protocole, vous devez également gérer également les clients qui envoient des données et appellent immédiatement après cet envoi (transfert de données unidirectionnel uniquement).

Ainsi, si le client A envoie des données et ferme la connexion avant que la connexion ne soit ouverte sur B, ne vous inquiétez pas, transmettez-les simplement à B normalement (lorsque la connexion à B est ouverte).

Il n’est pas nécessaire de mettre en œuvre un traitement spécial pour ce scénario.

Votre proxy détectera la connexion fermée lorsque:

  • read renvoie zéro après l’ouverture de la connexion à B et la lecture de toutes les données en attente de A. ou
  • vos programmes essaient d’envoyer des données (de B) à A.

Vous pouvez vérifier si le socket est toujours connecté en essayant d’écrire dans le descripteur de fichier pour chaque socket. Ensuite, si la valeur de retour de l’écriture est -1 ou si errno = EPIPE, vous savez que le socket a été fermé.
par exemple

 int isSockStillConnected(int *fileDescriptors, int numFDs){ int i,n; for (i=0;i