Reputation: 527
I wrote C++ service using WinAPI for socket operations. Communication between client and service is simple, after connection to service client sends some data and service sends response shortly after that.
Master socket (ListenSocket
) on service side is created like this:
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 loop on master socket:
while(...)
{
if(select(0, &readSet, NULL, NULL, &timeout) == 1)
{
ClientSocket = accept(ListenSocket, NULL, NULL); // SOCKET type, declared in global scope.
/* err checking */
break;
}
}
After breaking loop service starts reading from ClientSocket
and after that service sends response on this socket:
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);
After closesocket()
process returns to select-accept loop and waits for another connections.
My problem is that sometimes Client recieve not enough data and gets information that "Connection was closed by remote host". After debugging and some research i found this note on 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.
Delay You say Microsoft? :( Ok, I've implemented the short delay (/* FIX sleep(50) */
) and the problem is gone.
But this does not satisfy me at all. I would like to remove this sleep()
and make sure that buffers were fully flushed after send and that I can safely shutdown()
and closesocket()
.
I've tried to make master socket non-blocking with hope that send()
will block until completion but I were wrong - no effect.
Is there something else I can do to make it work without sleep()
?
Upvotes: 1
Views: 2862
Reputation: 527
The solution I decided to use:
1) call shutdown()
only with SD_SEND
instead of SD_BOTH
after send()
.
2) implement loop which waits until non-blocking recv()
return 0 meaning that client gracefully closed socket. Method exits with an error when socket received data, max retries were performed or some other error occurred.
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
}
}
Upvotes: 1
Reputation: 149195
The common way is that one part issues only a SD_SEND shutdown, then the peer will end with a 0 length read and will be able to close because nothing can still remain on the communication channel. A slightly more detailed explaination with some references can be found on this other answer of mine.
Upvotes: 1