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

All*_*nek 9 .net c# sockets .net-4.6 windows-server-2016

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

文档ServicePointManager.ReusePort非常小:

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

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

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

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

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

Evk*_*Evk 10

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

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);
    // ...
}
Run Code Online (Sandbox Code Playgroud)

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

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

socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
Run Code Online (Sandbox Code Playgroud)

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

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

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

例如,您可以在此MS支持文章中确认这一点:

SO_REUSE_UNICASTPORT

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

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

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

您还可以通过将临时端口(使用命令等netsh int ipv4 set dynamicport tcp)的范围限制为一些少量来使用此选项在\ off上执行测试,并查看它是如何进行的.