TCP/IP 参数调优以实现快速客户端连接





2.00/5 (2投票s)
打开和关闭大量客户端 TCP/IP 套接字的应用程序存在耗尽可用套接字端口的风险。引发的异常可能具有误导性,因为它们通常与服务器套接字冲突相关,而不是出站客户端连接。
最新源代码
除了上面的链接,您还可以从下面的链接下载最新代码,以确保您拥有最新版本
引言
我最近分享了我关于解决一个奇怪的 打开客户端套接字连接时端口冲突 的经历[^]。打开和关闭大量客户端 TCP/IP 套接字的应用程序存在耗尽可用套接字端口的风险。这可能发生在负载和性能测试场景中,使用像 iTKO 的 LISA Test[^] 这样的工具,或者在一个生产环境中,如果一个活跃的应用程序只需要快速打开和关闭大量出站连接,也可能发生。
在 .NET 平台上,引发的异常是
"System.Net.Sockets.SocketException: Only one usage of each socket address
(protocol/network address/port) is normally permitted <host>:<port>".
在 Java 中,异常是
"java.net.BindException: Address already in use: connect".
这两个异常都具有误导性,因为它们通常与服务器套接字冲突相关,而不是出站客户端套接字连接。然而,对 TCP 状态机的更深入理解可以揭示这种行为——以及解决方案。
常见的端口冲突异常
每当 TCP/IP 遇到端口冲突时,您都可以预期会根据您的环境抛出以下两个异常之一
在 C# 环境中,您会看到此异常
System.Net.Sockets.SocketException: Only one usage of each socket address
(protocol/network address/port) is normally permitted <host>:<port>
在 Java 中,您会看到这个
java.net.BindException: Address already in use: connect
at java.net.PlainSocketImpl.socketConnect(Native Method)
at java.net.PlainSocketImpl.doConnect(PlainSocketImpl.java:333)
at java.net.PlainSocketImpl.connectToAddress(PlainSocketImpl.java:195)
at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:182)
at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:366)
at java.net.Socket.connect(Socket.java:519)
at java.net.Socket.connect(Socket.java:469)
如果您在服务器套接字尝试监听传入连接时看到这些异常被抛出,原因很明显:您尝试监听的端口已在使用中。例如,如果您启动一个 Web 服务器,但另一个 Web 服务器已在运行,则会抛出一个异常,因为端口 80(或 8080)已代表另一个线程或应用程序在监听。
然而,在设置客户端连接时抛出这些异常,通常会令人困惑。客户端 TCP 连接在本地端始终分配一个操作系统选择的端口,那么为什么操作系统会选择一个正在使用的端口呢?事实是,该异常表示客户端**没有**可用的本地端口号。操作系统对错误的这种误报是造成一半困惑的原因。
调整本地客户端端口范围
问题是双重的。首先,Linux 和 Windows 只为客户端套接字提供有限数量的端口——默认范围是 1024 到 5000。因此,您一次可能只有 3,976 个活动客户端连接。对于大多数系统来说,这已经足够了。然而,在需要大量出站连接的特定系统上,这个限制可能会被耗尽。
然而,这个范围是可以调整的。在 Windows 上,可以使用此注册表项上的 `MaxUserPort` `DWORD` 值来调整客户端端口分配的上限
HKLM\System\CurrentControlSet\Services\Tcpip\Parameters
当然,比起使用繁琐的 `regedit`,您还可以下载本文中的 **IPtuner** 来快速设置 `MaxUserPort`,如下所示

在 Linux 上,可以使用以下参数设置下限和上限
net.ipv4.ip_local_port_range = 32768 65534
在 Linux 中设置此参数的方式取决于您使用的 Linux 发行版和版本——并且在更改后您需要重新启动网络服务。
调整 TCP TIME_WAIT 超时值
这些异常的第二个原因是 TCP 状态模型以及套接字关闭的方式。即使在套接字被正式“关闭”之后,它仍然会以 `TIME_WAIT` 状态停留一段时间,作为处理杂散数据包的安全机制。所有操作系统的默认等待时间通常长得离谱(Windows 上为 240 秒)。因此,即使一个应用程序不需要大量并发连接,在高负载情况下仍然可能耗尽可用端口。如果甚至一个连接反复快速地打开和关闭,您很快就会发现所有可用的本地套接字都处于 `TIME_WAIT` 状态,而没有可用的新客户端端口。
然而,`TIME_WAIT` 状态的持续时间也是可调的。
在 Windows 上,使用相同的注册表项,可以使用 `TCPTimedWaitDelay` 值将 `TIME_WAIT` 持续时间从 30 秒调整到 300 秒。同样,与其在 `regedit` 中摸索,不如下载本文中的 **IPtuner** 来快速设置 `TCPTimedWaitDelay`,如下所示

在 Linux 上,等待延迟是使用以下参数配置的
net.ipv4.tcp_fin_timeout = 30
通过减少 TCP 等待延迟,关闭的套接字在 `TIME_WAIT` 状态停留的时间更短,并能更快地返回到可用客户端端口池。但是,为了避免通信问题,请勿将此值降低到 30 秒以下。
需要管理员权限
在 Linux 和 Windows 上(即使使用 **IPtuner** 或 `regedit`),您都需要管理员权限才能更改这些参数。但是,任何人都可以查看这些设置,至少可以验证它们是否合理。
历史
- 2010年2月22日:初始发布