ServicePointManager.ReusePort和SO_REUSE_UNICASTPORT如何缓解短暂的端口耗尽?

时间:2017-06-14 15:05:33

标签: c# .net sockets .net-4.6 windows-server-2016

Windows 10和Windows Server 2016引入了SO_REUSE_UNICASTPORT套接字选项。它从版本4.6开始通过ServicePointManager.ReusePort静态属性在.NET中使用。我在.NET应用程序中遇到非常高负载时的短暂端口耗尽(许多并发传出请求通过HttpClient),我正在考虑使用此选项来处理它。我知道处理问题的其他方法(例如编辑Windows注册表以修改临时端口的最大数量或缩短TIME_WAIT),但我也想完全遵循此解决方案。

ServicePointManager.ReusePort的文档很少:

  

将此属性值设置为true会导致来自HttpWebRequest的所有出站TCP连接都使用套接字上的本机套接字选项SO_REUSE_UNICASTPORT。这会导致共享底层传出端口。这对于在短时间内建立大量传出连接并且应用程序可能会耗尽端口的情况非常有用。

查看SO_REUSE_UNICASTPORT的文档并未提供任何其他见解:

  

设置后,允许对需要显式绑定的Winsock API连接函数进行短暂端口重用,例如ConnectEx。请注意,具有隐式绑定的连接函数(例如没有显式绑定的连接)默认设置此选项。在两者都可用的平台上使用此选项而不是SO_PORT_SCALABILITY。

我无法在网上找到关于这个"短暂端口重用"的确切解释。实现了它,它在技术层面上的确切运作方式,以及它如何降低短暂端口耗尽的风险。我可以期待多少改进?使用此功能,如何计算应用程序的新限制?启用此功能是否有任何缺点?

这一切都笼罩在神秘之中,如果有人能够解释这个新机制及其含义,我就会喜欢它。

1 个答案:

答案 0 :(得分:7)

TCP连接由(本地IP,本地端口,远程IP,远程端口)唯一标识。这意味着完全可以将相同(本地IP,本地端口)对用于连接到不同远程端点的多个套接字。假设您要向" site1.com"发出http请求。和" site2.com"。您使用的套接字包含以下代码:

using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) {                
    socket.Bind(new IPEndPoint(IPAddress.Parse("some local ip"), 45455));
    socket.Connect(server, port);
    socket.Send(someBytes);
    // ...
}

因此,您使用端口45455将套接字绑定到特定的本地端点。如果您现在尝试同时执行此操作以向" site1.com"和" site2.com"你会得到一个已经在使用的地址"异常。

但是如果您在绑定之前添加ReuseAddress选项(请注意它不是您的问题的选项):

socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);

您将能够将套接字绑定到同一个本地(ip,端口),您将在netstat中看到两个ESTABLISHED连接。

以上所有内容都表明,理论上没有什么能阻止人们重复使用短暂的端口与不同的远程端点建立多个连接。但是,当您绑定到临时端口(0)时 - 您还不知道要连接哪个远程端点。假设所有临时端口都在使用,并且您绑定到0.操作系统为您提供了一些在绑定阶段重用的端口,有一个套接字使用此端口已连接到" site1.com"。您正在尝试连接到" site1.com"也失败了(因为两个套接字的所有4个标识tcp连接的值都相同)。

当你绑定到0直到实际连接阶段(例如SO_REUSE_UNICASTPORT调用)时,Connect()做什么延迟选择短暂端口。在这个阶段(与bind不同)你已经知道了要连接的本地ip,远程ip,远程端口,你需要选择短暂的端口。假设所有端口都在使用中。现在,您可以选择连接到不同远程端点的端口(与当前套接字尝试连接的端口不同),而不是选择一些随机端口进行重用(以后可能会在连接时失败)。< / p>

您可以在此MS support article

确认此示例
  

SO_REUSE_UNICASTPORT

     

对于要实现的连接方案,socket选项必须是   在绑定套接字之前设置。此选项指示系统   推迟端口分配,直到4元组的连接时间   连接的(四倍)是已知的。

请注意,SO_REUSE_UNICASTPORT仅对显式绑定有效(如问题引用中所述,但仍值得重复)。如果你隐式绑定(例如当你只是Connect()没有绑定时) - 默认情况下已经设置了此选项(当然支持)。

关于这对您的特定应用程序产生的影响。首先,应该清楚的是,如果您的应用程序向同一个远程端点(例如,对同一个http服务器)发出大量请求 - 此选项将不起作用。如果您向不同的端点发出大量请求 - 它应该有助于防止端口耗尽。 ServicePointManager.ReusePort本身的静止效果取决于我猜测HttpClient内部如何使用套接字。如果它只是Connect()没有显式绑定 - 默认情况下应该启用此选项(在支持的系统上),因此将ServicePointManager.ReusePort设置为true将不会产生额外的效果,否则会产生额外的效果。由于您不了解(并且不应该依赖)其内部实施,因此在您的特定情况下值得启用ServicePointManager.ReusePort

您还可以通过将临时端口的范围(使用netsh int ipv4 set dynamicport tcp之类的命令)限制为少量并使用此选项来执行此选项的测试。