Socket recv () зависает при большом сообщении с MSG_WAITALL

У меня есть приложение, которое читает большие файлы с сервера и часто зависает на определенной машине. Он долгое время успешно работал под RHEL5.2. Мы недавно обновились до RHEL6.1, и теперь он регулярно зависает.

Я создал тестовое приложение, которое воспроизводит проблему. Он зависает примерно 98 раз из 100.

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

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). С этого момента он зависает навсегда: сервер отправляет сообщения keep-alive, а клиент отправляет сообщения ZeroWindow. Кажется, что клиент никогда не расширяет свое окно, позволяя завершить передачу.
  • Во время зависания, если я исследую вывод "netstat -a", в очереди отправки серверов есть данные, но очередь приема клиентов пуста.
  • Если я удалю флаг MSG_WAITALL из вызова recv (), приложение завершится успешно.
  • Проблема зависания возникает только при использовании интерфейса обратной петли на одной конкретной машине. Я подозреваю, что все это может быть связано с временными зависимостями.
  • По мере того, как я уменьшаю размер «файла», вероятность зависания уменьшается.

Источник тестового приложения можно найти здесь:

Источник теста сокета

Захват tcpdump из Интерфейс loopback можно найти здесь:

tcpdump capture

Я воспроизвожу проблему, выполнив следующие команды:

>  gcc socket_test.c -o socket_test
>  perl -e 'for (1..6000000){ print "a" }' | nc -l 60000
>  ./socket_test 6000000

Это видит 6000000 байтов, отправленных в тестовое приложение, которое пытается прочитать данные с помощью одного вызова recv ( ).

Я хотел бы услышать любые предложения о том, что я делаю неправильно, или какие-либо дальнейшие способы отладки проблемы.

10
задан Duck 12 December 2011 в 06:01
поделиться