Reputation: 30699
I want to avoid the TIME_WAIT state when closing a TCP socket (I am aware of the pros and cons of circumventing TIME_WAIT).
I am using Windows and WinSock2/.Net sockets and am having great difficulty getting the SO_LINGER
socket option to work as described in the documentation.
My test code with most of the error checking removed for brevity is:
#include <winsock2.h>
#include <ws2tcpip.h>
#include <iostream>
int main()
{
std::cout << "starting..." << std::endl;
WSADATA w = { 0 };
int error = WSAStartup(0x0202, &w);
if (error || w.wVersion != 0x0202) {
std::cerr << "Could not initialise Winsock2." << std::endl;
return -1;
}
auto clientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
// Set socket options
linger lingerOpt = { 1, 0 };
setsockopt(clientSocket, SOL_SOCKET, SO_LINGER, (char*)&lingerOpt, sizeof(lingerOpt));
linger checkLingerOpt{ 0 };
int optLen = sizeof(checkLingerOpt);
int getOptResult = getsockopt(clientSocket, SOL_SOCKET, SO_LINGER, (char*)&checkLingerOpt, &optLen);
if (getOptResult < 0) {
wprintf(L"Failed to get SO_LINGER socket option on client socket, error: %ld\n", WSAGetLastError());
}
else {
std::cout << "Linger option set to onoff " << checkLingerOpt.l_onoff << ", linger seconds " << checkLingerOpt.l_linger << "." << std::endl;
}
// Bind local client socket.
sockaddr_in clientBindAddr;
clientBindAddr.sin_family = AF_INET;
clientBindAddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
clientBindAddr.sin_port = htons(15064);
bind(clientSocket, (SOCKADDR*)&clientBindAddr, sizeof (clientBindAddr));
sockaddr_in serverSockAddr;
serverSockAddr.sin_family = AF_INET;
serverSockAddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
serverSockAddr.sin_port = htons(5060);
// Connect to server.
connect(clientSocket, (SOCKADDR*)&serverSockAddr, sizeof (serverSockAddr));
std::cout << "connected." << std::endl;
Sleep(1000);
//shutdown(clientSocket, SD_BOTH);
closesocket(clientSocket);
std::cout << "finished." << std::endl;
}
Result:
starting...
Linger option set to onoff 1, linger seconds 0.
connected.
finished.
The sample above does avoid the TIME_WAIT state but does so because the client socket sends a RST packet.
If the Linger
option is changed to:
linger lingerOpt = { 1, 5 };
Result
starting...
Linger option set to onoff 1, linger seconds 5.
connected.
finished.
Then closing the socket does result in a TIME_WAIT but of 30s which is the same result as not setting the SO_LINGER
option.
Another observation is that if the socket is shutdown (which is the recommended way to cleanly close) with shutdown(clientSocket, SD_BOTH);
then the Linger option of {1,0}
will have no affect.
In summary:
What I'd like is:
Set Linger option as {1,0} & close with shutdown, closesocket => FIN-ACK & TIME_WAIT of 0s.
Update: As pointed out in the closesocket reference by Remy Lebeau
a Linger option of {nonzero,0} is hard coded to generate a RST.
A short TIME_WAIT of a few seconds would be just as good, i.e. a linger option of {1,1} caused closesocket
to exit gracefully with a 1s TIME_WAIT period, and which according to the closesocket
documentation should be possible.
Update 2: As again pointed out by Remy Lebeau
the Linger option and TIME_WAIT period are NOT linked. If you're reading this you probably made the same mistake I did and were trying to shorten the TIME_WAIT period via setsockopt
and SO_LINGER
.
By all accounts that can't be done and in cases where careful consideration judges TIME_WAIT needs to be avoided (such as in my case where the application layer protocol can deal with stray or orphaned TCP data packets) the ideal option looks to be a Linger setting of {1,0} to force a hard RST
socket close which will allow the connection to be immediately re-attempted without the OS blocking the attempt.
Upvotes: 1
Views: 1962
Reputation: 596387
You can't really avoid TIME_WAIT
when your app is the one closing the TCP connection first (TIME_WAIT
does not happen when the peer closes the connection first). No amount of SO_LINGER
settings will change that fact, other than performing an abortive socket closure (ie sending a RST
packet). It is simply part of how TCP works (look at the TCP state diagram). SO_LINGER
simply controls how long closesocket()
waits before actually closing an active connection.
The only way to prevent the socket from entering the TIME_WAIT
state is to set the l_linger
duration to 0, and don't call shutdown(SD_SEND)
or shutdown(SD_BOTH)
at all (calling shutdown(SD_RECEIVE)
is OK). This is documented behavior:
The closesocket call will only block until all data has been delivered to the peer or the timeout expires. If the connection is reset because the timeout expires, then the socket will not go into TIME_WAIT state. If all data is sent within the timeout period, then the socket can go into TIME_WAIT state.
If the l_onoff member of the linger structure is nonzero and the l_linger member is a zero timeout interval on a blocking socket, then a call to closesocket will reset the connection. The socket will not go to the TIME_WAIT state.
The real problem with your code (aside from the lack of error handling) is that your client is bind()
'ing a client socket before connect()
'ing it to a server. Typically, you should not bind()
a client socket at all, you should let the OS choose an appropriate binding for you. However, if you must bind()
a client socket, you will likely need to enable the SO_REUSEADDR
option on that socket to avoid being blocked when a previous connection boudn to the same local IP/Port is still in TIME_WAIT
state and you are trying to connect()
in a short amount of time after the previous closesocket()
.
See How to avoid TIME_WAIT state after closesocket() ? for more details. Also, the document you linked to in your question also explains ways to avoid TIME_WAIT
without resorting to messing with SO_LINGER
.
Upvotes: 2