Nano Hash - криптовалюты, майнинг, программирование

Предотвращение FIN_WAIT2 при закрытии сокета

У меня есть серверная программа, которая подключается к другой программе через заданный сокет, и в некоторых случаях мне нужно закрыть соединение и почти сразу же снова открыть его на том же сокете. Это по большому счету работает, за исключением того, что мне нужно ждать ровно одну минуту, чтобы сокет сбросился. Тем временем netstat указывает, что сервер видит сокет в FIN_WAIT2, а клиент видит его как CLOSE_WAIT. Я уже использую SO_REUSEADDR, который, как я думал, предотвратит ожидание, но это не помогает. Установка SO_LINGER в ноль также не помогает. Что еще я могу сделать, чтобы решить эту проблему?

Вот соответствующие фрагменты кода:

SetUpSocket()
{
   // Set up the socket and listen for a connection from the exelerate client.
   // Open a TCP/IP socket.
   m_baseSock = socket(PF_INET, SOCK_STREAM, IPPROTO_IP);
   if (m_baseSock < 0)
   {
      return XERROR;
   }

   // Set the socket options to reuse local addresses.
   int flag = 1;
   if (setsockopt(m_baseSock, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag)) == -1)
   {
      return XERROR;
   }

   // Set the socket options to prevent lingering after closing the socket.
   //~ linger li = {1,0};
   //~ if (setsockopt(m_baseSock, SOL_SOCKET, SO_LINGER, &li, sizeof(li)) == -1)
   //~ {
      //~ return XERROR;
   //~ }

   // Bind the socket to the address of the current host and our given port.
   struct sockaddr_in addr;
   memset(&addr, 0, sizeof(addr));
   addr.sin_family = AF_INET;
   addr.sin_addr.s_addr = INADDR_ANY;
   addr.sin_port = htons(m_port);
   if (bind(m_baseSock, (struct sockaddr*)&addr, sizeof(addr)) != 0)
   {
      return XERROR;
   }

   // Tell the socket to listen for a connection from client.
   if (listen(m_baseSock, 4) != 0)
   {
      return XERROR;
   }
   return XSUCCESS;
}

ConnectSocket()
{
   // Add the socket to a file descriptor set.
   fd_set readfds;
   FD_ZERO(&readfds);
   FD_SET(m_baseSock, &readfds);

   // Set timeout to ten seconds. Plenty of time.
   struct timeval timeout;
   timeout.tv_sec = 10;
   timeout.tv_usec = 0;

   // Check to see if the socket is ready for reading.
   int numReady = select(m_baseSock + 1, &readfds, NULL, NULL, &timeout);
   if (numReady > 0)
   {
      int flags = fcntl(m_baseSock, F_GETFL, 0);
      fcntl(m_baseSock, flags | O_NONBLOCK, 1);

      // Wait for a connection attempt from the client. Do not block - we shouldn't
      // need to since we just selected.
      m_connectedSock = accept(m_baseSock, NULL, NULL);
      if (m_connectedSock > 0)
      {
         m_failedSend = false;
         m_logout = false;

         // Spawn a thread to accept commands from client.
         CreateThread(&m_controlThread, ControlThread, (void *)&m_connectedSock);

         return XSUCCESS;
      }
   }
   return XERROR;
}

ControlThread(void *arg)
{
   // Get the socket from the argument.
   socket sock = *((socket*)arg);

   while (true)
   {
      // Add the socket to a file descriptor set.
      fd_set readfds;
      FD_ZERO(&readfds);
      FD_SET(sock, &readfds);

      // Set timeout to ten seconds. Plenty of time.
      struct timeval timeout;
      timeout.tv_sec = 10;
      timeout.tv_usec = 0;

      // Check if there is any readable data on the socket.
      int num_ready = select(sock + 1, &readfds, NULL, NULL, &timeout);
      if (num_ready < 0)
      {
         return NULL;
      }

      // If there is data, read it.
      else if (num_ready > 0)
      {
         // Check the read buffer.
         xuint8 buf[128];
         ssize_t size_read = recv(sock, buf, sizeof(buf));
         if (size_read > 0)
         {
            // Get the message out of the buffer.
            char msg = *buf;
            if (msg == CONNECTED)
            {
               // Do some things...
            }
            // If we get the log-out message, log out.
            else if (msg == LOGOUT)
            {
               return NULL;
            }
         }
      }
   } // while
   return NULL;
}

~Server()
{
   // Close the sockets.
   if (m_baseSock != SOCKET_ERROR)
   {
      close(m_baseSock);
      m_baseSock = SOCKET_ERROR;
   }
   if (m_connectedSock != SOCKET_ERROR)
   {
      close(m_connectedSock);
      m_connectedSock = SOCKET_ERROR;
   }
}

SOCKET_ERROR равно -1. Объект сервера уничтожается, после чего соединение должно закрыться, а затем создается заново, после чего вызываются процедуры SetUpSocket() и ConnectSocket().

Так почему я должен ждать минуту, пока сокет очистится? Любые идеи будут оценены.

РЕДАКТИРОВАТЬ: следуя советам моих первых плакатов, я нашел способ заставить клиента закрыть сокет с его конца. Однако что-то все равно не так. Теперь netstat показывает сокет с точки зрения сервера в TIME_WAIT, а с точки зрения клиента нет записи. Все, что у меня есть, это:

TCP 0 0 localhost.localdomain:19876 localhost.localdomain:54598 TIME_WAIT

и ничего наоборот. Серверу и клиенту по-прежнему требуется ровно минута для сброса TIME_WAIT, чтобы иметь возможность повторно подключиться. Теперь, что не так - неправильно ли использовать close() на клиентском сокете?

РЕДАКТИРОВАТЬ 2: Теперь, если я заставлю клиента повторно подключиться, он будет немедленно, но если я просто позволю ему делать свое дело, он будет ждать целую минуту, пока TIME_WAIT не очистится. Я подозреваю, что что-то не так в клиентском коде. Я не так уж много могу с этим поделать.

16.03.2011

Ответы:


1

Сервер ожидает, пока клиент отправит пакет FIN. Это должно быть сделано путем закрытия сокета на стороне клиента (или, возможно, закрытия приложения). Затем сервер должен перейти в состояние TIME_WAIT, ожидая истечения времени ожидания сокета. SO_REUSEADDR позволяет обойти этот статус.

введите здесь описание изображения

(Источник http://upload.wikimedia.org/wikipedia/commons/0/08/TCP_state_diagram.jpg)

16.03.2011
  • Я согласен с тем, что вы говорите, но я уже использую SO_REUSEADDR, и это не работает. Что-то еще вызывает проблему. Любые другие идеи? Я действительно не контролирую клиент, но он также должен правильно закрывать сокет. (Ударение должно быть, потому что я не могу этого знать.) 16.03.2011
  • @patrickvacek: CLOSE_WAIT — очень сильный признак того, что клиент не закрывает свою сторону соединения. Что произойдет, если вы убьете клиент и запустите другой? 16.03.2011
  • @LHMathies: Запустить и остановить клиент непросто и требует некоторого времени (это гигантское приложение), но действительно, как только он закрывается, netstat не показывает соединение, и как только он снова запускается, сервер и клиент могут соединиться. Мне удалось сократить время обработки до 11 секунд, большую часть которых приходится ждать, пока клиент закроется. Не совсем уверен, как действовать дальше, так как клиент на самом деле не моя территория. Придется вкладываться дальше. @M'vy: Это хорошая диаграмма; Благодарность! Кто-нибудь знает, как лучше всего я могу видеть отправку и получение FIN и ACKS? 16.03.2011
  • @patrickvacek: Wireshark или старый добрый tcpdump, чтобы увидеть, какие пакеты на самом деле отправляются и принимаются. О SO_LINGER: некоторые документы предполагают, что установка его (на ноль секунд) на стороне сервера должна заставить его отправлять RST вместо FIN, вызывая ошибку на стороне клиента и, надеюсь, переподключение - но я только что проверил код ядра Linux , и это происходит только в том случае, если есть буферизованные непрочитанные данные приема или ожидающие (un-ACK-ed) данные отправки в полете. Я думаю, вам нужно убедить людей-клиентов закрыть сокет (и переподключиться), если они увидят EOF при чтении (что они увидят, когда вы закроете). 16.03.2011
  • Определенно проблема на стороне клиента, мой сервер делает все возможное. Спасибо всем за помощь в выяснении деталей! 17.03.2011

  • 2

    CLOSE_WAIT на клиенте означает, что сетевой уровень ожидает, пока приложение отправит дополнительные данные или закроет сокет, чтобы оно могло начать свою сторону завершающего рукопожатия с сервером. Как работает TCP, одна сторона не может заставить другую закрыться «хорошо» — два направления работают независимо, и отправитель имеет всю инициативу — но сетевой уровень сервера может прервать соединение по тайм-ауту и ​​прервать соединение с RST один раз. серверная программа закрыла сокет на этой стороне (потому что, даже если клиент отправляет больше данных, никто не может их прочитать).

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

    SO_LINGER не влияет на эту ситуацию, если вы не оставляете данные непрочитанными на стороне клиента, когда он закрывает соединение.

    16.03.2011
    Новые материалы

    Кластеризация: более глубокий взгляд
    Кластеризация — это метод обучения без учителя, в котором мы пытаемся найти группы в наборе данных на основе некоторых известных или неизвестных свойств, которые могут существовать. Независимо от..

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

    Частный метод Python: улучшение инкапсуляции и безопасности
    Введение Python — универсальный и мощный язык программирования, известный своей простотой и удобством использования. Одной из ключевых особенностей, отличающих Python от других языков, является..

    Как я автоматизирую тестирование с помощью Jest
    Шутка для победы, когда дело касается автоматизации тестирования Одной очень важной частью разработки программного обеспечения является автоматизация тестирования, поскольку она создает..

    Работа с векторными символическими архитектурами, часть 4 (искусственный интеллект)
    Hyperseed: неконтролируемое обучение с векторными символическими архитектурами (arXiv) Автор: Евгений Осипов , Сачин Кахавала , Диланта Хапутантри , Тимал Кемпития , Дасвин Де Сильва ,..

    Понимание расстояния Вассерштейна: мощная метрика в машинном обучении
    В обширной области машинного обучения часто возникает необходимость сравнивать и измерять различия между распределениями вероятностей. Традиционные метрики расстояния, такие как евклидово..

    Обеспечение масштабируемости LLM: облачный анализ с помощью AWS Fargate и Copilot
    В динамичной области искусственного интеллекта все большее распространение получают модели больших языков (LLM). Они жизненно важны для различных приложений, таких как интеллектуальные..