c# - ServicePointManager.ReusePort 和 SO_REUSE_UNICASTPORT 如何缓解临时端口耗尽?

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

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

ServicePointManager.ReusePort 的文档非常小:

Setting this property value to true causes all outbound TCP connections from HttpWebRequest to use the native socket option SO_REUSE_UNICASTPORT on the socket. This causes the underlying outgoing ports to be shared. This is useful for scenarios where a large number of outgoing connections are made in a short time, and the app risks running out of ports.

查看 SO_REUSE_UNICASTPORT 的文档不提供任何额外的见解:

When set, allow ephemeral port reuse for Winsock API connection functions which require an explicit bind, such as ConnectEx. Note that connection functions with an implicit bind (such as connect without an explicit bind) have this option set by default. Use this option instead of SO_PORT_SCALABILITY on platforms where both are available.

我在网络上找不到任何关于这种“临时端口重用”究竟是如何实现的,它在技术层面上究竟如何运作,以及它如何降低临时端口耗尽风险的解释。我可以期待多少改进?使用此功能,我如何计算我的应用程序的新限制?启用此功能有什么缺点吗?

这一切都笼罩在神秘之中,如果有人能解释这种新机制及其影响,我会很高兴。

最佳答案

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);
    // ...
}

因此您将套接字绑定(bind)到端口为 45455 的特定本地端点。如果您现在尝试同时向“site1.com”和“site2.com”发出请求,您将得到“一个已在使用的地址”异常.

但是,如果您在绑定(bind)之前添加 ReuseAddress 选项(请注意,这不是您的问题所涉及的选项):

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

您将能够将套接字绑定(bind)到相同的本地(ip、端口),并且您将在 netstat 中看到两个已建立的连接。

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

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

例如,您可以在此 MS support article 确认这一点:

SO_REUSE_UNICASTPORT

For a connection scenario to be implemented, the socket option must be set before binding a socket. This option instructs the system to postpone port allocation until connection time when the 4-tuple (quadruple) for the connection is known.

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

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

您还可以通过将临时端口的范围(使用 netsh int ipv4 set dynamicport tcp 之类的命令)的范围限制在一些小的范围内来使用此选项打开/关闭来执行测试,并查看它是如何进行的。

关于c# - ServicePointManager.ReusePort 和 SO_REUSE_UNICASTPORT 如何缓解临时端口耗尽?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44548444/

相关文章:

c# - 在 C# 中克隆具有 DataTable 和其他对象作为属性的复杂对象

c# - 通用可绑定(bind)接口(interface)

c# - 如何从 .NET Core 2.1/2.2 创建 Windows 服务

C# 转换枚举 InvalidCastException 错误

c# - 如何在 .NET 中以编程方式启动 Amazon EC2 实例

c# - 将存储过程转换为 LINQ

c# - 为什么 C# 的 LastIndexOf 会这样?

java - HTTP/HTTPS 客户端,HTTPS 请求上的 "connection reset"

networking - Twisted Python、Ruby 的 Eventmachine、Java 的 NIO 等的 Lua 等价物是什么?

python套接字错误10060