我使用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-accept循环:
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()
进程返回select-accept循环后等待另一个连接。
我的问题是,有时客户端收不到足够的数据并获取“远程主机已关闭连接”的信息。 经过调试和一些研究后,我在MSDN上找到了这个说明:
使用带有SD_SEND或SD_BOTH的closesocket或shutdown功能会导致在控制通道上发送RELEASE信号。由于ATM使用单独的信号和数据信道,RELEASE信号有可能在最后一个数据到达目的地之前到达远端,导致数据丢失。一种可能的解决方案是在发送的最后一个数据和ATM套接字的closesocket或shutdown函数调用之间编写足够的延迟。
延迟你说微软? :(好的,我实施了短暂的延迟(/* FIX sleep(50) */
),问题就消失了。
但这根本不能让我满意。我想删除此sleep()
并确保在发送后完全刷新缓冲区,并且我可以安全地shutdown()
和closesocket()
。
我试图使主套接字无阻塞,希望send()
阻止直到完成,但我错了 - 没有效果。
我还能做些什么来让它在没有sleep()
的情况下发挥作用吗?
答案 0 :(得分:1)
常见的方法是,一个部分仅发出SD_SEND关闭,然后对等体将以0长度读取结束,并且能够关闭,因为通信信道上仍然没有任何内容。在我的另一个answer上可以找到一些稍微更详细的解释。
答案 1 :(得分:1)
我决定使用的解决方案:
1)仅在shutdown()
之后使用SD_SEND
而不是SD_BOTH
致电send()
。
2)实现循环,等待直到非阻塞recv()
返回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
}
}