Сокет recv () зависает на большом сообщении с MSG_WAITALL - PullRequest
10 голосов
/ 12 декабря 2011

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

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

Ответы [ 2 ]

16 голосов
/ 12 декабря 2011

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

Этот флаг запрашивает выполнение операционного блока до тех пор, пока не будет выполнен полный запрос.

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

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

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

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

1 голос
/ 04 ноября 2015

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

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

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

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

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

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

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

...