我有一个服务器程序通过给定的套接字连接到另一个程序,在某些情况下我需要关闭连接并几乎立即在同一个套接字上重新打开它。这个大体上的工作,除了我必须等待一分钟才能重置套接字。在此期间,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清除整整一分钟。我怀疑客户端代码中有些东西搞砸了。我不能做太多。
答案 0 :(得分:10)
服务器正在等待客户端发送FIN
数据包。这应该通过关闭客户端上的套接字(或者关闭应用程序)来完成。然后服务器将进入TIME_WAIT
状态,等待套接字超时。 SO_REUSEADDR
可让您绕过此状态。
(来源http://upload.wikimedia.org/wikipedia/commons/0/08/TCP_state_diagram.jpg)
答案 1 :(得分:2)
CLOSE_WAIT
表示网络层正在等待应用程序发送更多数据或关闭套接字,以便它可以从与服务器的结束握手开始。 TCP工作的方式,一方不能强迫另一方“很好地”关闭 - 两个方向独立工作,发送方有所有主动权 - 但服务器网络层可以超时并中止与{{的连接1}}一旦服务器程序关闭了那一侧的套接字(因为即使客户端发送了更多数据,也没有人在那里读取它)。
我猜测服务器网络层给客户端一分钟关闭,只是为了好,或者客户端在那时发送保持活动,触发重置。
SO_LINGER不会影响这种情况,除非您在关闭连接时在客户端保留未读数据。