Гнездо recv () висит на большом сообщении с MSG WAITALL

у меня есть приложение, которое читает большие файлы с сервера и часто зависает на определенной машине. Он успешно работал под 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 %dn", __LINE__);
    exit(1);
  }

  mFD = socket(res->ai_family, res->ai_socktype, res->ai_protocol);

  if (mFD == -1)
  {
    fprintf(stderr, "Exit %dn", __LINE__);
    exit(1);
  }

  if (connect(mFD, res->ai_addr, res->ai_addrlen) < 0)
  {
    fprintf(stderr, "Exit %dn", __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 bytesn", 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 %dn", __LINE__);
      exit(1);
    }
    else if (num > 0)
    {
      numRd += num;
      data += num;
      bytesLeft -= num;
      fprintf(stderr, "read %d bytes - remaining = %dn", num, bytesLeft);
    }
  }

  fprintf(stderr, "read total of %d bytesn", 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-alives, и клиентом, отправляющим сообщения ZeroWindow. Клиент, похоже, никогда не расширяет свое окно, позволяя завершить передачу.
  • во время зависания, Если я проверяю вывод "netstat-a", есть данные в очереди отправки серверов, но очередь получения клиентов пуста.
  • если я удалю флаг MSG_WAITALL из вызова recv (), приложение завершится успешно.
  • проблема зависания возникает только с использованием интерфейса loopback на 1 конкретной машине. Я подозреваю, что все это может быть связано со временем зависимости.
  • по мере удаления размера "файла" вероятность зависания уменьшается

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

Socket test source

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

захват tcpdump

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

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

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

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

2 ответов


MSG_WAITALL должны блок, пока не будут получены все данные. От страница руководства по recv:

этот флаг просит подождать, пока запрос будет удовлетворен.

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

решение заключается либо в увеличении размеров буфера ( до setsockopt), разделите сообщение на более мелкие части или получите меньшие куски, поместив их в свой собственный буфер. Последнее я бы рекомендовал.

Edit: я вижу в вашем коде, что вы уже делаете то, что я предложил (читайте меньшие куски с собственной буферизацией), поэтому просто удалите MSG_WAITALL флаг и он должен работать.

а когда recv возвращает ноль, что означает, что другой конец закрыл соединение, и что Вы тоже должны это сделать.


рассмотрим эти два возможных правил:

  1. получатель может ждать отправителя, чтобы отправить больше, прежде чем получить то, что уже было отправлено.

  2. отправитель может подождать, пока получатель получит то, что уже было отправлено, прежде чем отправлять больше.

У нас может быть любое из этих правил, но мы не можем иметь оба этих правила.

Почему? Потому что, если получателю разрешено ждать отправителя, это означает, что отправитель не может ждать, пока получатель получит перед отправкой больше,иначе мы блокируем. И если отправителю разрешено ждать получателя, это означает, что получатель не может ждать отправки отправителем, прежде чем получить больше, в противном случае мы блокируем.

Если обе эти вещи происходят одновременно, мы тупике. Отправитель не будет отправлять больше, пока получатель не получит то, что уже было отправлено, и получатель не получит то, что уже было отправлено, если только отправитель отправляет больше. Бум.

протокол TCP выбирает Правило 2 (по причинам, которые должны быть очевидны). Так оно и есть!--19-->не может правила поддержки 1. Но в вашем коде вы являетесь получателем, и вы ждете, что отправитель отправит больше, прежде чем вы получите то, что уже было отправлено. Так что это будет тупик.