У меня есть серверная программа, которая подключается к другой программе через заданный сокет, и в некоторых случаях мне нужно закрыть соединение и почти сразу же снова открыть его на том же сокете. Это по большому счету работает, за исключением того, что мне нужно ждать ровно одну минуту, чтобы сокет сбросился. Тем временем 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 не очистится. Я подозреваю, что что-то не так в клиентском коде. Я не так уж много могу с этим поделать.
CLOSE_WAIT
— очень сильный признак того, что клиент не закрывает свою сторону соединения. Что произойдет, если вы убьете клиент и запустите другой? 16.03.2011SO_LINGER
: некоторые документы предполагают, что установка его (на ноль секунд) на стороне сервера должна заставить его отправлятьRST
вместоFIN
, вызывая ошибку на стороне клиента и, надеюсь, переподключение - но я только что проверил код ядра Linux , и это происходит только в том случае, если есть буферизованные непрочитанные данные приема или ожидающие (un-ACK
-ed) данные отправки в полете. Я думаю, вам нужно убедить людей-клиентов закрыть сокет (и переподключиться), если они увидят EOF при чтении (что они увидят, когда вы закроете). 16.03.2011