半夜被叫起来处理一台业务机,远程桌面连上去,桌面干净得让人心里发慌——没有堡垒机客户端,没有抓包工具,连浏览器都被策略锁了,屏幕上能用的只有一个黑底白字的 cmd 窗口。这种时候你要回答的问题往往特别朴素:这台机器上现在到底有哪些程序在跑?它们各自占了哪个端口?是监听在 127.0.0.1 还是 0.0.0.0?外面那台机器连不上来,到底是程序没起、端口没开,还是绑错了地址?仅靠 cmd 抓取本机运行程序的 IP 和端口,听起来像是句废话——谁会连个 netstat 都不会敲?但我见过太多人敲完netstat -ano之后,对着一屏密密麻麻的四五百行记录发呆,最后还是去翻任务管理器。这篇文章讲的不是命令本身,而是怎么用一台 Windows 机器自带的这几个小工具,在没有任何第三方软件的环境里,把“程序—PID—进程名—可执行路径—监听地址—端口—连接状态”这条链路完整地串起来,包括怎么写成批处理一键导出、怎么处理 UDP、怎么判断暴露面、以及那些只有在真机上踩过才会知道的坑。适合刚入行的运维、做交付实施的、被安全策略锁死环境的同学,也适合日常在自己电脑上排查“谁占了我的 8080”的人。
1. 为什么我坚持先用 cmd 把情况摸清楚
1.1 只有黑窗口时的真实处境
先讲清楚这类场景的共性:机器是别人的,权限是受限的,时间窗口是短的。你可能是远程登上去的,也可能是坐在客户机房里用一台临时机器操作,还有可能是通过某种只放行了命令行的通道连进去的。这三种情况叠加起来,结果就是你不能下载工具、不能装软件、不能改环境变量,甚至不能保证有 PowerShell 可用。我遇到过最极端的一次,是一台被策略精简过的系统,图形界面基本残废,开始菜单点不开,但 cmd 可以正常唤起,netstat、tasklist、wmic 三个命令全都在。
在这种约束下,你的目标不是“做一个漂亮的监控”,而是“用最短的时间确认三件事”:第一,目标端口有没有人在监听;第二,监听它的是哪个进程;第三,这个进程的可执行文件在哪、启动参数是什么。这三件事一旦确认,后面 90% 的问题就能定性。比如端口在监听但外部连不上,那基本就是绑定地址或防火墙的问题;端口压根没在监听,那就是服务没起来或者启动失败了,跟网络没半毛钱关系;端口在监听、程序也对,但连接数全是 CLOSE_WAIT,那就是程序自己没关连接,属于代码层面的问题。
之所以优先用 cmd 而不是别的,核心逻辑是“零依赖”。netstat 和 tasklist 属于系统自带组件,只要系统还是 Windows,它们就在。第三方工具再强,也需要落地、需要执行权限、可能被杀软拦、可能被策略禁止运行。你在别人的生产机上折腾这些,本身就是风险。而netstat -ano这一条命令,敲下去不到一秒就有结果,不写注册表、不加载驱动、不留痕迹,这是它在排障现场不可替代的原因。
1.2 三个命令的能力边界先划清楚
很多人把 netstat 当成万能钥匙,其实它只管“连接”这一层,它知道有个 PID 占了端口,但它不知道这个 PID 对应哪个程序。这个信息要靠 tasklist 补。而 tasklist 知道进程名和 PID,但默认不给你可执行文件的完整路径,也不给启动命令行,这个要靠 wmic 补。三者是一条链,缺一环就断。
| 命令 | 能拿到什么 | 拿不到什么 | 是否需要提权 |
|---|---|---|---|
| netstat -ano | 协议、本地地址、外部地址、状态、PID | 进程名、路径 | 否 |
| netstat -anob | 在上面基础上多一列可执行文件名 | 完整路径、命令行 | 是 |
| tasklist | PID、进程名、内存、会话、服务名(加 /svc) | 可执行文件路径 | 否 |
| wmic process | 进程名、完整路径、命令行、父进程 PID、启动时间 | 网络连接 | 否 |
| ipconfig /all | 本机所有网卡 IP、MAC、网关、DNS | 端口相关的一切 | 否 |
这张表建议直接记住。实际排障中你会发现,只用netstat -ano加tasklist /FI "PID eq xxx"就能解决 80% 的问题,剩下 20% 卡在“进程名是 java.exe 或者 svchost.exe,光知道名字没用”这种情况,那就必须上 wmic 拿命令行和路径。
1.3 为什么不第一时间上第三方工具
有个很现实的理由:第三方工具给出的信息,往往比系统命令多得多,多到你无法判断哪些是真实、哪些是推断。比如某些工具会给你一个“进程所属服务”的友好名称,看起来很贴心,但它可能是通过一堆启发式规则猜的,在虚拟化环境或者容器里经常猜错。而 netstat 给你的 PID 是内核直接报的,tasklist 给你的进程名是系统 API 返回的,这两个数据是“硬”的。你拿着硬数据去推,推错了知道自己错在哪;拿着软数据去推,推错了你连错在哪都不知道。
另一个理由是“可复现”。你用系统命令得出的结论,可以在工单里写成一条命令,让现场的人自己敲一遍验证;你用某个第三方工具得出的结论,对方没装这个工具,验证不了。做交付和运维时间长了会发现,能被一条命令复现的结论,才是有价值的结论。
2. netstat 输出逐列拆解,别在第一步看错
2.1 五个列里最容易看错的是哪一列
标准的netstat -ano输出长这样(数字是编的,结构是真实的):
活动连接 协议 本地地址 外部地址 状态 PID TCP 0.0.0.0:135 0.0.0.0:0 LISTENING 1024 TCP 0.0.0.0:8080 0.0.0.0:0 LISTENING 8864 TCP 127.0.0.1:5037 0.0.0.0:0 LISTENING 6120 TCP 192.168.1.20:52341 10.0.0.8:3306 ESTABLISHED 4412 TCP 192.168.1.20:52350 10.0.0.8:3306 TIME_WAIT 0 UDP 0.0.0.0:500 *:* 1188第一列是协议,第二列是本地地址(本机 IP 加本地端口),第三列是外部地址(对端 IP 加对端端口),第四列是状态,第五列是 PID。很多人第一眼看错的地方在于:第二列和第三列的端口含义完全不同。第二列的端口是“本机占用的端口”,第三列的端口是“对方服务的端口”。
举个最常见的误判:你在排查“到底是谁连了数据库”,敲了netstat -ano | findstr ":3306",结果出来一堆 PID 为 0 的行,你可能以为查错了。其实这些行是本机作为客户端去连别人 3306 的记录,本机那一侧的端口是 52341、52350 这种随机高位端口,3306 出现在第三列。真正“本机在监听 3306”的记录,3306 只会出现在第二列,而且状态必须是 LISTENING。这个区分如果不搞清楚,你会把一堆正常的出站连接当成异常。
2.2 参数组合的取舍:每个参数都有代价
netstat的参数不多,但组合起来差别很大,我给你按使用频率排一下。
-a显示所有连接和监听端口。不加这个参数,你只能看到已建立的连接,看不到监听状态的端口,那就等于没法排查“服务起没起”。所以这个参数基本是必加的。
-n用数字形式显示地址和端口。不加这个参数,netstat 会尝试做反向 DNS 解析,把 IP 解析成主机名。在一个 DNS 配得不太好的内网里,这一下能让你等十几秒甚至更久,命令像卡死了一样。我见过有人在客户现场敲netstat -a,等了半分钟以为机器挂了,其实就是在做解析。这个参数不是“可选优化”,是必加项,除非你确实需要看主机名。
-o显示 PID。这是整条链路的起点,没有它你后面什么都做不了。注意在比较老的系统上这个参数可能不支持,遇到就只能靠-b拿进程名反推,但那种系统现在基本见不到了。
-b显示创建连接的可执行程序名。听起来很美好,两个坑:一是必须管理员权限,普通用户敲下去会明确提示“请求的操作需要提升”;二是这个参数非常慢,因为它要为每个连接去查询进程信息,几百条连接的情况下能卡好几秒,而且有些系统进程还会拒绝查询。所以在时间紧张的时候,我的习惯是先用-ano拿 PID,再针对性地去查进程,而不是一上来就-anob。
-p tcp或者-p udp按协议过滤。这个在 UDP 端口排查时特别有用,因为默认输出里 TCP 和 UDP 混在一起,UDP 那一行又没有状态列,看着很乱。
提示:如果你只是想看某个端口,别直接
netstat -ano然后肉眼找。netstat -ano | findstr ":8080 "快得多,但注意后面那个空格——不加空格,8080 会同时匹配到 80800、18080 这些端口号,你会看到一堆无关记录。
2.3 本地地址那一段,决定了你的服务能不能被外面访问
这是我认为整个 netstat 输出里信息量最大、也最容易被忽略的部分。同样是监听 8080,绑定在什么地址上,结果天差地别。
0.0.0.0:8080表示监听在本机所有 IPv4 网卡上,包括内网 IP、外网 IP、以及可能的虚拟网卡。别的机器可以通过本机 IP 访问到。127.0.0.1:8080表示只监听回环地址,只有本机能访问,别的机器一律连不上,哪怕防火墙全开也没用。[::]:8080表示监听在所有 IPv6 地址上。在双栈系统上,这一条往往同时覆盖了 IPv4,具体行为取决于系统的双栈配置,不能一概而论。192.168.1.20:8080表示只监听在这一个具体网卡上,换了网段或者网卡 IP 变了就连不上。
这四种情况在排查“外部连不上”时是决定性的。我经历过不止一次,现场反馈说“服务肯定起来了,端口也在”,结果一看是127.0.0.1:8080,程序配置里默认绑了回环地址,外面当然连不上。改配置重新绑 0.0.0.0 就好了,跟防火墙、跟网络设备一点关系都没有。反过来也有另一种情况:程序绑了 0.0.0.0,你以为万事大吉,结果机器上装了安全软件,入站规则只放了特定端口,照样连不上,这时候要去看防火墙,而不是盯着 netstat。
所以我的建议是,把“本地地址”这一列当作判断暴露面的第一手依据。看到 0.0.0.0 或 [::],就说明这个端口对外可达(能不能通再看防火墙);看到 127.0.0.1,就说明这个端口是内部自用的,外部问题跟它无关。
先把端口听没听、听在哪儿看清楚,再去看是谁在听。
3. 把 PID 翻译成程序名,这一步最容易卡住
3.1 tasklist 的三种输出格式怎么选
拿到 PID 之后,最直接的动作是tasklist /FI "PID eq 8864"。默认输出是表格式的,五列:映像名称、PID、会话名、会话号、内存使用。这个格式人看很舒服,但如果你想用命令去解析,就不好办了。所以 tasklist 提供了/FO参数控制格式,三个可选值:TABLE(默认)、LIST、CSV。
/FO LIST是竖排的,一行一个字段,适合肉眼快速找到你要的那个值:
tasklist /FI "PID eq 8864" /FO LIST
/FO CSV /NH是横排加引号,/NH表示不输出表头,这个格式是给脚本用的,字段被引号包起来,后续处理起来不容易因为名字里有空格而出错。
还有一个很实用但我发现很多人不知道的参数:/SVC。加上它,tasklist 会在每个进程下面列出它承载的服务名。这个在排查 svchost.exe 的时候是救命的——一个系统上可能跑着几十个 svchost,光看名字完全不知道哪个是你要找的。加上/SVC之后,你能直接看到它背后挂的是哪个服务,再去服务管理器里对,链路就通了。
3.2 wmic 拿路径和命令行,这才是定位程序的关键
进程名叫 java.exe、python.exe、node.exe 的时候,tasklist 给的信息等于零。一台机器上可能有三个 java 进程,跑着三个不同的应用,你到底要找哪一个?这时候必须看可执行文件的完整路径和启动命令行。
wmic process where "processid=8864" get name,executablepath,commandline /format:list
这条命令会给你三样东西:进程名、可执行文件的绝对路径、完整的启动参数。启动参数里往往藏着答案——配置文件路径、监听端口、绑定的 IP、启动的 jar 包名字,全在里面。我有一次就是靠 commandline 里的--server.port=8081直接定位到问题:现场一直以为程序监听 8080,实际配置里写的是 8081,前面所有排查都是在错的端口上打转。
wmic还有一个用法是反查服务:wmic service where processid=8864 get name,displayname,startmode。进程和服务是两回事,一个服务可以拉起多个进程,一个进程也可能承载多个服务。用这条命令能从 PID 直接倒推到服务名,然后你就可以用sc qc 服务名看它的启动类型和可执行路径,判断它是不是应该开机自启、是不是配置错了。
注意:wmic 在新版本系统上已经被标记为弃用,部分精简安装的环境里可能直接没有这个命令。如果敲下去提示“不是内部或外部命令”,别慌,用替代方案:
powershell -Command "Get-CimInstance Win32_Process -Filter 'ProcessId=8864' | Format-List Name,ExecutablePath,CommandLine"。在 cmd 里直接调用 powershell 执行一条命令,算是 cmd 环境下最接近“零依赖”的退路了。
3.3 用 for 循环把两步合成一步
每次都先 netstat 再 tasklist,敲两遍很烦,尤其是要查好几个端口的时候。cmd 的for /f可以把上一条命令的输出按列切开,直接喂给下一条命令。
在命令行里直接敲(注意是单个百分号):
for /f "tokens=5" %a in ('netstat -ano ^| findstr ":8080 " ^| findstr "LISTENING"') do @tasklist /FI "PID eq %a" /FO LISTtokens=5取第五列,也就是 PID。管道符号在 for 的命令部分里必须写成^|转义,这是 cmd 的语法要求,漏了会报错。@是为了不把命令本身回显出来。
如果你把这段写进 .bat 文件,百分号要写成两个:%%a。这是 cmd 的一个老规矩,命令行里单写,批处理里双写,我见过太多人复制粘贴之后发现不生效,就是因为这个。
3.4 写成一个可复用的一键清单脚本
单次排查用上面的命令就够了,但如果你要在一台机器上摸清“所有监听端口和对应程序”,手工一个个查太慢。我一般会写一个十几行的批处理,跑完直接出一个 csv,拷走慢慢看。
@echo off chcp 936 >nul set OUT=%~dp0port_list.csv echo 协议,本地地址,状态,PID,进程名,可执行路径 > "%OUT%" for /f "tokens=1,2,4,5" %%a in ('netstat -ano ^| findstr /R /C:"LISTENING"') do ( set PID=%%d set PNAME= set PPATH= for /f "tokens=1,2 delims=," %%x in ('tasklist /FI "PID eq %%d" /FO CSV /NH 2^>nul') do ( set PNAME=%%~x ) for /f "tokens=2 delims==" %%p in ('wmic process where "processid=%%d" get executablepath /value 2^>nul ^| findstr "="') do set PPATH=%%p echo %%a,%%b,%%c,%%d,!PNAME!,!PPATH! >> "%OUT%" ) echo 完成,结果在 %OUT% pause这段脚本有几个地方值得说明,也是我调试时踩过的。
第一,chcp 936是为了保证中文进程名不乱码。如果你的系统是英文的,改成 437 或者干脆删掉这一行。如果要输出 UTF-8,用chcp 65001,但要注意有些中文字体在 UTF-8 代码页下会显示成方块,看着像乱码其实是字体问题。
第二,延迟变量展开。在 for 循环里给变量赋值又立刻读取,必须用!VAR!而不是%VAR%,并且在脚本开头加上setlocal enabledelayedexpansion。不加的话,你拿到的永远是循环开始前的空值,这个坑我栽过不止一次。
第三,2^>nul是为了把错误输出吞掉。当一个 PID 已经退出、或者权限不够查不到路径时,wmic 会往标准错误里写一堆提示,不屏蔽的话会把 csv 搞得乱七八糟。
第四,tasklist的 CSV 输出里,如果进程名带逗号(极少但可能),按delims=,切分就会错位。用%%~x取变量时带上波浪号能自动去掉引号,已经能挡住大部分问题,剩下的异常行人工看一眼就行,不必为了追求完美把脚本写得太复杂。
跑完之后你会得到一个 csv,用表格软件打开,按端口排序,一屏就能看清这台机器到底对外开了哪些口子。这个文件本身也是一个很好的交付物,比截图强得多。
4. 反过来查:知道程序,找它占了哪些端口
4.1 先拿 PID,再过滤 netstat
前面的思路是“端口到程序”,实际工作中还有一半的情况是反过来的:现场告诉你“某个程序好像有问题”,你要确认它到底占了哪些端口、连了哪些地址。这时候顺序要倒过来,先tasklist拿到 PID,再netstat -ano | findstr "PID"。
但这里有个细节要注意:直接findstr "8864"是不安全的,因为 8864 这个数字可能出现在 IP 地址里、端口号里、甚至另一个 PID 里(比如 18864、88640)。稳妥的写法是利用 netstat 输出里 PID 前面有多个空格这个特征,或者干脆把整行输出下来用眼睛对。
我自己的习惯是分两步:先tasklist /FI "IMAGENAME eq 进程名.exe"拿到所有同名进程的 PID,再对每个 PID 单独跑一次netstat -ano | findstr "LISTENING" | findstr " PID",注意 PID 前面那个空格。虽然多敲几次,但不会误判。
4.2 多实例场景:同名进程怎么区分
浏览器、Java 应用、某些开发工具,一台机器上开好几个实例是常态。这时候你拿到的是一串 PID,光看名字完全分不清谁是谁。解决方案是给每个 PID 都取一次命令行,靠命令行里的差异来区分。
命令行里可用于区分的特征通常有这几类:配置文件路径(-Dconfig=...)、工作目录(--cwd)、监听端口(--port=...)、以及某些框架会带的实例标识。我在一次排查里遇到三个同名进程,最后是靠命令行里的-Dspring.profiles.active=prod和=test区分开的,其中一个测试环境的实例占用了生产要用的端口,导致生产服务起不来。这种问题如果不看命令行,你能查一整天。
还有一种情况是进程的实际可执行文件路径不同。同名的 exe 可能存在于不同目录,比如一个是安装目录下的,一个是临时目录下的。看到临时目录、用户下载目录、回收站这类路径下的可执行文件在监听端口,基本就要拉警报了。这也是为什么我坚持要看executablepath,而不只是进程名。
4.3 UDP 端口没有状态列,判断方式完全不同
TCP 有三次握手,所以有 LISTENING、ESTABLISHED、TIME_WAIT 这些状态。UDP 是无连接的,netstat 输出里 UDP 那几行没有状态列,PID 的位置也从第五列变成第四列。这一点非常关键,很多人写 for 循环的时候用tokens=5去取 UDP 的 PID,取到的是空值,然后误以为“这个 UDP 端口没有进程在用”。
正确的写法是:UDP 用tokens=4,或者干脆分两次处理,TCP 和 UDP 分别跑。
for /f "tokens=4" %a in ('netstat -ano -p udp ^| findstr ":500 "') do @tasklist /FI "PID eq %a"UDP 端口排查的另一个难点是“看不到连接”。TCP 你能看到谁连着谁,UDP 只能看到本机开了哪个口,看不到对端,因为 UDP 本来就没有连接的概念。这时候唯一能做的就是确认端口开着、确认是哪个进程开着,然后靠业务日志去判断流量正不正常。
5. 三个真实排障流程复盘
5.1 端口被占,服务起不来
这是最高频的场景。现象是服务启动失败,日志里写着“地址已被占用”。处理流程我总结成四步,顺序不能乱。
第一步,确认端口确实被占:netstat -ano | findstr ":8080 "。如果有输出且状态是 LISTENING,说明确实有人占着。
第二步,拿到 PID,查进程:tasklist /FI "PID eq 8864" /FO LIST。这一步能排除掉最常见的一种情况——占用者其实是这个服务自己的上一个残留进程。服务没停干净,新进程起不来,杀掉旧的就好了。
第三步,如果进程名是 java、node 这类,必须查命令行:wmic process where "processid=8864" get commandline /format:list。我曾经遇到过一个 java 进程占着 8080,查命令行发现是另一个完全不相关的应用,两个团队各配了同一个端口,这种就是配置冲突,得协调,不是技术问题。
第四步,如果不是残留进程也不是配置冲突,那就查服务:tasklist /svc /FI "PID eq 8864",看它承载的是哪个服务,再sc qc 服务名看启动类型。有时候是一个设置了自动启动但你不知道的服务先起来了,占住了端口。
心得:不要一上来就
taskkill /F /PID。杀之前一定先确认这个 PID 到底是什么,我在早期干过一次,看到一个占端口的进程顺手就杀了,结果那是运行在同一台机器上的另一个业务的核心进程,事后追查了半天。确认再动手,多花的那两分钟绝对值得。
5.2 评估一台机器的对外暴露面
这个需求在做安全自查或者交接的时候特别常见:这台机器到底对外开了多少口子?靠 netstat 就能出一个初步结论。
做法是netstat -ano | findstr "LISTENING",然后只看本地地址是0.0.0.0或[::]的那些行,这些是全网卡监听的。把 PID 提取出来,逐个查进程名和路径,形成一个清单。清单里重点关注三类:一是不认识的进程在监听,这是最需要警惕的;二是本该只在内网用的服务绑在了 0.0.0.0 上,属于配置不当;三是监听在高位随机端口上的程序,可能是某个临时起的服务忘了关。
配合ipconfig /all能拿到本机所有网卡的 IP,你就能判断某个监听地址对应的是哪块网卡——是内网业务网卡,还是某个虚拟网卡,还是公网网卡。这个对应关系搞清楚了,暴露面就清楚了。再往下就是看防火墙,netsh advfirewall firewall show rule name=all能列出所有规则,不过输出很长,一般用findstr过滤端口号更快。
5.3 服务在跑但外部连不上
这个场景最能体现“本地地址”那一列的价值。现象是:本机telnet 127.0.0.1 8080通,但别的机器连不上。
按这个顺序查:先netstat -ano | findstr ":8080 "看绑定地址。如果是 127.0.0.1,问题定性,改配置绑定 0.0.0.0 重启,结束。如果是 0.0.0.0 或具体网卡 IP,那 netstat 这一层没问题,往下走。
第二步看防火墙,netsh advfirewall firewall show rule name=all | findstr "8080",没有对应规则就说明入站没放行。注意有些安全软件不走系统防火墙,是自己在驱动层拦的,这种情况用系统命令查不出来,得看软件自己的日志。
第三步,如果防火墙也没问题,就要怀疑网络链路了。这时候 cmd 里的ping和手工 telnet 就派上用场。注意很多系统默认没装 telnet 客户端,可以用powershell -Command "Test-NetConnection -ComputerName 目标IP -Port 8080"代替,这个命令在新系统上基本都有,返回结果里TcpTestSucceeded是 true 还是 false 一目了然。
6. 常见问题与排查技巧实录
6.1 权限、编码、截断这三个坑
权限问题。netstat -anob在非管理员窗口下会明确报“请求的操作需要提升”,这个好识别。麻烦的是 wmic 查某些系统进程的可执行路径时,会返回“拒绝访问”,这不代表进程不存在,只是权限不够。遇到这种情况,要么提权,要么接受“只知道进程名不知道路径”这个结果,转而用其他线索(比如启动时间、父进程 PID)去推断。
编码问题。在中文系统上,tasklist输出的中文进程名在 cmd 窗口里可能显示成乱码。先试chcp 936,如果还不行,试试chcp 65001,再不行就是字体不支持,换个字体或者直接输出到文件用记事本看。输出到文件是最稳的做法,> %temp%\out.txt然后notepad %temp%\out.txt,绕开控制台的所有显示问题。
截断问题。netstat 的记录一多,控制台的缓冲区就不够,往上翻看不到开头。两个办法:一是mode con cols=200 lines=200把窗口调大,二是直接重定向到文件。后者更靠谱,还能搜索。
| 现象 | 大概率原因 | 处理方式 |
|---|---|---|
| netstat 命令卡住不动 | 没加 -n,在做反向 DNS 解析 | Ctrl+C 中断,加 -n 重跑 |
| 查到 PID 但 tasklist 无结果 | 进程已退出,PID 是残留记录 | 重新跑一次 netstat 确认 |
| wmic 提示不是内部命令 | 系统精简,组件被移除 | 改用 powershell Get-CimInstance |
| 中文进程名乱码 | 控制台代码页不匹配 | chcp 936 / 输出到文件 |
| UDP 端口取不到 PID | 状态列不存在,PID 在第 4 列 | 把 tokens 改成 4 |
| findstr 匹配出一堆无关端口 | 没加空格,8080 匹配到 18080 | 用":8080 "带空格匹配 |
| 大量 TIME_WAIT | 短连接频繁,属正常回收过程 | 观察数量,配合 netstat -s 看统计 |
6.2 两个不太常用但很好使的招
第一个是netstat -ano 5,后面加数字表示每 5 秒刷新一次。这个在观察“连接是不是一直在重试”的时候特别有用。你盯着屏幕看,如果某个 PID 每隔几秒就冒出一条新的 SYN_SENT,说明它在反复尝试连接一个连不上的目标,这比看静态快照直观得多。
第二个是netstat -s -p tcp,输出的是协议栈的统计信息,包括总连接数、失败重连数、重传数。这个不解决具体问题,但能给你一个整体判断:如果重传数一直在涨,说明网络质量有问题,这时候去查程序逻辑就是白费劲。
6.3 我踩过的几个具体坑
PID 复用。系统的 PID 是循环使用的,一个进程退出后,它的 PID 可能很快被新进程拿走。如果你拿着几分钟前记录的 PID 去查 tasklist,查到的可能是另一个完全不相干的程序。所以排查时一定要保证 netstat 和 tasklist 是连着敲的,中间不要隔太久。
findstr的正则陷阱。findstr默认不是正则模式,但一旦加了/R就是。":8080"里的点在某些模式下会被当成通配符,虽然这里没有点,但如果你要匹配 IP 地址比如192.168.1.20,里面的点会匹配任意字符,结果把192x168y1z20这种也给捞出来。稳妥的写法是用/C:指定字面字符串。
系统进程不要乱动。查到 PID 之后发现是svchost.exe、System、lsass.exe这类系统进程在监听端口,这是正常的,Windows 本身就有一堆系统服务占着 135、445、3389 这些端口。看到这些不要惊慌,也不要去杀,先查它承载的是哪个服务,确认是不是必要服务。
远程桌面占端口的情况。远程桌面默认用 3389,如果被人改过端口,你netstat会看到一个陌生的高位端口在 LISTENING,进程是 svchost。这时候去查注册表里的端口配置,或者用wmic process where "processid=xxx" get commandline看它带的参数,能看到-p后面跟的端口号。这种情况如果不知道,很容易误判成异常程序。
6.4 cmd 之外的两条退路
虽然标题强调的是“仅靠 cmd”,但实际环境里如果发现有 PowerShell 可用,有些事会轻松很多,我把两条最常用的记下来备用。
Get-NetTCPConnection -State Listen | Select-Object LocalAddress,LocalPort,OwningProcess这一条能直接给出监听列表,字段拆得干干净净,不用自己切列。再配合Get-Process -Id就能一步拿到进程名和路径,整个链路一行命令搞定。
Get-Process -Id 8864 | Select-Object Id,ProcessName,Path,StartTime适合在已知 PID 的情况下快速拿全信息,尤其是StartTime,这个字段在判断“这个进程是不是刚重启过”时特别有用,而 cmd 的 tasklist 是不给启动时间的。
不过话说回来,这两条命令能不能用,取决于目标机器的策略。我遇到过 PowerShell 被限制执行脚本的情况,-Command后面跟一条简单命令还行,一旦涉及管道和模块就可能被拦。所以我的习惯始终是:先试 cmd,cmd 够用就用 cmd,不够用再考虑 PowerShell,最后才考虑带工具进去。这个顺序不是为了显得朴素,而是因为它的失败率最低。
真要把这套东西用熟,其实不需要记太多命令,把netstat -ano、tasklist /FI、wmic process where这三条练到条件反射,再配上一个自己写的导出脚本,日常九成以上的本机端口排查都能在五分钟内出结论。剩下的那一成,靠的是你对这台机器上跑着什么业务的熟悉程度,那个是命令替代不了的。