如何使用 winsock2 应用 Linger 选项

How to apply Linger option with winsock2

我想在关闭 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 => FIN-ACK & TIME_WAIT of 0s.

更新: 正如 Remy Lebeauclosesocket 参考中指出的那样,{nonzero,0} 的 Linger 选项被硬编码为生成 RST .

几秒钟的短暂 TIME_WAIT 也一样好,即 {1,1} 的延迟选项导致 closesocket 以 1 秒 TIME_WAIT 的周期优雅地退出,根据 closesocket 文档,这应该是可能的。

更新 2: 正如 Remy Lebeau 再次指出的,Linger 选项和 TIME_WAIT 周期没有关联。如果你正在读这篇文章,你可能犯了和我一样的错误,并试图通过 setsockoptSO_LINGER 来缩短 TIME_WAIT 周期。

根据所有无法完成的帐户以及在仔细考虑判断需要避免的情况下 TIME_WAIT(例如在我的情况下,应用层协议可以处理杂散或孤立的 TCP 数据包) 理想的选项看起来是 {1,0} 的 Linger 设置,以强制硬 RST 套接字关闭,这将允许立即重新尝试连接而不会 OS 阻止尝试。

当您的应用程序首先关闭 TCP 连接时,您无法真正避免 TIME_WAIT(当对等方首先关闭连接时不会发生 TIME_WAIT)。再多的 SO_LINGER 设置也不会改变这个事实,除了执行异常的套接字关闭(即发送 RST 数据包)。它只是 TCP 工作方式的一部分(查看 TCP state diagram)。 SO_LINGER 只是控制 closesocket() 在实际关闭活动连接之前等待的时间。

防止套接字进入TIME_WAIT状态的唯一方法是将l_linger持续时间设置为0,并且不要调用shutdown(SD_SEND)shutdown(SD_BOTH)完全没有(调用 shutdown(SD_RECEIVE) 是可以的)。这是 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.

您的代码的真正问题(除了缺少错误处理之外)是您的客户端在 connect() 将其连接到服务器之前 bind() 连接客户端套接字。通常,您根本不应该 bind() 客户端套接字,您应该让 OS 为您选择合适的绑定。但是,如果您必须 bind() 一个客户端套接字,您可能需要在该套接字上启用 SO_REUSEADDR 选项以避免在先前连接到同一本地 IP/Port 的连接仍然存在时被阻塞处于 TIME_WAIT 状态,并且您正试图在前一个 closesocket().

之后的短时间内 connect()

请参阅您的问题中的 How to avoid TIME_WAIT state after closesocket() ? for more details. Also, the document you linked to 还解释了避免 TIME_WAIT 而不求助于 SO_LINGER 的方法。