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:

Prise de test source

Le tcpdump capture à partir de l'interface de bouclage peut être trouvé ici:

tcpdump capture

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