У меня есть приложение, которое читает большие файлы с сервера и часто зависает на определенной машине.Он успешно работал под RHEL5.2 в течение длительного времени.Недавно мы обновились до RHEL6.1, и теперь он регулярно зависает.
Я создал тестовое приложение, которое воспроизводит проблему.Он зависает примерно в 98 раз из 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;
}
Некоторые заметки из моего тестирования:
- Если "localhost" отображается на адрес обратной петли 127.0.0.1, приложение зависаетвызов recv () и НИКОГДА не возвращается.
- Если «localhost» отображается на ip машины, таким образом, маршрутизируя пакеты через интерфейс Ethernet, приложение успешно завершается.
- Когда яВ случае зависания сервер отправляет сообщение «TCP Window Full», а клиент отвечает сообщением «TCP ZeroWindow» (см. изображение и прикрепленный захват tcpdump).С этого момента он навсегда зависает с сервером, отправляющим сообщения об активности, и клиентом, отправляющим сообщения ZeroWindow.Кажется, что клиент никогда не расширяет свое окно, позволяя завершить передачу.
- Во время зависания, если я проверяю вывод «netstat -a», в очереди отправки серверов есть данные, но клиенты получают очередьпусто.
- Если я уберу флаг MSG_WAITALL из вызова recv (), приложение успешно завершится.
- Проблема с зависанием возникает только при использовании интерфейса обратной связи на 1 конкретном компьютере.Я подозреваю, что все это может быть связано с временными зависимостями.
- По мере того как я отбрасываю размер файла, вероятность возникновения зависания уменьшается
Источник тестаПриложение можно найти здесь:
Источник проверки гнезда
Захват tcpdump из интерфейса обратной связи можно найти здесь:
Захват tcpdump
Я воспроизвожу проблему, выполнив следующие команды:
> gcc socket_test.c -o socket_test
> perl -e 'for (1..6000000){ print "a" }' | nc -l 60000
> ./socket_test 6000000
Это видит 6000000 байтов, отправленных в тестовое приложение, которое пытается прочитать данные, используя один вызов recv ().
Мне бы очень хотелось услышать любые предложения о том, что я могу делать неправильно, или какие-либо дальнейшие способы устранения проблемы.