Le socket recv () se bloque sur un gros message avec MSG_WAITALL
J'ai une application qui lit des fichiers volumineux à partir d'un serveur et se bloque souvent sur une machine particulière. Il a travaillé avec succès en vertu de RHEL5.2 pour une longue période de temps. Nous avons récemment mis à niveau vers RHEL6.1 et il se trouve maintenant régulièrement.
J'ai créé une application de test qui reproduit le problème. Il se bloque environ 98 fois sur 100.
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/time.h>
int mFD = 0;
void open_socket()
{
struct addrinfo hints, *res;
memset(&hints, 0, sizeof(hints));
hints.ai_socktype = SOCK_STREAM;
hints.ai_family = AF_INET;
if (getaddrinfo("localhost", "60000", &hints, &res) != 0)
{
fprintf(stderr, "Exit %d\n", __LINE__);
exit(1);
}
mFD = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (mFD == -1)
{
fprintf(stderr, "Exit %d\n", __LINE__);
exit(1);
}
if (connect(mFD, res->ai_addr, res->ai_addrlen) < 0)
{
fprintf(stderr, "Exit %d\n", __LINE__);
exit(1);
}
freeaddrinfo(res);
}
void read_message(int size, void* data)
{
int bytesLeft = size;
int numRd = 0;
while (bytesLeft != 0)
{
fprintf(stderr, "reading %d bytes\n", bytesLeft);
/* Replacing MSG_WAITALL with 0 works fine */
int num = recv(mFD, data, bytesLeft, MSG_WAITALL);
if (num == 0)
{
break;
}
else if (num < 0 && errno != EINTR)
{
fprintf(stderr, "Exit %d\n", __LINE__);
exit(1);
}
else if (num > 0)
{
numRd += num;
data += num;
bytesLeft -= num;
fprintf(stderr, "read %d bytes - remaining = %d\n", num, bytesLeft);
}
}
fprintf(stderr, "read total of %d bytes\n", numRd);
}
int main(int argc, char **argv)
{
open_socket();
uint32_t raw_len = atoi(argv[1]);
char raw[raw_len];
read_message(raw_len, raw);
return 0;
}
Quelques notes de mon test:
- Si "localhost" correspond à l'adresse de bouclage 127.0.0.1, l'application se bloque sur l'appel à recv() et ne retourne JAMAIS.
- Si "localhost" correspond à l'adresse ip de la machine, donc le routage des paquets via l'interface ethernet, l'application se termine avec succès.
- Quand j'ai de l'expérience d'un blocage, le serveur envoie une "Fenêtre TCP Plein de message", et le client répond avec un "TCP ZeroWindow" message (voir l'image et le joint tcpdump capture). À partir de ce point, il se bloque toujours avec le serveur d'envoi de keep-alives et le client envoi ZeroWindow messages. Le client ne semble jamais à élargir sa fenêtre, permettant le transfert.
- Pendant le coup, si j'examine la sortie de la commande "netstat -a", il y a des données dans les serveurs de la file d'attente d'envoi, mais les clients reçoivent de la file d'attente est vide.
- Si je supprime le MSG_WAITALL drapeau de l'appel recv (), l'application se termine avec succès.
- La pendaison problème ne se pose qu'à l'aide de l'interface de bouclage sur 1 machine en particulier. Je soupçonne que cela peut être lié à temps dépendances.
- Que je baisse la taille de la "fichier", la probabilité de les accrocher de produit est réduit
La source pour l'application de test peuvent être trouvés ici:
Le tcpdump capture à partir de l'interface de bouclage peut être trouvé ici:
Je reproduire le problème en exécutant les commandes suivantes:
> gcc socket_test.c -o socket_test
> perl -e 'for (1..6000000){ print "a" }' | nc -l 60000
> ./socket_test 6000000
Ce voit 6000000 octets envoyés à l'application de test qui essaie de lire les données à l'aide d'un seul appel à recv().
J'aimerais entendre toutes les suggestions sur ce que je fais mal ou tout autres moyens de déboguer le problème.
source d'informationauteur Shane Carr
Vous devez vous connecter pour publier un commentaire.
MSG_WAITALL
devrait bloc jusqu'à ce que toutes les données ont été reçues. À partir de la page de manuel sur recv:Cependant, la mémoire dans la pile réseau ne sont probablement pas assez grand pour contenir tout ce qui est la raison pour laquelle les messages d'erreur sur le serveur. La pile réseau client ne peut tout simplement pas tenir beaucoup de données.
La solution consiste à augmenter la taille de mémoire tampon (
SO_RCVBUF
option poursetsockopt
), de diviser le message en petits morceaux, ou de recevoir des petits morceaux les mettre dans votre propre tampon. La dernière, c'est ce que je recommanderais.Edit: je vois dans votre code que vous avez déjà faire ce que j'ai proposé (lire en petits morceaux avec tampon,) il suffit donc de retirer le
MSG_WAITALL
drapeau et cela devrait fonctionner.Oh, et quand
recv
renvoie zéro, cela signifie que l'autre extrémité qui ont fermé la connexion, et que vous devriez le faire aussi.Tenir compte de ces deux règles:
Le récepteur peut attendre pour l'expéditeur d'envoyer de plus avant de recevoir ce qui a déjà été envoyé.
L'expéditeur peut attendre le récepteur pour recevoir ce qui a déjà été envoyé avant l'envoi de plus.
Nous pouvons avoir l'autre de ces règles, mais nous ne pouvons pas avoir deux de ces règles.
Pourquoi? Parce que si le récepteur est autorisé à attendre de l'expéditeur, cela signifie que l'expéditeur ne peut pas attendre pour le récepteur à la réception avant l'envoi de plus, sinon que nous de blocage. Et si l'émetteur est autorisé à attendre pour le récepteur, cela signifie que le récepteur ne peut pas attendre pour l'expéditeur d'envoyer avant de recevoir de plus, sinon que nous de blocage.
Si ces deux choses se produisent en même temps, nous avons impasse. L'expéditeur n'enverra plus de jusqu'à ce que le récepteur reçoit ce qui a déjà été envoyé, et le récepteur ne recevra pas ce qui a déjà été envoyé, à moins que l'expéditeur d'envoyer plus. Boom.
TCP choisit la règle 2 (pour des raisons qui devraient être évidentes). Ainsi, il ne peut pas l'appui de la règle 1. Mais dans votre code, vous êtes le destinataire, et vous êtes en attente pour l'expéditeur d'envoyer plus avant de recevoir ce qui a déjà été envoyé. Ce sera l'impasse.