ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

tcping批量端口巡检脚本实战:从手工检测到自动定时任务

tcping批量端口巡检脚本实战:从手工检测到自动定时任务 1. 为什么我最终选了 tcping 做批量巡检1.1 先聊聊端口检测这件事有多烦做运维这些年最烦的事情之一就是排查端口通不通。比如刚部署完一套服务客户说连不上第一反应肯定是去看端口有没有监听、防火墙有没有放行、网络通不通。这时候如果只是三两台机器手动敲几条命令还能接受但一旦机器数量上到十台、二十台或者要定期巡检一批线上服务还是用 telnet 一个端口一个端口去试效率就非常低。telnet 的痛点其实很明显默认 23 端口连其他端口要手动指定检测一个端口要等半天超时一旦目标机器没有安装 telnet 服务端甚至会出现端口明明开着却连不上的假象。PowerShell 自带的Test-NetConnection也能做端口检测但输出信息特别冗长循环调用时性能也不算好而且早期 Windows 版本用起来还有各种兼容问题。所以我在实际项目里还是回归到了 tcping 这个轻量级工具。tcping 从名字就能看出来它是针对 TCP 端口的 ping。它不依赖 ICMP 协议而是直接发起一次 TCP 连接尝试能准确告诉你指定端口是否可达、握手是否成功、耗时多少。相比原版 ping 只能探测主机存活tcping 解决的是端口是否真的在听这个问题。1.2 批量巡检脚本到底解决了什么问题单纯有 tcping 还不够因为运维场景下的需求往往是这样的一台机器上同时要检查 Web 端口、数据库端口、缓存端口、内部 RPC 端口或者一组机器上要检查同一个业务端口。手动复制粘贴命令输错一个 IP 或端口号排查起来更费劲。所以我写这套脚本的初衷是要实现三个目标把待检测的 IP 和端口清单集中到一个配置里想巡检哪批服务改配置就行不用改脚本本身。一键执行后能自动循环检测全部节点并把每个节点的状态、耗时、失败原因汇总成明确的结果。结果要留痕方便事后追溯甚至能接到计划任务里做成每天自动巡检。这套方案落地之后我日常排查问题的时间至少省了一半。以前要开好几个终端窗口同时 telnet现在跑一下脚本看一眼输出哪里通哪里不通一目了然。下面我把完整思路、脚本实现和排坑经验都整理出来给同样被端口巡检困扰的朋友做一个参考。2. tcping 工具准备与参数速查2.1 获取 tcping 的两种方式tcping 本身是一个绿色免安装的小工具网上常见的版本是 Eliot Lash 等人维护的开源项目也有各种改版。我习惯在 Windows 上用单文件 exe 版本下载下来直接拷到脚本目录或者系统 PATH 路径下就能用。具体做法有两种把tcping.exe放到某个固定目录比如C:\Tools\然后把该目录加进系统环境变量 PATH。这样在任何路径下执行tcping都能直接调用。如果你的巡检脚本是放在固定目录里的不想动系统环境变量可以直接把 tcping.exe 和脚本放同目录脚本里用%~dp0tcping.exe这样的写法去定位工具路径这样整个巡检包拷贝到任何机器上都能直接跑。我自己的习惯是把 tcping.exe 放进包内固定目录因为大部分生产机器的权限卡得比较严不一定允许改系统环境变量脚本目录携带工具的方式更省事也不容易污染系统。2.2 高频参数与真实使用含义tcping 的参数在不同版本里略有差异但最常用的几个基本是通用的。我列一个我平时使用频率最高的参数表参数含义说明-t持续 ping相当于 ping 的-t无限循环检测手动 CtrlC 停止-n 次数指定检测次数默认通常是 4 次批量巡检建议设 1 次或 2 次提升速度-w 超时等待响应的超时时间单位是秒默认较保守内网建议 1-2 秒跨公网可放宽-p 端口目标端口如果目标地址用host:port格式写了就不用加这个参数--interval每次探测间隔防止高频探测被防火墙或安全设备拦截-h帮助查看当前版本支持的全部参数这里需要特别说下超时时间的设置。很多人刚开始用 tcping发现端口明明通着但脚本里显示超时失败十有八九就是-w设置得太小。内网环境一般 1 秒足够跨公网或者目标机器负载较高时建议放宽到 3 秒甚至 5 秒。不过超时设太长也有副作用批量巡检时会拉长整体耗时因为不可达的端口要等满超时时间才会返回结果。所以我一般是先用-w 1做快速初筛对失败项再用-w 5复测一次避免误判。2.3 先手动验证单端口检测在写批量脚本之前我建议你先手动执行一条命令确认 tcping 在这个环境上能正常工作。比如检测本机的 135 端口tcping -n 1 -w 2 127.0.0.1 135如果返回类似Port is open的信息说明工具没问题。如果你下载的版本要求端口用-p参数指定也可以写成tcping -n 1 -w 2 -p 135 127.0.0.1这一步很重要因为很多环境存在杀毒软件拦截、DLL 缺失、或者下载的是 Linux 版本误拷到 Windows 上的情况提前验证能省掉后面一大串排错时间。3. 批量巡检脚本的完整实现3.1 脚本设计思路配置与逻辑分离整套脚本我最看重的一点就是配置和逻辑分离。巡检的目标清单不能写死在代码里否则每次增删一个端口都得改主脚本容易改出问题。我把所有待检节点放在一个独立的targets.txt文件里每行一个节点格式是IP 端口 备注脚本读取这个文件逐行执行检测最后汇总输出。这样带来的好处很明显业务侧的人只需要维护 targets.txt不用理解脚本逻辑脚本本身变成了一套通用执行引擎换一批机器、换一组端口改配置就行完全不用动脚本。3.2 完整批处理脚本内容下面是我在实际项目中用的一套批处理脚本适合 Windows 7 及以上系统不需要额外安装运行库。我把脚本文件命名为tcping_check.bat和tcping.exe、targets.txt放在同一目录下。echo off setlocal enabledelayedexpansion chcp 65001 nul set TCPING%~dp0tcping.exe set TARGETS%~dp0targets.txt set LOG%~dp0check_result_%date:~0,4%%date:~5,2%%date:~8,2%_%time:~0,2%%time:~3,2%%time:~6,2%.log set PASS_COUNT0 set FAIL_COUNT0 if not exist %TARGETS% ( echo [ERROR] targets.txt not found. exit /b 1 ) if not exist %TCPING% ( echo [ERROR] tcping.exe not found. exit /b 1 ) echo echo Batch TCP Port Checker echo Time: %date% %time% echo echo. for /f usebackq tokens1,2,* delims %%a in (%TARGETS%) do ( set IP%%a set PORT%%b set REMARK%%c if not !IP! ( if not !PORT! ( %TCPING% -n 1 -w 2 !IP! !PORT! nul 21 if !errorlevel! equ 0 ( set /a PASS_COUNT1 echo [OK] !IP!:!PORT! !REMARK! ) else ( set /a FAIL_COUNT1 echo [FAIL] !IP!:!PORT! !REMARK! ) ) ) ) echo. echo echo Summary: PASS%PASS_COUNT% FAIL%FAIL_COUNT% echo if %FAIL_COUNT% gtr 0 ( exit /b 2 ) else ( exit /b 0 )这个脚本的核心逻辑其实很简单用for /f逐行读取 targets.txt按空格切分成 IP、端口、备注三列然后调用tcping -n 1 -w 2做单次检测通过errorlevel判断结果累加成功和失败计数最后打印汇总信息。3.3 关键逻辑逐个拆解这个脚本里有两个地方容易踩坑我单独说一下。第一个是chcp 65001 nul。这行命令把控制台代码页切换成 UTF-8目的是让 targets.txt 里的中文备注能正常显示。如果文件里有中文备注但控制台代码页不对输出就可能乱码。不过要注意系统自带的老版本批处理对 UTF-8 编码的兼容性有时会出问题如果你的 targets.txt 是用记事本默认 ANSI 编码保存的chcp 65001反而可能让中文变成乱码这里需要根据你实际文件的编码方式来调整。第二个是errorlevel的判断。tcping 命令执行成功时返回 0失败时返回非 0这是脚本判断端口通断的依据。但在批处理里如果直接写if !errorlevel! equ 0需要确保延迟变量扩展已经开启也就是脚本开头的setlocal enabledelayedexpansion必须存在同时在 for 循环体内用!errorlevel!而不是%errorlevel%否则取到的是循环之前的值判断就会出错。还有就是退出码的设计。我把完全成功定义为 0有任何失败定义为 2这样后面接计划任务时可以根据退出码来触发不同的后续动作比如失败时给运维群发告警。3.4 配置文件的格式与示例targets.txt 的格式非常简单每一行对应一个检测节点三个字段之间用空格隔开第三个字段是备注可有可无。我贴一个实际使用的例子192.168.1.10 8080 Web服务-生产环境 192.168.1.11 3306 MySQL-订单库 192.168.1.12 6379 Redis-缓存 192.168.1.13 9200 Elasticsearch-日志检索 10.10.20.5 22 SSH-跳板机有几点要注意行内不要出现额外空格否则for /f切分时会把备注字段切碎。如果某行以#开头我通常不会让它参与检测但上面这个简单脚本没有过滤注释行的逻辑。如果你希望支持注释可以在for /f循环里加一个判断跳过以#开头的行。文件末尾不要留多余空行空行在for /f中会被跳过但为了整洁还是清理一下。3.5 进阶用 PowerShell 实现同样的功能如果你的环境里 PowerShell 执行策略允许或者你想要更灵活的报表输出我这里也提供一份 PowerShell 版本的脚本效果等价但可扩展性更强。$targets Get-Content -Path $PSScriptRoot\targets.txt $results () foreach ($line in $targets) { if ([string]::IsNullOrWhiteSpace($line) -or $line.StartsWith(#)) { continue } $parts $line -split \s $ip $parts[0] $port [int]$parts[1] $remark if ($parts.Count -ge 3) { $parts[2] } else { } $tcpClient New-Object System.Net.Sockets.TcpClient try { $asyncResult $tcpClient.BeginConnect($ip, $port, $null, $null) $success $asyncResult.AsyncWaitHandle.WaitOne(2000) if ($success -and $tcpClient.Connected) { $results [PSCustomObject]{ IP $ip Port $port Remark $remark Status OK } } else { $results [PSCustomObject]{ IP $ip Port $port Remark $remark Status FAIL } } } catch { $results [PSCustomObject]{ IP $ip Port $port Remark $remark Status FAIL } } finally { $tcpClient.Close() } } $results | Format-Table -AutoSizePowerShell 版本的优点是不需要额外下载 tcping.exe直接用 .NET 的 TcpClient 类发起连接跨机器部署更方便。缺点是对旧版 PowerShell 的兼容性需要注意另外某些安全策略会限制脚本执行权限需要先设置执行策略或者绕过签名检查。我个人是批处理和 PowerShell 两边都用内网工具机用批处理比较多管理机上有 PowerShell 3.0 以上环境的用 PS 版更顺手。4. 实战落地从手工巡检到自动定时任务4.1 第一次跑脚本你会看到什么把 tcping.exe、tcping_check.bat、targets.txt 放在同一目录下双击 bat 文件执行过程大致长这样 Batch TCP Port Checker Time: 2025/01/15 10:23:45 [OK] 192.168.1.10:8080 Web服务-生产环境 [OK] 192.168.1.11:3306 MySQL-订单库 [FAIL] 192.168.1.12:6379 Redis-缓存 [OK] 192.168.1.13:9200 Elasticsearch-日志检索 [OK] 10.10.20.5:22 SSH-跳板机 Summary: PASS4 FAIL1 看到 FAIL 的那一行就说明对应节点的端口不通或者超时。注意这只是第一步判断不能直接断定服务挂了还要进一步确认是网络不通、防火墙拦截、还是进程没起来。这个后面在排障部分展开说。不过这里有个体验小问题直接双击 bat 运行时如果中途报错或者执行结束窗口可能一闪而过根本看不到输出。遇到这种情况我的做法是打开 CMD 窗口手动切到脚本目录再执行cd /d C:\Tools\port_check tcping_check.bat这样窗口会保留住输出内容方便排查。如果确实需要双击运行后暂停可以在脚本末尾加一行pause但我个人不建议放到正式脚本里因为接入计划任务时会阻塞执行。4.2 用计划任务实现每日自动巡检手工跑脚本只是第一步真正的价值在于无人值守的自动巡检。Windows 自带的计划任务程序也就是 taskschd.msc或者直接用 schtasks 命令就能实现每天定时执行脚本并把结果写入日志。我用得最多的配置方式是命令行直接创建schtasks /create /tn PortCheckDaily /tr C:\Tools\port_check\tcping_check.bat /sc daily /st 09:00 /f这条命令创建了一个名为 PortCheckDaily 的计划任务每天上午 9 点执行巡检脚本。参数说明/sc daily表示按天触发/st 09:00指定开始时间/f表示如果任务已存在则强制覆盖。这里有个坑必须提醒如果脚本是通过网络路径或者是需要读写目标文件的计划任务默认以登录用户身份运行如果该用户没有相应权限脚本可能执行失败。稳妥的做法是在计划任务属性里勾选使用最高权限运行或者指定一个有足够权限的专用账号。另外脚本输出的日志文件名里如果带了%time%之类的动态时间字段每次运行会生成独立日志文件不会覆盖。这在追溯历史记录时特别有用可以快速定位某一天某个节点的状态。日志文件因为命名中包含了小时、分钟、秒同一天内多次运行也能区分开。4.3 结果留痕与预警思路日志文件是自动巡检的核心输出我习惯把每天的结果日志统一集中到一个logs子目录里文件名带日期。这样一个月下来哪天哪台机器哪次巡检失败一查便知。但光有日志还不够因为失败发现得再及时如果没人看日志巡检就失去了意义。最简单的预警方式是利用脚本的退出码。前面批处理脚本里我已经做了设计有任何失败节点就exit /b 2全通过则exit /b 0。计划任务里可以用这个退出码去触发后续动作比如把退出码作为条件在计划任务操作里追加一条发送邮件或调用 Webhook 的指令。自己写一个外层脚本调用 tcping_check.bat 后判断%errorlevel%如果等于 2 就把日志内容发到群机器人或者邮件列表。我对接告警系统时通常是在外层加一段 PowerShell 调 REST API 的逻辑把失败节点列表 POST 到内部监控平台。不过这块内容跟具体监控体系绑定太紧这里就不展开贴完整代码了思路供大家参考。5. 常见问题与排查技巧实录5.1 脚本提示 tcping 不是内部或外部命令这是最常见的问题原因一般有三个tcping.exe 和 bat 脚本不在同一目录且没有配置 PATH 环境变量。杀毒软件把 tcping.exe 当作未知程序清除了。下载的 tcping 版本本身损坏或者不是 Windows 版本。解决办法也很直接先确认文件是否还在其次确认脚本里TCPING变量指向的路径是否正确。我见过不少朋友把 tcping 下载到了下载目录运行脚本时却在别的地方自然就找不到了。如果文件确实被杀毒软件删了建议把 tcping.exe 所在目录加入杀毒软件的信任区或者换成 PowerShell 版本用 .NET 内置类来做端口检测彻底绕开对第三方 exe 的依赖。5.2 端口明明开着脚本却报失败这个问题的排查方向比较多我按可能性从高到低排序防火墙拦截。服务器上 Windows 防火墙或者安全组规则只放行了特定来源 IP 的访问而执行脚本的机器 IP 不在白名单里。这时 tcping 发出的 SYN 包会被直接丢弃表现就是超时失败。超时时间设置过短。跨机房、跨公网链路时RTT 可能达到几百毫秒甚至一秒以上-w 2虽然多数场景够用但如果链路质量差或者目标服务器负载高握手可能超过 2 秒。目标服务只监听了 IPv6 地址而脚本传的是 IPv4 地址导致连接被拒。端口处于半开状态服务进程还没完全启动端口还没开始 accept 连接。针对这些情况我的排查习惯是先用telnet IP PORT手动测一遍再用 tcping 加大超时重测最后登录目标机器用netstat -ano | findstr PORT看一下端口是否真的在监听。如果监听正常但外部连不上重点查防火墙和路由。5.3 批量巡检整体跑得很慢这个问题的根源几乎都出在不可达节点上。一个不可达端口的检测要等满超时时间才会返回失败。如果清单里有 10 个不可达节点每个超时 5 秒光这些失败节点就要耗 50 秒。解决办法有几种把超时时间调小比如内网设置-w 1。对失败节点进行二次复测而不是一次超时就直接判死避免网络抖动造成误报。把 targets.txt 里的节点按业务重要性分组核心节点单独巡检非核心节点合并到低峰批次执行。如果需要更极致的并行能力改用 PowerShell 脚本用System.Net.Sockets.TcpClient配合异步连接同时对多个节点发起握手整体耗时会大幅下降。我在处理超过 50 个节点的巡检场景时基本不再用批处理逐行串行跑而是改用 PowerShell 的并行任务或者直接在脚本里开多个异步连接几十个节点几秒钟就能出结果。5.4 计划任务里运行脚本一闪而过不知道结果计划任务调度 bat 时默认不显示窗口或者窗口一闪而过很容易让人误以为任务没执行。要排查任务是否真正跑过可以看两方面一是任务计划程序里上次运行时间和上次结果字段二是我前面提到的日志文件是否生成了新的内容。如果任务显示运行成功但日志文件没有更新大概率是脚本里的相对路径出了问题。计划任务的起始于目录不一定是脚本所在目录脚本里如果用了相对路径去定位 targets.txt就会找不到文件。这也是为什么我在脚本里统一用%~dp0来定位脚本自身目录的原因不管任务计划从哪里启动%~dp0永远指向脚本所在路径。另外如果 bat 脚本在计划任务里运行失败但手动执行又正常优先检查计划任务的使用最高权限运行是否勾选以及运行账号对脚本目录是否有读写权限。很多企业机器的 C 盘 Program Files 目录权限管控很严脚本放在里面普通账号可能没有写日志文件的权限。5.5 如何判断端口检测结果是否可靠最后说一个经验问题tcping 显示 OK能说明端口通但通不一定等于服务可用。TCP 三次握手成功只能证明服务器的协议栈接受了连接请求listen 状态的端口已经建立连接但应用层的处理逻辑可能已经异常比如数据库连接池满了、HTTP 服务返回 500、或者磁盘满了导致服务假死。所以端口巡检脚本更适合作为网络层和传输层的健康检查用它来确认网络路径是否通畅、端口是否监听这是最可靠的。如果你要判断服务真正可用建议在 tcping 之上再加一层应用层探活比如 HTTP 探测、数据库查询、或者 gRPC 健康检查接口。我在生产环境里的习惯是一套脚本做端口巡检另一套脚本做应用层探活两者配合使用才能构建出完整的服务可用性监控体系。这里也顺带讲明白一个基础概念TCP 建连为什么能这么直接地反映端口状态。TCP 三次握手的过程本质上就是客户端发 SYN服务端回 SYNACK客户端再回 ACK。tcping 做的事情就是完整走一遍这样的握手只要服务端的端口处于 LISTEN 状态握手就能成功。所以 tcping 的结果比 ICMP ping 更贴近这个端口到底能不能连上的真实情况。日常巡检中ICMP ping 显示通但端口连不上是非常典型的场景而 tcping 恰好补上了这个盲区。6. 我踩过几次坑之后的最终建议整个脚本从最早的单条命令演化到今天的配置化批量巡检中间踩过的坑确实不少有几个经验想重点分享。第一不要把 targets.txt 编码搞错。Windows 记事本默认的 ANSI 编码在不同语言版本的系统下解释方式不一样如果脚本里用chcp 65001转成 UTF-8 但文件本身是 ANSI中文备注照样乱码。我现在统一把 targets.txt 保存为 UTF-8 with BOM 或者干脆不用中文备注一劳永逸。第二超时时间务必根据实际链路调整。我自己踩过最狠的一次是把巡检脚本部署到跨公网环境超时沿用内网的 1 秒结果三分之一的节点全部误报失败排查了好久才意识到是链路延迟问题。现在我的配置原则是同机房内网用 1 秒跨地域内网用 2 到 3 秒跨公网用 5 秒宁可在总耗时长一点也不能误报。第三任何巡检工具都只能证明端口可连不能证明业务可用。这个我刚才也强调过但还是要再说一次。曾经有一次客户反馈服务异常端口巡检却全部正常后来发现是应用层线程池耗尽导致连接建立了但请求处理不了直到我们加上应用层探活才暴露出来。端口巡检是兜底不能当唯一的健康检查手段。如果后续想继续完善这套巡检体系可以考虑的方向有把巡检结果解析成 Prometheus 指标接入监控系统、用 Webhook 把失败节点推送到企业微信群、或者将 targets.txt 迁移到数据库或配置中心让脚本从接口动态拉取待检清单这样新增节点时连配置文件都不用改了。我目前已经把这套脚本整合到了内部运维平台里配合定时任务和告警通知几百个端口的日常巡检基本做到了无人值守。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进