【发布时间】:2019-10-22 14:43:49
【问题描述】:
我想在关闭 TCP 套接字时避免 TIME_WAIT 状态(我知道绕过 TIME_WAIT 的pros and cons)。
我正在使用 Windows 和 WinSock2/.Net 套接字,并且很难让 SO_LINGER 套接字选项按照 documentation 中的说明工作。
为简洁起见,我删除了大部分错误检查的测试代码是:
#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;
}
结果:
starting...
Linger option set to onoff 1, linger seconds 0.
connected.
finished.
上面的示例确实避免了 TIME_WAIT 状态,但这样做是因为客户端套接字发送了一个 RST 数据包。
如果Linger 选项更改为:
linger lingerOpt = { 1, 5 };
结果
starting...
Linger option set to onoff 1, linger seconds 5.
connected.
finished.
然后关闭套接字确实会导致 TIME_WAIT 但为 30 秒,这与未设置 SO_LINGER 选项的结果相同。
另一个观察结果是,如果使用shutdown(clientSocket, SD_BOTH); 关闭套接字(这是干净关闭的推荐方法),那么{1,0} 的Linger 选项将没有任何影响。
总结:
- 将 Linger 选项设置为 {1,0} 并使用 closesocket => RST 关闭。
- 将 Linger 选项设置为 {1,5} 并使用 closesocket 关闭 => FIN-ACK 和 TIME_WAIT 为 30 秒。
- 将 Linger 选项设置为 {1,0} 并关闭并关闭,closesocket => FIN-ACK 和 TIME_WAIT 为 30 秒。
- 将 Linger 选项设置为 {1,5} 并关闭并关闭,closesocket => FIN-ACK 和 TIME_WAIT 为 30 秒。
我想要的是:
将 Linger 选项设置为 {1,0} & close with shutdown, closesocket => FIN-ACK & TIME_WAIT of 0s。
更新:正如Remy Lebeau 在closesocket 引用中指出的那样,{nonzero,0} 的 Linger 选项被硬编码以生成 RST。
几秒钟的短暂 TIME_WAIT 也一样好,即 {1,1} 的逗留选项导致 closesocket 以 1 秒的 TIME_WAIT 周期优雅退出,根据 closesocket 文档应该是可能。
更新 2: 正如Remy Lebeau 再次指出的那样,Linger 选项和 TIME_WAIT 周期没有关联。如果您正在阅读本文,您可能犯了与我相同的错误,并试图通过 setsockopt 和 SO_LINGER 缩短 TIME_WAIT 周期。
根据所有无法做到的情况,以及在仔细考虑判断 TIME_WAIT 需要避免的情况(例如在我的情况下,应用层协议可以处理杂散或孤立的 TCP 数据包),理想的选择是将 Linger 设置为 {1,0} 以强制关闭RST 套接字,这将允许立即重新尝试连接,而操作系统不会阻止尝试。
【问题讨论】: