【问题标题】:How to apply Linger option with winsock2如何使用winsock2应用Linger选项
【发布时间】: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 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 数据包),理想的选择是将 Linger 设置为 {1,0} 以强制关闭RST 套接字,这将允许立即重新尝试连接,而操作系统不会阻止尝试。

【问题讨论】:

    标签: c++ windows winsock2


    【解决方案1】:

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

    防止socket进入TIME_WAIT状态的唯一方法是将l_linger的持续时间设置为0,并且完全不要调用shutdown(SD_SEND)shutdown(SD_BOTH)(调用shutdown(SD_RECEIVE)就可以了) .这是documented behavior

    closesocket 调用只会阻塞,直到所有数据都已交付给对等方或超时到期。如果连接因为超时而被重置,那么套接字将不会进入 TIME_WAIT 状态。如果在超时时间内所有数据都发送完毕,则socket可以进入TIME_WAIT状态。

    如果 linger 结构的 l_onoff 成员非零且 l_linger 成员是阻塞套接字上的零超时间隔,则调用 closesocket 将重置连接。套接字不会进入 TIME_WAIT 状态。

    您的代码的真正问题(除了缺少错误处理)是您的客户端在connect()'将其发送到服务器之前bind()'ing 客户端套接字。通常,您根本不应该 bind() 客户端套接字,您应该让操作系统为您选择合适的绑定。但是,如果您必须 bind() 客户端套接字,则可能需要在该套接字上启用 SO_REUSEADDR 选项以避免在与同一本地 IP/端口的先前连接仍处于 TIME_WAIT 状态时被阻塞,并且您正在尝试在之前的closesocket() 之后的短时间内connect()

    有关详细信息,请参阅How to avoid TIME_WAIT state after closesocket() ?。此外,您的问题中的the document you linked to 还解释了避免TIME_WAIT 的方法,而不是诉诸SO_LINGER

    【讨论】:

    • 谢谢,一定跳过了 closesocket 细节。我没有得到 l_onoff 非零和 l_linger 非零的描述行为。对于我所有的尝试,TIME_WAIT 始终是 30 秒。我跳过了上下文,但我确实需要始终绑定到同一个众所周知的端口,并且我正在设置 SO_REUSEADDR(它是一个 SIP 服务器应用程序,TCP 连接必须来自端口 5060)。即使使用 SO_REUSEADDR,如果确切的套接字对处于 TIME_WAIT 状态,也无法避免连接错误。 .
    • closesocket() 在关闭连接之前等待的时间(SO_LINGER 超时)与连接在实际关闭后保持在TIME_WAIT 状态的时间不同。您不能设置TIME_WAIT 本身的超时时间(不借助特定于平台的系统设置),其持续时间由 TCP 标准规定。而且我熟悉 SIP 的工作原理,SIP servers 侦听端口 5060,但 SIP clients 不需要从端口 5060 连接到服务器,它们可以使用任何他们想要的源端口。同样,不需要connect() 之前的bind()
    • 啊,现在明白了。我陷入了一种陈规,认为 linger 选项的目的是影响 TIME_WAIT 期间。它是一个 SIP 服务器,充当其他 SIP 服务器的客户端。端口 5060 通常会被列入白名单。 99% 的流量是 UDP,所以没有戏剧性。需要从单个端口发起 TCP 的最后 1% 是戏剧性的。
    • 同样,在连接到侦听端口 5060 的服务器之前,SIP 客户端套接字不需要在本地绑定到端口 5060。您的应用程序是使用客户端套接字连接到其他 SIP 的 SIP 服务器服务器不会改变这一点。
    • 在这种情况下,如果 SIP TCP 连接源自端口 5060,它将被接受。如果它来自一个随机端口,它会被阻塞。这是一个政策设置。
    猜你喜欢
    • 1970-01-01
    • 2012-06-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-01-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多