Socket recv() hängen große Nachricht mit MSG_WAITALL
Ich habe eine Anwendung, die liest große Dateien von einem server und hängt sich Häufig auf eine bestimmte Maschine. Er arbeitete erfolgreich unter RHEL5.2 für eine lange Zeit. Wir haben vor kurzem ein Upgrade auf RHEL6.1 und es hängt jetzt regelmäßig.
Habe ich eine test-app, die das problem reproduziert. Es hängt ca 98 mal von 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;
}
Einige Notizen von meinen Tests:
- Wenn "localhost" - Karten an die loopback-Adresse 127.0.0.1, die app hängt sich der Aufruf von recv() und kehrt NIE zurück.
- Wenn "localhost" verweist auf die ip Adresse des Rechners, damit das routing der Pakete über die ethernet-Schnittstelle, die app erfolgreich abgeschlossen ist.
- Wenn ich ein hang, sendet der server eine "TCP Window Full" angezeigt, und der client antwortet mit einem "TCP ZeroWindow" - Meldung (siehe Bild und befestigt tcpdump capture). Von diesem Punkt, es hängt immer mit dem server senden von keep-alives und den client senden ZeroWindow Nachrichten. Der client scheint nie erweitern Sie das Fenster, so dass die übertragung abgeschlossen ist.
- Während der hängen, wenn ich überprüfen Sie die Ausgabe von "netstat-a", es gibt Daten in den Server senden-Warteschlange, aber die Kunden erhalten die Warteschlange leer ist.
- Wenn ich entfernen Sie das flag MSG_WAITALL von der recv () - Aufruf, wird die app erfolgreich abgeschlossen ist.
- Die hängenden Problem entsteht nur über das loopback-interface auf 1 Maschine. Ich vermute, dass dies alles im Zusammenhang mit timing-Abhängigkeiten.
- Wie ich drop die Größe des 'Datei', die Wahrscheinlichkeit, dass der hang vorkommenden reduziert
Quelle für die test-app finden Sie hier:
Den tcpdump-Aufzeichnung von der loopback-Schnittstelle finden Sie hier:
Ich das Problem reproduzieren, indem Sie die folgenden Befehle ausgeben:
> gcc socket_test.c -o socket_test
> perl -e 'for (1..6000000){ print "a" }' | nc -l 60000
> ./socket_test 6000000
Dieser sieht 6000000 bytes gesendet, um die test-app, die versucht, Lesen Sie die Daten mit einem einzigen Aufruf von recv().
Ich würde gerne Anregungen zu hören, auf was ich vielleicht falsch mache oder weitere Möglichkeiten zum Debuggen das Problem.
InformationsquelleAutor Shane Carr | 2011-12-12
Du musst angemeldet sein, um einen Kommentar abzugeben.
MSG_WAITALL
sollte blockiert, bis alle Daten empfangen wurden. Aus der Handbuch Seite auf recv:Jedoch, den Puffer in der Netzwerk-stack wahrscheinlich nicht groß genug, um alles enthalten, was der Grund für die Fehlermeldungen auf dem server. Die client-Netzwerk-stack einfach nicht halten kann, viele Daten.
Die Lösung ist entweder um die buffer-Größen (
SO_RCVBUF
optionsetsockopt
), teilen Sie die Nachricht in kleinere Stücke, oder eine kleinere Stücke, setzen Sie es in Ihrem eigenen Puffer. Das Letzte ist, was ich empfehlen würde.Edit: ich sehe in deinem code, dass Sie bereits das tun, was ich vorgeschlagen (Lesen kleinere Stücke mit eigener Pufferung) so entfernen Sie die
MSG_WAITALL
Flagge und es sollte funktionieren.Oh, und wenn
recv
gibt null zurück, das bedeutet, dass das andere Ende haben die Verbindung geschlossen hat, und dass Sie es auch tun sollten.InformationsquelleAutor Some programmer dude
Betrachten Sie diese zwei mögliche Regeln:
Kann der Empfänger warten, bis der sender zum senden vor dem empfangen, was bereits gesendet wurde.
Dürfen die sender warten, bis der Empfänger zu erhalten, was bereits gesendet wurde, bevor Sie Sie versenden mehr.
Können wir diese Regeln, aber wir können nicht beide Regeln.
Warum? Denn wenn der Empfänger gestattet, zu warten, bis der Absender, das bedeutet, dass der Absender nicht warten können, bis der Empfänger Sie empfangen vor dem senden mehr, sonst hätten wir deadlock. Und wenn der Absender zulässig ist, zu warten, bis der Empfänger, d.h. der Empfänger kann nicht warten, bis der sender zum senden vor dem Empfang mehr, sonst hätten wir deadlock.
Wenn diese beiden Dinge passieren zur gleichen Zeit, wir deadlock. Der Absender wird nicht mehr senden, bis der Empfänger erhält das, was bereits gesendet wurde, und der Empfänger wird nicht erhalten, was bereits gesendet wird, wenn der sender mehr senden. Boom.
TCP wählt, Regel 2 (aus Gründen, sollte klar sein). So ist es nicht support Regel 1. Aber in deinem code sind Sie der Empfänger, und warten Sie für den Absender zu senden, bevor Sie erhalten, was bereits gesendet wurde. Dies wird also deadlock.
InformationsquelleAutor David Schwartz