Java Connection refused 排查:TCP握手与端口监听实战

发布时间:2026/9/17 5:17:08
Java Connection refused 排查:TCP握手与端口监听实战 java.net.ConnectException 这个报错几乎每个写过 Java 的人都撞上过。报错信息往往就一行java.net.ConnectException: Connection refused: connect简洁得像什么都没说但它背后可能是服务没起来、端口写错了、绑定地址不对、容器网络不通、依赖仓库连不上甚至只是启动顺序差了几秒。我第一次遇到它是在一个 Spring Boot 项目里本地跑得好好的一上测试环境就炸查了大半天才发现是配置里数据库地址写的是别人的机器。从那以后我就养成了一个习惯看到 Connection refused先别改代码先把谁在连、连哪里、谁在听这三件事弄清楚。这篇内容就是把这些年积累的排查套路、命令、配置和踩过的坑整理出来适合刚接触网络编程的新手也适合已经能写业务但一遇到连接问题就头大的开发者看完你至少能有一套可复用的定位流程而不是靠猜。1. 先搞清楚报错语义Connection refused 到底是谁拒绝了谁1.1 从 TCP 三次握手的角度看拒绝这个动作很多人把 Connection refused 理解成网络不通这个理解是偏的。网络不通通常表现为连接超时Connection timed out而 refused 恰恰说明网络是通的包也送到了只是对面明确地回了一个拒绝信号。用 TCP 的视角来看客户端发起连接先发一个 SYN 包到目标 IP 和端口。如果目标主机上那个端口没有任何进程在监听操作系统内核会直接回一个 RST重置包。客户端收到 RST就立刻抛出java.net.ConnectException: Connection refused。整个过程非常快通常几毫秒就返回了这也解释了一个经验秒拒大概率是端口没监听慢拒几十秒后超时才是网络或防火墙的问题。还有两种情况会产生类似但不同的结果。一种是防火墙把包直接丢弃DROP而不是拒绝客户端迟迟收不到回应最后超时另一种是目标主机不可达路由层返回 ICMP 消息Java 层可能报 NoRouteToHostException。这三种错误的排查方向完全不同所以第一步永远是先分清自己遇到的到底是哪一种。注意Connection refused: connect这个带connect后缀的写法在 Windows 平台上的 JDK 里更常见同样是连接被拒Linux 上通常只显示Connection refused。别因为后缀不一样就以为是两个不同的问题。1.2 三类连接异常的分界线别混在一起查我在带新人时发现最容易浪费时间的就是把这几类错误混为一谈。下面这张表可以当作第一道分诊台异常类型典型信息底层原因排查方向ConnectExceptionConnection refused端口无进程监听内核回 RST服务是否启动、端口是否一致、绑定地址SocketTimeoutExceptionconnect timed out包被丢弃或路由不通防火墙、安全组、路由、目标主机状态UnknownHostException主机名无法解析DNS 或 hosts 配置问题域名拼写、DNS 服务、hosts 文件NoRouteToHostException无路由到主机网络层不可达网卡、子网、路由表判断顺序建议这样走先看异常类名再看到底是立刻失败还是等了一会儿才失败。如果日志时间戳显示请求发出到报错不足 100ms几乎可以锁定是端口问题如果等了 20 秒以上那就要往防火墙和网络策略方向查。1.3 从堆栈里读出有效信息别只盯着最后一行java.net.ConnectException的堆栈里最有价值的其实是at那几行。它们能告诉你到底是谁发起的连接。常见的发起方有几类数据库驱动比如 MySQL Connector 的ConnectionImpl.createNewIO、HTTP 客户端HttpURLConnection或 OkHttp、Apache HttpClient、消息队列客户端Kafka、RabbitMQ、缓存客户端Jedis、Lettuce还有构建工具自己的下载器。举个例子如果堆栈里出现com.mysql.cj.jdbc.exceptions.SQLError.createCommunicationsException那基本就是数据库连接问题去查 JDBC URL 的 host 和 port 就行如果出现org.apache.maven.wagon之类的类名那说明是构建期下载依赖被拒跟业务代码一点关系都没有。先看调用方是谁再去查那个调用方配置的连接串这一步能省掉大量无头苍蝇式的搜索。2. 排查框架三个问题锁定九成故障2.1 谁在连、连哪里、谁在听我把这套方法叫三问定位法简单但极其好用。第一问谁在连是本地开发机上的应用还是容器里的服务还是远程服务器上的进程发起方不同网络路径完全不同排查手段也不同。第二问连哪里目标地址和端口具体是多少这个信息一定要从实际运行时读取到的配置里拿而不是从你记忆中的配置里拿。配置文件可能有多份dev、test、prod环境变量、启动参数、配置中心都可能覆盖它。我见过太多次我明明改了啊结果改的是没生效的那份。第三问谁在听目标机器上那个端口到底有没有进程在监听监听在哪个网卡上只有 127.0.0.1 还是 0.0.0.0这一步用一条命令就能验证。这三问回答完问题范围基本就缩小到一两个可能性了。剩下的只是验证。2.2 五分钟快速排查清单下面这套动作我基本是肌肉记忆了全程不超过五分钟。# 1. 看本机端口监听情况Linux/macOS ss -lntp | grep 8080 # 或者用 netstat netstat -lntp | grep 8080 # 2. Windows 下查看端口占用 netstat -ano | findstr 8080 # 3. 直接测试目标端口是否可连 nc -vz 127.0.0.1 8080 telnet 127.0.0.1 8080 # 4. 如果是 HTTP 服务直接请求一下 curl -v http://127.0.0.1:8080/actuator/healthss -lntp输出里的几个字段值得说清楚。-l表示只看监听状态-n表示不做名称解析显示数字端口-t是 TCP-p显示进程。看输出时重点确认三件事端口号是否一致、监听地址是127.0.0.1还是0.0.0.0、进程名是不是你以为的那个。如果连一行输出都没有那答案已经很明确了没人监听问题在服务端启动环节。2.3 三个命令的分工与使用时机ss用来查本机适合排查服务到底起没起来。它的输出最直接0.0.0.0:8080表示监听所有网卡127.0.0.1:8080表示只监听回环地址这两者的区别在跨机器访问时是致命的。nc或telnet用来做连通性验证适合排查从我这台机器能不能连到那台机器。它们不关心应用协议只做 TCP 握手返回成功就说明端口是开的。telnet在有些精简系统上没装nc也不一定有那就退回到curl -v从它的Connected to一行判断。curl用来验证应用层适合排查端口通了但接口是不是正常。这三个工具是递进关系先用地板级的 TCP 握手确认再往上走应用层逐层收敛。3. 高频场景拆解不同上下文里的拒绝各有各的脾气3.1 目标服务还没启动或者启动到一半这是最朴素也最常见的原因。微服务架构下服务 A 启动时要调服务 B如果 B 还在初始化A 发出的连接自然被拒。这类问题的典型特征是日志里第一波请求报 refused过几十秒后自己就好了或者重启一次就好了。解决思路有三条。一是给调用加合理的重试和退避第一次失败后等 1 秒、2 秒、4 秒再试别一上来就放弃。二是配置连接超时别太长连接超时设 2 秒左右就够设成 30 秒反而会让问题暴露得很慢。三是梳理启动依赖让下游先起。我在实际项目里一般用配置化的重试示例如下Bean public RestTemplate restTemplate() { SimpleClientHttpRequestFactory factory new SimpleClientHttpRequestFactory(); // 连接超时 2 秒读超时 5 秒 factory.setConnectTimeout(2000); factory.setReadTimeout(5000); return new RestTemplate(factory); }注意重试只能用在对幂等友好的操作上。HTTP GET 重试一般安全涉及到写数据、扣款、下单这类操作重试前一定要确认接口是不是幂等的否则会把连不上变成重复扣款这种更麻烦的事。3.2 端口对不上配置漂移与硬编码的老毛病端口不一致是第二大高频原因。常见形态包括配置文件里写 8080实际启动参数--server.port9090覆盖了Nginx 转发配置里指向 8081后端服务起的却是 8080容器映射的是宿主机 8082 到容器 8080但客户端连的是 8081。这类问题最坑的地方在于每一处配置单看都没问题问题是它们没对齐。我的经验是连接地址只能有一个事实来源。要么全部走配置中心要么全部走环境变量别一半写在代码里、一半写在配置文件里。排查的时候把运行时真正生效的值打印出来比如在 Spring Boot 启动时把数据源 URL 打日志或者通过 actuator 的/actuator/env端点查看。看到实际值问题往往当场就明白了。3.3 绑定地址的坑127.0.0.1 与 0.0.0.0 的区别这个坑我在容器化项目里见过太多次。服务在自己容器里监听127.0.0.1:8080同一个容器里访问没问题但一旦别的容器或宿主机来连就会立刻被拒。原因是127.0.0.1只绑在回环网卡上外部流量根本不进入这个网卡。正确的做法是让服务监听0.0.0.0也就是所有网卡。Spring Boot 里配置server.address0.0.0.0或者干脆不配这个属性让默认值生效。Tomcat 的Connector默认绑的就是所有地址但如果有人手动改过address属性就要留个心眼。同理MySQL 的bind-address、Redis 的bind配置项都有这个问题。默认情况下它们往往只监听本机一旦从另一台机器连过去看到的症状就是 Connection refused而不是用户名密码错误。排查时用ss -lntp看一眼监听地址比翻配置文件快得多。3.4 容器与虚拟网络localhost 的语义差异容器场景下localhost的含义和在宿主机上完全不同。在容器内localhost指容器自己不是宿主机。如果应用配置里写的是localhost:3306而这个 MySQL 跑在宿主机或另一个容器里那必然会拒绝连接。Docker Compose 场景下正确写法是用服务名当主机名比如jdbc:mysql://mysql-db:3306/demo其中mysql-db是 compose 文件里定义的服务名Docker 的内嵌 DNS 会把它解析到对应容器的 IP。如果用的是宿主机上的服务Linux 下可以用host.docker.internal这个特殊域名需要额外配置或较新版本支持或者直接写宿主机的局域网 IP。这里还有一个经常被忽略的点容器启动顺序。Compose 的depends_on只保证启动顺序不保证依赖服务已经就绪。MySQL 容器启动了但还没初始化完此时应用去连一样会被拒。稳妥的做法是加健康检查让应用在依赖真正健康后再启动。3.5 依赖仓库与插件下载被拒构建期的拒绝有一种 Connection refused 跟业务代码毫无关系出现在构建阶段。典型场景是 Maven 拉依赖、Gradle 同步、IDE 初始化项目时报错里带着一堆 URL还提示请检查 url、网络和代理设置或者初始化失败。这种时候不要去看业务代码问题在下载链路。常见原因有三类。一是仓库地址不可达或者配置的私服挂了二是公司内网需要走 HTTP 代理才能出网但构建工具或 IDE 没配代理三是配置了错误的代理导致请求全被发到一个不存在的地方。我遇到过最隐蔽的一次是开发机上设置了系统级代理但那个代理服务已经卸载了所有请求都打到本地的代理端口上结果自然是满屏 refused。Maven 的settings.xml是排查重点配置镜像时建议使用稳定可用的公共镜像mirror idaliyun-public/id mirrorOfcentral/mirrorOf nameAliyun Public Repository/name urlhttps://maven.aliyun.com/repository/public/url /mirror注意如果你在公司内网镜像地址和代理配置一定要找运维确认别照搬网上博客。改完settings.xml后建议执行mvn -U clean package强制更新快照同时用mvn help:effective-settings确认最终生效的配置避免改了没生效的经典问题。Gradle 的话检查gradle.properties里的代理相关属性和init.gradle中的仓库配置IDE 里还有一层独立的代理设置这三个地方要保证一致否则很容易出现命令行能拉、IDE 拉不了的情况。3.6 中间件连接MySQL、Redis、Kafka 的典型拒绝数据库场景下Connection refused 和 Access denied 是两码事。前者是连不上后者是账号密码不对。看到 refused说明网络层就没通别去折腾密码了。MySQL 的常见原因包括服务没启动systemctl status mysqld看一眼、端口不是 3306、bind-address只绑了本机、防火墙拦了 3306。Redis 类似默认bind 127.0.0.1且开启保护模式从别的机器连就是拒绝。Kafka 的坑更绕它涉及listeners和advertised.listeners两个配置客户端先连 bootstrap server 拿到元数据元数据里返回的是advertised.listeners里配置的地址如果那里写的是内网主机名而客户端解析不了或者连不上就会报 refused。这种时候看客户端日志会发现第一次连接是成功的后面才失败。排查中间件我一般这样走先在目标机上ss -lntp确认端口在听再从客户端机器nc -vz测连通最后才用客户端工具连一次。三步都过问题多半在配置的地址上而不是网络。4. 一次完整的实战定位记录4.1 现场服务启动正常调用下游接口就报 refused前阵子碰到一个案例记录一下完整过程比较有代表性。情况是这样一个 Spring Boot 服务本地开发一切正常部署到测试环境后调用下游的用户中心接口一直失败日志里是java.net.ConnectException: Connection refused。后端同学第一反应是下游挂了但下游服务的负责人说他们跑得好好的访问日志里根本没有收到这个请求。这就是典型的还没到应用层就被拒了说明连接在 TCP 层就没建立起来。判断依据很直接如果是应用层报错下游的访问日志里会有记录哪怕返回 500现在下游日志干干净净说明包根本没被应用接收。4.2 逐层验证从本机到目标一步步收紧第一步在报错的应用机器上确认配置的目标地址。通过环境变量和配置中心查了一遍实际生效的是user-center.internal:8080。注意这里是个内部域名不是 IP。第二步测域名解析。用nslookup或者getent hosts user-center.internal查一下解析出来是一个内网 IP这一步正常。第三步测端口连通性。nc -vz user-center.internal 8080结果是Connection refused。到这一步确认了域名没问题就是目标端口拒绝连接。第四步去目标机器上查监听。ss -lntp | grep 8080显示服务确实在跑但监听地址是127.0.0.1:8080而不是0.0.0.0:8080。问题找到了下游服务在一个容器里配置文件里显式绑定了回环地址导致只有容器内部能访问跨容器就是拒绝。4.3 修复与回归验证修复方案很简单把下游服务的监听地址改成0.0.0.0重新发布。发布后再从调用方机器执行nc -vz user-center.internal 8080这次返回succeeded。然后重新触发一次业务调用接口正常返回问题解决。整个过程从开始定位到修复大概二十分钟其中真正花时间的是确认下游服务到底监听在哪这一步因为一开始大家都盯着调用方看没人去目标机器上确认。这也是我想强调的点Connection refused 的答案大概率在目标端不在发起端。发起端只能告诉你我被拒了目标端才能告诉你为什么拒。4.4 把这次经验固化下来这个案例之后我在团队里推了两个小改动。一是在所有服务的启动脚本里加了一行日志把实际监听地址和端口打印出来方便一眼确认。二是在部署流水线里加了个健康检查步骤发布后自动从另一个容器去连目标端口连不通就直接判定发布失败别等到业务报错才发现。健康检查这段可以用很朴素的脚本实现#!/bin/bash # 发布后连通性自检 HOST$1 PORT$2 RETRY10 for i in $(seq 1 $RETRY); do if nc -z $HOST $PORT; then echo port $PORT is reachable exit 0 fi echo waiting for $HOST:$PORT ... ($i/$RETRY) sleep 2 done echo port $PORT is NOT reachable exit 1这个脚本看着简单但在滚动发布、容器重启的场景里能拦掉不少低级问题尤其是那种服务还没起来就被调用的时序问题。5. 常见问题速查与避坑经验5.1 一张表覆盖八成场景现象高概率原因验证命令处理方式本地调用本地服务被拒服务未启动或端口写错ss -lntp | grep 端口启动服务、核对端口跨机器访问被拒监听在 127.0.0.1ss -lntp看地址改绑 0.0.0.0容器内访问宿主机被拒localhost 语义错误ip addr查网关用服务名或宿主机 IP构建时下载依赖被拒仓库地址或代理配置错误mvn -X看请求地址修 settings.xml 与代理连接数据库被拒端口不通或 bind-address 限制nc -vz db 3306改配置、开防火墙Kafka 首次能连后续被拒advertised.listeners 配错查 broker 配置改成客户端可达地址偶发被拒重试就好下游启动慢或滚动发布看报错时间分布加重试 健康检查5.2 我踩过的几个坑第一个坑是只改配置不重启。有些配置是启动时读取的运行期改了文件不生效。我一度以为是网络问题折腾半天最后重启一下就好了。判断方法很简单看修改时间和你上次重启时间如果配置比进程新那就该重启了。第二个坑是把超时当成拒绝。有一回日志里写的是 refused但实际排查发现是防火墙做的静默丢弃只是客户端框架把超时异常包装成了 ConnectException 的子类误导了方向。后来我养成习惯一定要看日志的时间戳从请求发起到报错隔了多久这个信息比异常类名还可靠。第三个坑是代理设置残留。开发机上配过代理工具卸载了但环境变量还在导致所有请求都往一个不存在的地址发。排查方式就是检查http_proxy、https_proxy、HTTP_PROXY这些环境变量以及 IDE、构建工具各自独立的代理配置。这类问题最隐蔽因为命令行和 IDE 的表现可能不一样。第四个坑是IPv6 优先。有些环境里localhost优先解析成::1而服务只监听了 IPv4 的127.0.0.1于是连接被拒。解决办法要么把服务改成同时监听要么在客户端直接用127.0.0.1而不是localhost。这种问题在 macOS 上尤其常见因为它的解析顺序和 Linux 有差异。5.3 几条预防性的配置建议与其每次出问题临时查不如提前把该做的做了。连接超时统一设成 3 到 5 秒别用默认的无限等待关键的下游调用加上带退避的重试服务启动日志里打印实际监听地址和关键连接串健康检查走真实的 TCP 探测而不是简单的进程存活判断环境变量和配置文件之间确定一个优先级规则并写进文档。还有一点值得单独提日志里把目标地址打出来。很多框架的默认日志只写连接失败不写连的是谁。在客户端初始化的时候加一行 info 日志把 host 和 port 打出来故障时能省下大量时间。这个改动成本极低收益极高。我个人在这些年处理这类问题的体会是Connection refused 本身其实是个很诚实的错误它几乎从不骗人说端口不通就是端口不通。真正难的是找到那个生效的配置在哪儿以及确认目标端到底在听什么。把三问定位法和那几条命令变成习惯大部分同类问题都能在十分钟内定位到方向。最后再分享一个小技巧如果机器上没装 nc 也没装 telnet可以用 bash 自带的能力测端口省得临时装工具# 不依赖 nc/telnet 的端口探测 timeout 2 bash -c cat /dev/null /dev/tcp/127.0.0.1/8080 echo open || echo closed这个写法在大多数 Linux 和 macOS 的 bash 环境里都能用出问题时手边有台机器就能立刻验证比翻工具箱方便得多。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询