web-dev-qa-db-fra.com

Linux Socket: Comment détecter un réseau déconnecté dans un programme client?

Je débogue un programme de socket linux basé sur c. Comme tous les exemples disponibles sur les sites Web, j'ai appliqué la structure suivante:

sockfd= socket(AF_INET, SOCK_STREAM, 0);

connect(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr));

send_bytes = send(sockfd, sock_buff, (size_t)buff_bytes, MSG_DONTWAIT);

Je peux détecter la déconnexion lorsque le serveur de suppression ferme son programme serveur. Mais si je débranche le câble Ethernet, la fonction d'envoi renvoie toujours des valeurs positives plutôt que -1.

Comment puis-je vérifier la connexion réseau dans un programme client en supposant que je ne peux pas changer côté serveur?

14
user2052197

Mais si je débranche le câble Ethernet, la fonction d'envoi renvoie toujours des valeurs positives plutôt que -1.

Tout d'abord, vous devez savoir que send n'envoie rien, c'est juste une fonction de copie de mémoire/un appel système. Il copie les données de votre processus vers le noyau - quelque temps plus tard, le noyau récupérera ces données et les enverra de l'autre côté après les avoir empaquetées en segments et en paquets. Par conséquent, send ne peut renvoyer une erreur que si:

  • Le socket n'est pas valide (par exemple, un faux descripteur de fichier)
  • La connexion est clairement invalide, par exemple, elle n'a pas été établie ou a déjà été interrompue d'une manière ou d'une autre (FIN, RST, timeout - voir ci-dessous)
  • Il n'y a plus de place pour copier les données

Le point principal est que send n'envoie rien et donc son code retour ne vous dit rien sur les données atteignant réellement l'autre côté.

Revenons à votre question, lorsque TCP envoie des données, elle attend un accusé de réception valide dans un délai raisonnable. S'il n'en obtient pas, il est renvoyé. À quelle fréquence est-il renvoyé? Chaque pile TCP fait les choses différemment, mais la norme est d'utiliser des interruptions exponentielles. Autrement dit, attendez d'abord 1 seconde, puis 2, puis 4 et ainsi de suite. Sur certaines piles, ce processus peut prendre quelques minutes.

Le point principal est qu'en cas d'interruption, TCP déclarera une connexion morte seulement après une période de silence très longue (sous Linux, il fait quelque chose comme 15 tentatives - plus de 5 minutes).

Une façon de résoudre ce problème consiste à implémenter un mécanisme d'accusé de réception dans votre application. Vous pouvez par exemple envoyer une requête au serveur " répondre dans les 5 secondes ou je déclarerai cette connexion morte " puis recv avec un timeout.

37
cnicutar

Pour détecter une déconnexion à distance, effectuez une read()

Consultez ce fil pour plus d'informations:

La fonction read () sur un socket connecté peut-elle retourner zéro octet?

2
Forhad Ahmed

Vous ne pouvez pas détecter le câble Ethernet débranché uniquement en appelant la fonction write (). C'est à cause de la retransmission tcp agie par la pile tcp sans votre conscience. Voici des solutions.

Même si vous avez déjà défini l'option keepalive sur le socket de votre application, vous ne pouvez pas détecter à temps l'état de connexion morte du socket, au cas où votre application continuerait d'écrire sur le socket. C'est à cause de la retransmission tcp par la pile tcp du noyau. tcp_retries1 et tcp_retries2 sont des paramètres de noyau pour configurer le délai de retransmission tcp. Il est difficile de prédire l'heure précise du délai d'expiration de la retransmission car il est calculé par le mécanisme RTT. Vous pouvez voir ce calcul dans rfc793. (3.7. Communication des données)

https://www.rfc-editor.org/rfc/rfc793.txt

Chaque plate-forme a des configurations de noyau pour la retransmission TCP.

Linux : tcp_retries1, tcp_retries2 : (exist in /proc/sys/net/ipv4)

http://linux.die.net/man/7/tcp

HPUX : tcp_ip_notify_interval, tcp_ip_abort_interval

http://www.hpuxtips.es/?q=node/5

AIX : rto_low, rto_high, rto_length, rto_limit

http://www-903.ibm.com/kr/event/download/200804_324_swma/socket.pdf

Vous devez définir une valeur inférieure pour tcp_retries2 (par défaut 15) si vous souhaitez détecter précocement une connexion morte, mais ce n'est pas l'heure précise comme je l'ai déjà dit. De plus, actuellement, vous ne pouvez pas définir ces valeurs uniquement pour un socket unique. Ce sont des paramètres globaux du noyau. Il y a eu un essai pour appliquer l'option de socket de retransmission tcp pour un socket unique ( http://patchwork.ozlabs.org/patch/55236/ ), mais je ne pense pas qu'il ait été appliqué à la ligne principale du noyau. Je ne trouve pas ces définitions d'options dans les fichiers d'en-tête du système.

Pour référence, vous pouvez surveiller votre option de socket keepalive via 'netstat --timers' comme ci-dessous. https://stackoverflow.com/questions/34914278

netstat -c --timer | grep "192.0.0.1:43245             192.0.68.1:49742"

tcp        0      0 192.0.0.1:43245             192.0.68.1:49742            ESTABLISHED keepalive (1.92/0/0)
tcp        0      0 192.0.0.1:43245             192.0.68.1:49742            ESTABLISHED keepalive (0.71/0/0)
tcp        0      0 192.0.0.1:43245             192.0.68.1:49742            ESTABLISHED keepalive (9.46/0/1)
tcp        0      0 192.0.0.1:43245             192.0.68.1:49742            ESTABLISHED keepalive (8.30/0/1)
tcp        0      0 192.0.0.1:43245             192.0.68.1:49742            ESTABLISHED keepalive (7.14/0/1)
tcp        0      0 192.0.0.1:43245             192.0.68.1:49742            ESTABLISHED keepalive (5.98/0/1)
tcp        0      0 192.0.0.1:43245             192.0.68.1:49742            ESTABLISHED keepalive (4.82/0/1)

De plus, lorsque le délai d'expiration Keepalive se produit, vous pouvez rencontrer différents événements de retour en fonction des plates-formes que vous utilisez, vous ne devez donc pas décider de l'état de connexion morte uniquement par des événements de retour. Par exemple, HP renvoie l'événement POLLERR et AIX ne renvoie que l'événement POLLIN lorsqu'un délai d'expiration Keepalive se produit. Vous rencontrerez l'erreur ETIMEDOUT dans l'appel recv () à ce moment-là.

Dans la version récente du noyau (depuis 2.6.37), vous pouvez utiliser l'option TCP_USER_TIMEOUT qui fonctionnera bien. Cette option peut être utilisée pour une prise unique.

Enfin, vous pouvez utiliser la fonction de lecture avec l'indicateur MSG_PEEK, qui peut vous permettre de vérifier que le socket est correct. (MSG_PEEK lit juste si les données sont arrivées dans le tampon de pile du noyau et ne copie jamais les données dans le tampon utilisateur.) Vous pouvez donc utiliser cet indicateur juste pour vérifier que socket est correct sans aucun effet secondaire.

2
cloudrain21

Vérifiez la valeur de retour et voyez si elle est égale à cette valeur:

EPIPE
Cette prise était connectée mais la connexion est maintenant rompue. Dans ce cas, send génère d'abord un signal SIGPIPE; si ce signal est ignoré ou bloqué, ou si son gestionnaire revient, l'envoi échoue avec EPIPE.

Ajoutez également une vérification du signal SIGPIPE dans votre gestionnaire, pour le rendre plus contrôlable.

1
Ramy Al Zuhouri