shutdown() 和 closesocket() 在 SOCKET 上的 send() 之后不久
shutdown() and closesocket() shortly after send() on SOCKET
我使用 WinAPI 为套接字操作编写了 C++ 服务。客户端和服务之间的通信很简单,在连接到服务客户端后发送一些数据,服务随后发送响应。
服务端的主套接字(ListenSocket
)是这样创建的:
SOCKET ListenSocket = socket(pAddrResult->ai_family, pAddrResult->ai_socktype, pAddrResult->ai_protocol);
/* err checking */
/* Setting socket I/O mode to non-blocking */
u_long nonBlockingMode = 1;
opResult = ioctlsocket(ListenSocket, FIONBIO, &nonBlockingMode);
/* err checking */
Select-在主套接字上接受循环:
while(...)
{
if(select(0, &readSet, NULL, NULL, &timeout) == 1)
{
ClientSocket = accept(ListenSocket, NULL, NULL); // SOCKET type, declared in global scope.
/* err checking */
break;
}
}
中断循环后,服务开始从 ClientSocket
读取,然后服务在此套接字上发送响应:
int iSendResult = send(ClientSocket, (const char*) messageBuffer.getData(), messageBuffer.getSize(), 0);
/* err checking */
/* FIX sleep(50) */
int opResult = shutdown(ClientSocket, SD_BOTH);
/* err checking */
closesocket(ClientSocket);
在 closesocket()
处理 returns 到 select-接受循环并等待另一个连接后。
我的问题是,有时客户端接收到的数据不足并获取 "Connection was closed by remote host" 的信息。
经过调试和一些研究后,我在 MSDN 上找到了这条注释:
Using the closesocket or shutdown functions with SD_SEND or SD_BOTH results in a RELEASE signal being sent out on the control channel. Due to ATM's use of separate signal and data channels, it is possible that a RELEASE signal could reach the remote end before the last of the data reaches its destination, resulting in a loss of that data. One possible solutions is programming a sufficient delay between the last data sent and the closesocket or shutdown function calls for an ATM socket.
延迟你说微软? :( 好的,我已经实施了短延迟 (/* FIX sleep(50) */
),问题消失了。
但这一点都不让我满意。我想删除这个 sleep()
并确保缓冲区在发送后被完全刷新并且我可以安全地 shutdown()
和 closesocket()
.
我试图让主套接字成为非阻塞的,希望 send()
会阻塞直到完成,但我错了 - 没有效果。
如果没有 sleep()
,我还能做些什么来让它工作?
常见的方式是一个部分只发出 SD_SEND 关闭,然后对等方将以 0 长度读取结束并且能够关闭,因为通信通道上仍然没有任何内容。在我的另一个answer
上可以找到更详细的解释和一些参考资料。
我决定使用的解决方案:
1) 在 send()
.
之后仅使用 SD_SEND
而不是 SD_BOTH
调用 shutdown()
2) 实现等待非阻塞的循环 recv()
return 0 表示客户端正常关闭套接字。当套接字接收到数据、执行最大重试或发生其他错误时,方法退出并出现错误。
while (true)
{
int result = recv(ClientSocket, &buff, sizeof(buff), 0);
if (result == 0)
return 0; // Client gracefully closed connection.
else if (result > 0)
return -1; // Received unexpected data instead of socket closure
else
{
if (WSAGetLastError() == WSAEWOULDBLOCK)
{
if (retryNumber++ == MAX_RETRY_NUMBER)
return -1; // Client didn't close socket within specified time
sleep(10); // wait 10ms
}
else
return -1; // Unexpected error occured
}
}
我使用 WinAPI 为套接字操作编写了 C++ 服务。客户端和服务之间的通信很简单,在连接到服务客户端后发送一些数据,服务随后发送响应。
服务端的主套接字(ListenSocket
)是这样创建的:
SOCKET ListenSocket = socket(pAddrResult->ai_family, pAddrResult->ai_socktype, pAddrResult->ai_protocol);
/* err checking */
/* Setting socket I/O mode to non-blocking */
u_long nonBlockingMode = 1;
opResult = ioctlsocket(ListenSocket, FIONBIO, &nonBlockingMode);
/* err checking */
Select-在主套接字上接受循环:
while(...)
{
if(select(0, &readSet, NULL, NULL, &timeout) == 1)
{
ClientSocket = accept(ListenSocket, NULL, NULL); // SOCKET type, declared in global scope.
/* err checking */
break;
}
}
中断循环后,服务开始从 ClientSocket
读取,然后服务在此套接字上发送响应:
int iSendResult = send(ClientSocket, (const char*) messageBuffer.getData(), messageBuffer.getSize(), 0);
/* err checking */
/* FIX sleep(50) */
int opResult = shutdown(ClientSocket, SD_BOTH);
/* err checking */
closesocket(ClientSocket);
在 closesocket()
处理 returns 到 select-接受循环并等待另一个连接后。
我的问题是,有时客户端接收到的数据不足并获取 "Connection was closed by remote host" 的信息。 经过调试和一些研究后,我在 MSDN 上找到了这条注释:
Using the closesocket or shutdown functions with SD_SEND or SD_BOTH results in a RELEASE signal being sent out on the control channel. Due to ATM's use of separate signal and data channels, it is possible that a RELEASE signal could reach the remote end before the last of the data reaches its destination, resulting in a loss of that data. One possible solutions is programming a sufficient delay between the last data sent and the closesocket or shutdown function calls for an ATM socket.
延迟你说微软? :( 好的,我已经实施了短延迟 (/* FIX sleep(50) */
),问题消失了。
但这一点都不让我满意。我想删除这个 sleep()
并确保缓冲区在发送后被完全刷新并且我可以安全地 shutdown()
和 closesocket()
.
我试图让主套接字成为非阻塞的,希望 send()
会阻塞直到完成,但我错了 - 没有效果。
如果没有 sleep()
,我还能做些什么来让它工作?
常见的方式是一个部分只发出 SD_SEND 关闭,然后对等方将以 0 长度读取结束并且能够关闭,因为通信通道上仍然没有任何内容。在我的另一个answer
上可以找到更详细的解释和一些参考资料。我决定使用的解决方案:
1) 在 send()
.
SD_SEND
而不是 SD_BOTH
调用 shutdown()
2) 实现等待非阻塞的循环 recv()
return 0 表示客户端正常关闭套接字。当套接字接收到数据、执行最大重试或发生其他错误时,方法退出并出现错误。
while (true)
{
int result = recv(ClientSocket, &buff, sizeof(buff), 0);
if (result == 0)
return 0; // Client gracefully closed connection.
else if (result > 0)
return -1; // Received unexpected data instead of socket closure
else
{
if (WSAGetLastError() == WSAEWOULDBLOCK)
{
if (retryNumber++ == MAX_RETRY_NUMBER)
return -1; // Client didn't close socket within specified time
sleep(10); // wait 10ms
}
else
return -1; // Unexpected error occured
}
}