问题背景

在生产环境中,我们的 Windows Server 2016 服务器出现了大量的 TIME_WAIT 连接,导致可用端口不足,新的连接请求被拒绝。通过 netstat -ano查看,发现 TIME_WAIT 状态的连接数非常多。

什么是 TIME_WAIT

TIME_WAIT 是 TCP 连接断开过程中的一个正常状态。当主动关闭连接的一方发送最后一个 ACK 后,会进入 TIME_WAIT 状态,默认持续 2MSL(Maximum Segment Lifetime)时间,以确保:

  1. 远端正确接收到最后的 ACK
  2. 网络中延迟的数据包不会影响新连接

正常情况下,TIME_WAIT 会在一段时间后自动释放。但在高并发场景下,如果连接建立和关闭频繁,就会产生大量 TIME_WAIT 连接。

监控脚本编写

为了实时监控 TIME_WAIT 连接的情况,我编写了一个 PowerShell 脚本来持续跟踪和记录。

完整监控脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# ==============================
# Monitor-TimeWait-Loop.ps1
# ==============================
param(
[int]$IntervalSeconds = 60, # 采样间隔,单位:秒
[int]$Threshold = 100 # TIME_WAIT 连接数阈值
)

# 日志目录
$baseLogDir = "C:\Temp\TimeWaitMonitor"
if (!(Test-Path $baseLogDir)) { New-Item -Path $baseLogDir -ItemType Directory | Out-Null }

# 定义主循环
while ($true) {
$date = (Get-Date).ToString("yyyy-MM-dd")
$time = (Get-Date).ToString("HH:mm:ss")
$logFile = Join-Path $baseLogDir "TimeWaitMonitor_$date.log"
$csvFile = Join-Path $baseLogDir "TimeWaitTrend_$date.csv"

# 确保日志存在
if (!(Test-Path $csvFile)) {
"Time,Count" | Out-File -Encoding UTF8 $csvFile
}

# 获取 TIME_WAIT 全部行
$timeWaitLines = netstat -ano | findstr /c:"TIME_WAIT"
$timeWaitCount = ($timeWaitLines | Measure-Object).Count

# 写入趋势 CSV
"$time,$timeWaitCount" | Out-File -FilePath $csvFile -Append -Encoding UTF8

# 日志函数
function Write-Log {
param([string]$Message)
$msg = "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') - $Message"
Write-Host $msg
Add-Content $logFile $msg
}

Write-Log "TIME_WAIT connections: $timeWaitCount"

if ($timeWaitCount -gt $Threshold) {
Write-Log "-----------------------------"
Write-Log "TIME_WAIT 超过阈值 ($Threshold),详细占用如下:"

# 提取本地端口统计
$ports = $timeWaitLines | ForEach-Object {
($_ -split '\s+') | Where-Object { $_ -match "^\d+\.\d+\.\d+\.\d+:" } | ForEach-Object {
($_ -split ":")[-1]
}
}

$topPorts = $ports | Group-Object | Sort-Object Count -Descending | Select-Object -First 10
Write-Log "Top 10 本地端口 TIME_WAIT 占用:"
foreach ($p in $topPorts) {
Write-Log ("端口 {0,-8} : {1,5}" -f $p.Name, $p.Count)
}

# 提取远端 IP
$remoteIPs = $timeWaitLines | ForEach-Object {
($_ -split '\s+') | Where-Object { $_ -match "^\d+\.\d+\.\d+\.\d+:\d+$" } | Select-Object -Last 1
} | ForEach-Object {
($_ -split ":")[0]
}

$topIPs = $remoteIPs | Group-Object | Sort-Object Count -Descending | Select-Object -First 10
Write-Log ""
Write-Log "Top 10 远端 IP TIME_WAIT 占用:"
foreach ($ip in $topIPs) {
Write-Log ("IP {0,-16} : {1,5}" -f $ip.Name, $ip.Count)
}

# 提取 PID≠0 的连接
$pids = $timeWaitLines | ForEach-Object {
($_ -split '\s+')[-1]
} | Where-Object { $_ -ne "0" } | Group-Object | Sort-Object Count -Descending | Select-Object -First 10

if ($pids.Count -gt 0) {
Write-Log ""
Write-Log "Top 10 有效 PID TIME_WAIT 占用:"
foreach ($p in $pids) {
try { $procName = (Get-Process -Id $p.Name -ErrorAction SilentlyContinue).ProcessName }
catch { $procName = "N/A" }
Write-Log ("PID {0,-6} ({1,-15}) : {2,5}" -f $p.Name, $procName, $p.Count)
}
}
else {
Write-Log ""
Write-Log "PID 信息:均为系统层 TIME_WAIT (PID=0),表示已断开的套接字等待回收。"
}

Write-Log "-----------------------------`n"
}

# 等待下一个采样
Start-Sleep -Seconds $IntervalSeconds
}

脚本使用方法

1
2
3
4
5
6
7
8
# 基本使用(默认每60秒检查一次,阈值100)
.\Monitor-TimeWait-Loop.ps1

# 自定义参数(每30秒检查一次,阈值200)
.\Monitor-TimeWait-Loop.ps1 -IntervalSeconds 30 -Threshold 200

# 后台运行
Start-Process powershell -ArgumentList "-NoProfile -ExecutionPolicy Bypass -File .\Monitor-TimeWait-Loop.ps1" -WindowStyle Hidden

输出示例

正常情况:

1
2
2025-10-20 10:00:00 - TIME_WAIT connections: 85
2025-10-20 10:01:00 - TIME_WAIT connections: 92

超过阈值时:

CSV 趋势分析

脚本会自动生成 CSV 文件记录 TIME_WAIT 连接数的变化趋势,可以使用 Excel 打开并插入折线图进行可视化分析:

扩展:Windows TCP 参数优化

如果通过监控发现 TIME_WAIT 连接数持续过高,可以考虑调整 Windows Server 的 TCP 参数来扩大可用端口范围和缩短等待时间。

查看当前配置

1
2
3
4
5
# 查看最大用户端口数(默认可能不存在此键值)
reg query "HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters" /v MaxUserPort

# 查看TIME_WAIT等待时长(默认可能不存在此键值)
reg query "HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters" /v TcpTimedWaitDelay

Windows Server 2016 默认情况:

  • MaxUserPort:注册表中不存在此键值,系统默认约为 5000
  • TcpTimedWaitDelay:注册表中不存在此键值,系统默认为 240 秒(4 分钟)

优化配置

如果业务场景需要,可以通过以下命令扩展端口范围和缩短等待时间:

1
2
3
4
5
# 设置最大用户端口为 65534(几乎使用全部动态端口范围)
reg add "HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters" /v MaxUserPort /t REG_DWORD /d 65534 /f

# 设置TIME_WAIT等待时长为 30秒(默认240秒)
reg add "HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters" /v TcpTimedWaitDelay /t REG_DWORD /d 30 /f

参数说明:

参数 说明 默认值 建议值 范围
MaxUserPort 最大用户端口数 ~5000 65534 5000-65534
TcpTimedWaitDelay TIME_WAIT 等待时长(秒) 240 30-60 30-300

应用配置

修改注册表后,需要重启服务器才能生效:

1
2
3
4
5
6
# 查看是否需要重启
reg query "HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters" /v MaxUserPort
reg query "HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters" /v TcpTimedWaitDelay

# 重启服务器
Restart-Computer -Force

验证配置

重启后验证配置是否生效:

1
2
# CMD 查看
netsh int ipv4 show dynamicport tcp

注意事项

1. TIME_WAIT 的作用

  • 不要设置过小:TcpTimedWaitDelay 不建议低于 30 秒
  • 保证可靠性:过短可能导致网络可靠性问题

2. 端口范围

  • 不要超过 65535:这是 TCP 端口的最大值
  • 保留端口:1-1024 为系统保留端口

3. 应用场景

这些优化适用于:

  • 高并发 Web 服务器
  • API 网关
  • 反向代理服务器
  • 数据库连接池服务

不适用于:

  • 普通办公电脑
  • 低并发应用
  • 对网络延迟敏感的实时应用

4. 监控建议

  • 定期检查 TIME_WAIT 趋势
  • 设置合理的告警阈值
  • 保留历史数据用于分析

总结

通过 PowerShell 脚本持续监控 TIME_WAIT 连接,可以及时发现和分析端口使用情况。配合 CSV 趋势分析,能够清晰地了解系统的连接状态变化。

核心思路:

  1. 使用监控脚本实时跟踪 TIME_WAIT 连接数
  2. 分析连接来源(端口、IP、进程)定位问题
  3. 通过 CSV 数据生成趋势图辅助决策
  4. 必要时通过注册表参数优化扩展系统能力

监控建议:

  • 根据业务特点设置合理的阈值
  • 定期查看 CSV 趋势图发现异常模式
  • 保留历史日志用于问题回溯

网络问题排查的关键在于数据:有了持续的监控数据,才能准确判断问题的严重程度和变化趋势。