Предотвращение FIN_WAIT2 при закрытии сокета - PullRequest
2 голосов
/ 16 марта 2011

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

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

EDIT: Следуя советам моих первых авторов, я нашел способ заставить клиента закрыть сокет с его конца. Что-то все еще не правильно, хотя. Теперь netstat показывает сокет с точки зрения сервера в TIME_WAIT, а запись с точки зрения клиента отсутствует. Все, что у меня есть, это:

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

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

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

Ответы [ 2 ]

10 голосов
/ 16 марта 2011

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

enter image description here

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

2 голосов
/ 16 марта 2011

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...