我那天遇到一个特别头疼的问题:自己写的服务监听 127.0.0.1:11434,重启时报错 error: listen tcp 127.0.0.1:11434: bind: only one usage of each socket address。字面意思是端口被占用,可我明明把旧进程都杀干净了。一路排查下来,发现这事牵扯到 TCP 连接状态、TIME_WAIT 堆积、抓包验证,最后还撞上防火墙拦截策略。整个排查过程让我把 TCP、抓包、TIME_WAIT、防火墙这四块知识完整串了一遍,收获非常大,所以整理成这篇学习总结,写给同样被连接问题折磨过的后端开发、运维和网络初学者。
如果你遇到过 Connection reset by peer、connect timeout、服务端口突然起不来、或者抓包抓不到数据这类问题,这篇文章应该能帮你建立一套完整的排查思路。我会按四次握手的连接状态、TIME_WAIT 产生的原因和调优方法、Wireshark 与 tcpdump 的实际抓包姿势、以及防火墙如何影响 TCP 连接这条线展开,最后附上我踩过的坑和排查记录。
1. TCP 连接管理:从三次握手到四次挥手
1.1 三次握手不是“打招呼”,是双向能力探测
很多人把 TCP 三次握手理解成“你好了我也好了”,这个说法不够准确。三次握手的本质是双方确认彼此的收发能力都正常,同时协商初始序列号。
我用一个生活化的例子解释:你给朋友打电话,说“喂,能听到吗?”(SYN),朋友回答“能听到,你能听到我吗?”(SYN+ACK),你再说“能听到”(ACK)。电话接通,开始聊天。这个过程中,任何一次应答丢失,双方都无法确认链路是通的。
实际报文长这样:
code复制客户端 → 服务端: SYN, seq=1000
服务端 → 客户端: SYN+ACK, seq=5000, ack=1001
客户端 → 服务端: ACK, seq=1001, ack=5001
注意看 seq 和 ack 的变化规律:发送方每次发数据,seq 是它自己的序列号;ack 是“我期望收到对方的下一个字节”,所以 ack 等于对方的 seq 加 1(如果没带数据)。这个机制在后面抓包分析时会反复用到。
握手过程中的两个细节值得记一下:
- SYN 包会消耗一个序列号,所以握手完成后客户端下一次发送数据的 seq 是 1001 而不是 1000。
- 握手的超时重传机制:客户端发出 SYN 后,如果 1 秒没收到 SYN+ACK,会重传,间隔翻倍(1s、2s、4s、8s…),达到重传上限后就报
connect timeout。
1.2 四次挥手与连接状态流转
断开连接比建立连接更复杂,因为 TCP 是全双工的,双方都可以主动发送数据,所以每一方的收发通道都要单独关闭。这就形成了四次挥手:
code复制主动关闭方 → 被动关闭方: FIN, seq=x
被动关闭方 → 主动关闭方: ACK, seq=y, ack=x+1
被动关闭方 → 主动关闭方: FIN, seq=y
主动关闭方 → 被动关闭方: ACK, seq=x+1, ack=y+1
为什么不能把中间两个报文合并成一次?因为被动关闭方收到 FIN 后,可能还有数据要发给对方,它必须先发 ACK 表示“我收到你的关闭请求了”,等自己的数据发完了再发 FIN。这中间的时间差就是 CLOSE_WAIT 状态。
连接状态流转上,有个特别容易混淆的点:主动关闭方和被动关闭方经过的状态完全不同。
主动关闭方:ESTABLISHED → FIN_WAIT_1 → FIN_WAIT_2 → TIME_WAIT → CLOSED
被动关闭方:ESTABLISHED → CLOSE_WAIT → LAST_ACK → CLOSED
实际线上最容易出问题的两个状态就是 CLOSE_WAIT 和 TIME_WAIT。CLOSE_WAIT 大量堆积,通常说明服务端代码没有正确调用 close(比如 IO 流没关);TIME_WAIT 大量堆积,通常说明有大量短连接主动关闭,或者代理层、网关层频繁创建连接。
1.3 TCP 与 UDP 怎么选
很多人纠结 TCP 和 UDP 的选择问题,我用一个表格整理清楚:
| 维度 | TCP | UDP |
|---|---|---|
| 连接状态 | 有连接,维护状态机 | 无连接,不维护状态 |
| 可靠性 | 可靠传输,有重传机制 | 尽力而为,不保证交付 |
| 有序性 | 保证字节流按序到达 | 不保证顺序 |
| 速度 | 相对慢,有握手和确认 | 快,无握手开销 |
| 适用场景 | HTTP、数据库、文件传输 | 音视频通话、游戏、DNS、广播 |
注意“可靠”不代表“绝对可靠”,TCP 只是通过确认和重传提高了可靠性,网络断了照样会断。搞实时音视频的用 UDP 不是因为 UDP 更好,而是因为 TCP 的重传机制在丢包时会引入延迟,画质卡顿比丢几个音频帧更让人难以接受。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. TIME_WAIT 解剖:为什么是 2MSL
2.1 TIME_WAIT 存在的两个理由
TIME_WAIT 是主动关闭连接的一方在收到对端 FIN 后进入的状态,持续时间为 2 倍的 MSL(Maximum Segment Lifetime,报文最大生存时间)。MSL 在 Linux 上默认是 60 秒,所以 TIME_WAIT 通常持续 120 秒,但一般你看到 TIME_WAIT 状态的连接几乎都在这个时间窗口内。
这个状态为什么必须存在?两个核心原因:
第一,保证最后一个 ACK 能到达对端。如果主动关闭方发完最后的 ACK 就立刻释放连接,这个 ACK 在网络中丢失了,被动关闭方在 LAST_ACK 状态一直等不到 ACK,会重发 FIN。此时主动关闭方已经 CLOSED,收到 FIN 后会回一个 RST,导致被动关闭方报错。TIME_WAIT 让主动关闭方在 2MSL 窗口内可以响应重发的 FIN。
第二,让旧连接的延迟报文在网络上自然消亡。假设连接 A 关闭后,端口立刻被连接 B 复用,如果连接 A 的旧数据包还在网络中游荡,到达对端后会被当成连接 B 的数据,造成数据错乱。2MSL 时间足够一个数据包从网络中消失。
2.2 如何观察 TIME_WAIT
排查 TIME_WAIT 最常用的两条命令:
bash复制netstat -an | grep TIME_WAIT | wc -l
ss -state time-wait | wc -l
ss 是新版 iproute2 工具包的命令,性能比 netstat 好,大数据量下推荐用 ss。
只看数量还不够,最好结合端口信息定位是哪些连接产生的:
bash复制# 查看本机所有 TIME_WAIT 连接的本地端口和远端端口
ss -state time-wait -tn '( sport = :5000 or dport = :5000 )'
实战中如果你发现 TIME_WAIT 数量达到几千甚至上万,同时查看 /proc/sys/net/ipv4/ip_local_port_range 范围内的端口数量,可能就会得出“端口快被耗尽”的结论:
bash复制cat /proc/sys/net/ipv4/ip_local_port_range
# 32768 60999
这个范围默认有 28232 个可用端口,如果 TIME_WAIT 堆积到接近这个数量,新连接将无法分配本地端口,表现为“连接建立失败”“connect 报错”。
2.3 端口耗尽问题与解决
我遇到的那次报错 bind: only one usage of each socket address,不是连接端口耗尽,而是监听端的端口被 TIME_WAIT 状态的连接占用。Linux 默认情况下,如果某个端口上有 TIME_WAIT 状态的连接,服务进程绑定这个端口时可能报 Address already in use。
这个问题的本质是:客户端断开连接后,如果服务端是主动关闭方,那么服务端的那个 socket 会进入 TIME_WAIT,在 120 秒内不能被重新 bind。
解决思路有几个,按推荐程度排序:
- 开服务端时设置
SO_REUSEADDR套接字选项,这是最正统的解法。它允许在 TIME_WAIT 状态下重新绑定端口,Nginx、Redis、Tomcat 这些成熟的服务默认都会设置。 - 如果连接数是常态化的短连接高并发,优先考虑让客户端复用连接,比如 HTTP 的 keep-alive、数据库连接池,减少主动关闭的次数。
- 对客户端(出站连接)可以开启
net.ipv4.tcp_tw_reuse,这个参数允许客户端复用处于 TIME_WAIT 状态的连接作为新的出站连接。注意:这个参数只对出站连接生效,不要指望它解决服务端的 bind 报错。
调整内核参数的姿势:
bash复制sysctl -w net.ipv4.tcp_tw_reuse=1
sysctl -w net.ipv4.tcp_fin_timeout=30
tcp_fin_timeout 默认 60 秒,调小可以缩短 TIME_WAIT 的持续时间,但要注意:这个参数改的是 FIN_WAIT_2 的超时时间,不完全等于 TIME_WAIT 的时长。 真正的 TIME_WAIT 时长是 tcp_max_tw_buckets 和 2MSL 决定的,如果 TIME_WAIT 总量超过 tcp_max_tw_buckets(默认 180000),系统会直接释放多余的 TIME_WAIT socket,数量少的时候一般不触发。
这里特别提醒一句:网上很多教程让你同时开 tcp_tw_recycle,这个参数在 NAT 环境下会带来严重问题。它开启后,Linux 会根据时间戳判断同一来源 IP 的包是否合法,但不同设备经过同一个 NAT 出口时时间戳不连续,会导致大量丢包和连接失败。不要在生产环境开启 tcp_tw_recycle,这个参数在 Linux 4.12 之后也已经从内核中移除了。
3. 抓包实操:从现象到证据
3.1 工具选型:Wireshark 与 tcpdump
排查网络问题,光看日志和分析状态不够,只有抓包能给出“铁证”。我用得最多的两个工具是 tcpdump 和 Wireshark,它们的定位完全不同:
tcpdump是命令行工具,适合在服务器上直接抓包,不依赖图形界面,可以抓完包保存为 pcap 文件再拉回本地分析。Wireshark是图形化工具,适合对 pcap 文件做深度协议分析,界面交互友好,过滤和统计功能强大。
线上环境首选 tcpdump,因为服务器通常没有图形界面,而且 Wireshark 做实时抓包性能开销大。本地环境、或者想快速复现某个协议问题时,可以用 Wireshark 直接抓。
还有一个容易忽略的点:抓包工具看到的“包”和应用程序实际收发的数据不完全一致。 服务端 curl 报 Connection reset by peer,抓到的包可能是客户端发出的 RST;应用程序卡住不动,抓包可能看到的是零窗口(TCP Window 为 0)通知。抓包反映的是内核协议栈的处理结果,应用层有没有正确读取数据要结合应用日志一起看。
3.2 常用过滤语法
抓包最核心的能力是“过滤”。不是所有流量都值得看,网络流量一大,不设过滤条件等于大海捞针。
tcpdump 的常用姿势:
bash复制# 抓所有经过 eth0 的 TCP 包
tcpdump -i eth0 tcp
# 抓指定端口 8080 的流量
tcpdump -i eth0 port 8080
# 抓指定主机和端口的双向流量
tcpdump -i eth0 host 192.168.1.100 and port 8080
# 抓 SYN 包,也就是新连接请求
tcpdump -i eth0 'tcp[tcpflags] & tcp-syn != 0'
# 抓 RST 包,这通常和连接异常相关
tcpdump -i eth0 'tcp[tcpflags] & tcp-rst != 0'
# 保存为 pcap 文件,方便后用 Wireshark 分析
tcpdump -i eth0 -w capture.pcap host 192.168.1.100 and port 8080
Wireshark 的显示过滤器语法更友好,直接在过滤栏写:
code复制tcp.port == 8080
ip.addr == 192.168.1.100
tcp.flags.syn == 1
tcp.flags.reset == 1
http
实际抓包时,优先用小范围过滤,比如 host 192.168.1.100 and port 443,能避免很多干扰流量。如果不知道确切端口只知道 IP,可以先不设端口过滤,抓完整包再用 Wireshark 的“Conversation”功能按连接筛选。
3.3 抓包验证三次握手和 TIME_WAIT
纸上谈兵没意思,我用一个实际例子演示抓包流程。启动一个本地 HTTP 服务,然后用 curl http://127.0.0.1:8080/index.html 发起请求,同时 tcpdump 监听回环接口:
bash复制tcpdump -i lo -n -S 'tcp and port 8080' -w handshake.pcap
抓完用 Wireshark 打开 handshake.pcap,按 tcp.stream eq 0 筛选出这条连接,你会看到完整的报文序列:
- 第 1 个包:客户端 127.0.0.1:xxxxx → 服务端 127.0.0.1:8080,SYN。
- 第 2 个包:服务端 → 客户端,SYN+ACK。
- 第 3 个包:客户端 → 服务端,ACK。
然后看到 HTTP 请求和响应,最后是四次挥手:
- 客户端或服务端发 FIN。
- 对端回 ACK。
- 对端发 FIN。
- 发起方回 ACK。
注意看第 7 个包之后的连接状态,主动关闭方进入 TIME_WAIT,但抓包里看不到 TIME_WAIT 状态本身,它只存在于协议栈里。你只能通过“最后一个 ACK 发出后,源端口依然存在一段时间”这个现象推断 TIME_WAIT 的发生。
有个技巧可以验证 TIME_WAIT:抓完包后立刻执行 netstat -an | grep 8080,你会看到源端口进入 TIME_WAIT,等大约 120 秒再执行一次,状态消失。这个验证做法比单纯看理论解释印象深刻得多。
USB 抓包、无线网卡抓包这些场景有个共同痛点:普通网卡抓不到其他设备的包,因为网卡默认只收发给自己的数据。抓无线流量时可能需要开启监听模式(Monitor Mode),抓 USB 流量需要专门的抓包硬件或软件支持。如果你只是排查本地服务问题,回环接口 lo 最省事,不用关心网卡混杂模式的问题。
4. 防火墙为什么会“搅局”
4.1 防火墙的工作层级与黑白名单
防火墙在 TCP 连接中扮演的角色经常被忽视,但很多“诡异”的网络问题最后都指向防火墙。
防火墙的工作位置在网络层和传输层,核心机制就两种思路:
- 白名单:默认拒绝所有流量,只放行明确允许的。安全要求高的生产环境常用。
- 黑名单:默认放行所有流量,只阻止明确禁止的。开发测试环境、个人电脑常用。
黑白名单的优先级值得注意:一旦规则匹配,后面的规则就不再执行,顺序不同结果可能完全不同。比如一条“拒绝所有”规则放在前面,后面再写“允许 80 端口”就没用了;把允许规则放前面,拒绝规则放后面,才能生效。
防火墙对 TCP 连接的影响集中在两个层面:
- 网络层:直接丢弃数据包,不做任何回应。表现是“连接超时”,客户端等不到 SYN+ACK。
- 传输层:主动回复 RST 包或 ICMP 不可达信息。表现是“连接被重置”,客户端很快收到错误。
如果你看到 curl: (35) TCP connection reset by peer,很多时候不是对端应用挂了,而是中间的防火墙直接回了 RST。这是跟“连接超时”最大的区别:超时说明包被丢了,reset 说明有设备在主动干预。
4.2 防火墙对 TCP 连接的影响现象
我在实践中总结了几种最典型的“防火墙搞事情”现象:
现象一:connect() failed: Connection timed out
SYN 包发出去后石沉大海。可能原因有两个:对端服务不在监听,或者防火墙静默丢弃了 SYN。区分方法是找另一台机器 telnet 同端口,如果别人能通你不行,大概率是防火墙按来源 IP 做了限制。
现象二:connect() failed: Connection reset by peer
SYN 包得到了响应,但响应是 RST 而不是 SYN+ACK。这种情况通常是防火墙匹配到黑名单规则,直接回 RST。也有可能是目标端口确实没人监听,本机内核回了 RST。
现象三:连接能建立,但请求一发起就被掐断
这种最令人困惑。三次握手正常,应用层发送数据后立刻收到 RST。原因多半是防火墙规则只允许建连、不允许传输特定数据,或者状态检测防火墙发现连接状态异常。
现象四:内网穿透、端口映射不通
服务部署在内网,通过网关映射到公网,外部访问失败。这种情况最常见的坑是:网关的防火墙规则只放行了入口方向的流量,没有放行出口方向的回包流量,导致 SYN+ACK 回不去。
4.3 Linux 防火墙与 conntrack
Linux 上最常见的防火墙是 iptables 和 nftables。iptables 用起来是最普及的:
bash复制# 查看当前规则
iptables -L -n -v
# 放行 8080 端口的 TCP 流量
iptables -A INPUT -p tcp --dport 8080 -j ACCEPT
# 禁止 8080 端口
iptables -A INPUT -p tcp --dport 8080 -j DROP
iptables 里最容易造成 TCP 连接问题的是 DROP 和 REJECT 的区别。DROP 是静默丢弃,客户端表现为超时;REJECT 是回 RST 或 ICMP,客户端表现为连接被拒绝/重置。很多初学者随手写了条 -j DROP 规则,排查半天发现是自己在拦自己。
iptables -A INPUT -p tcp --dport 8080 -j DROP 这条规则只拦入站流量,但 conntrack 状态检测机制会影响出站。Linux 有连接跟踪(conntrack)子系统,它维护一张连接状态表:
NEW:新连接的第一个包ESTABLISHED:连接后续的包RELATED:相关联的子连接
常见的错误是只放行了 NEW 和 ESTABLISHED,忘记放行 RELATED,导致 FTP 数据连接、ICMP 错误通知这类流量被拦。排查这类问题可以直接查 conntrack 表:
bash复制cat /proc/net/nf_conntrack
如果发现连接表爆炸(数量接近 nf_conntrack_max),新连接会直接丢包,表现为“部分连接能通,部分连接超时”,重启服务后症状消失又很快复现。解决方式是调大限制,或者清理无用连接:
bash复制sysctl -w net.netfilter.nf_conntrack_max=1048576
另外很多新手查防火墙只查 iptables,忘了还有 firewalld 或者 ufw 这些上层封装。Ubuntu 的 ufw 是 iptables 的前端,Debian 系的 UFW 和 iptables 规则会互相覆盖。检查时两条命令都跑一下,避免漏查。
4.4 硬件防火墙的一些注意点
不少人用华为、H3C 这些硬件防火墙设备做透明部署或路由模式部署。硬件防火墙和服务器软件防火墙有个本质区别:硬件防火墙默认就是白名单策略,不主动放行任何流量,需要显式配置安全策略。如果你把设备接进去之后业务不通,先检查策略有没有放行对应源地址、目的地址、端口。
有个常见问题是部分型号的防火墙规则库更新失败。这个问题通常出在授权过期、时间不对、或者网络层根本连不上更新服务器。可以先确认设备时间是否正确——设备时间误差过大会导致证书验证失败。再用 display firewall session table 这类命令查看当前会话,确认流量是否正确到达防火墙。
还有一些人在华为 eNSP 模拟器里启动防火墙设备时报错,这类问题多半是软件兼容性、虚拟化环境不支持导致的,和真实网络问题关系不大。模拟器里抓包可以用 Wireshark,但有些接口默认不加探针抓不到包,需要把接口拖进 Wireshark 的抓包点,这个和实体设备的端口镜像是一个道理。
5. 常见问题与排查技巧实录
5.1 典型问题速查表
我把自己踩过和被问过的问题整理成一张排查速查表,方便你遇到问题时快速定位方向:
| 现象 | 可能原因 | 排查命令/方法 |
|---|---|---|
| connect 超时 | 对端未监听、防火墙 DROP、网络不通 | telnet ip port、抓包看 SYN 有无响应 |
| connection reset by peer | 防火墙 REJECT、对端服务异常退出、协议栈 RST | ss -state established、抓包看 RST 来源 |
| bind: Address already in use | TIME_WAIT 占用端口、端口被其他进程占用 | ss -lntp、ss -state time-wait |
| 大量 CLOSE_WAIT | 服务端代码没关连接 | 查看进程 fd、代码 review |
| 大量 TIME_WAIT | 短连接过多 | `ss -state time-wait |
| 服务重启后端口起不来 | TIME_WAIT + 未设置 SO_REUSEADDR | 代码中设置 SO_REUSEADDR |
| 抓包抓不到数据 | 网卡模式不对、没有权限、过滤条件错误 | tcpdump -i any、sudo 权限、检查监听的网卡 |
| 部分连接不通 | conntrack 表满、防火墙规则顺序问题 | cat /proc/net/nf_conntrack、iptables -L -n -v |
| 本机能通别的机器不通 | 防火墙黑白名单按来源 IP 限制 | 检查防火墙规则、iptables -L INPUT -n |
5.2 一次完整的排查流程
回到最开头那个报错。当时我重启服务时看到 error: listen tcp 127.0.0.1:11434: bind: only one usage of each socket address,第一反应是端口被占。排查顺序是这样的:
ss -lntp | grep 11434,发现端口根本没有 LISTEN 状态的进程。ss -state time-wait -tn | grep 11434,发现大量 TIME_WAIT 状态的连接占着这个端口。- 查看这些连接的来源和去向,确认是一个客户端程序在频繁创建短连接,每个连接都是客户端主动断开,导致服务端作为主动关闭方积累了 TIME_WAIT。
- 因为服务端代码是我自己的,我直接在 bind 之前设置
SO_REUSEADDR,问题立刻解决。
设置方式不同语言写法不同,Python 里这样写:
python复制import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('127.0.0.1', 11434))
sock.listen(128)
C/C++ 里对应的是 setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)),Java 里是 ServerSocket(int port, int backlog, InetAddress bindAddr) 配合 setReuseAddress(true)。
这里有个经验判断:如果你改了代码还是报错,先确认是不是同一个进程老实例没杀干净。 ss -lntp 里显示的进程 PID 最靠谱,不要只看 ps -ef | grep 的结果,有些守护进程会 fork 出子进程,父进程杀了子进程还活着。
5.3 抓包时的几个玄学问题
抓包本身技术难度不大,但实际操作有几个坑经常让人怀疑人生。
坑一:tcpdump 抓到包,Wireshark 打开却是空的。
检查一下保存文件时是不是用了 -w 参数,这个没问题的话,看是不是权限不足。Linux 上抓包需要 root 权限,普通用户抓包经常遇到“抓到 0 个包”的情况。用 sudo tcpdump 解决。
坑二:服务跑在 eth0,你却抓的是 eth1。
这个听起来低级,但真实发生过。服务器上有多个网卡,服务监听的地址可能是 0.0.0.0,实际流量从 eth0 进,你却在 eth1 上抓,自然啥都抓不到。先 ss -lntp 看清楚监听地址对应的网卡,再选对接口。不确定的时候直接 -i any 抓所有接口。
坑三:看到 TCP 包全是乱序和重传,但应用层没报错。
这种情况要么是网卡驱动开启了 TSO/GRO 硬件卸载,要么是抓包工具所在的位置(比如虚拟化环境)导致分片重组异常。很多网卡会把数据包在硬件层面做大包合并或分段,抓包看到的“乱序”其实是驱动处理后的结果。可以通过 ethtool -k eth0 查看 offload 状态,排查时临时关闭卸载功能:
bash复制ethtool -K eth0 gro off gso off tso off
坑四:抓包抓到了报文,但应用就是收不到。
这种问题基本和抓包无关了,多半是防火墙没放行。数据包到达网卡、通过内核协议栈,但在交给应用 socket 之前被防火墙拦截了。抓包位置如果在网卡层,能看到包进了本机;如果应用就是收不到,瞄一眼 iptables 规则,经常有惊喜。
5.4 防火墙配置经验
最后分享几个防火墙配置的实践经验。
改完防火墙规则不生效,先看规则顺序。iptables 的规则是从上到下匹配的,匹配到第一条就直接执行,不再往下走。你在前面写了一条 -j DROP 的宽泛规则,后面再写细粒度允许规则,等于白写。修改规则前用 iptables -L -n --line-numbers 查看规则序号,插入时指定位置:
bash复制iptables -I INPUT 3 -p tcp --dport 8080 -j ACCEPT
-I 表示插入到指定位置,比如插入到第 3 条之前。
线上环境修改防火墙规则前,先写一个“保底”计划。有些设备支持定时回滚规则,或者你在操作前保存当前规则:
bash复制iptables-save > /root/iptables-backup-$(date +%Y%m%d%H%M%S).rules
出现问题马上恢复。
另外,很多人的服务器是 CentOS、Ubuntu 默认开启 firewalld 或 ufw,如果 iptables 里没规则但连接不通,看一下是不是上层服务在管防火墙。检查 firewalld 状态:
bash复制systemctl status firewalld
firewall-cmd --list-all
如果你直接操作 iptables,而 firewalld 还在运行,两个管理工具很容易互相冲突。最简单的办法是统一用一种管理方式:要么用 firewalld 的规则,要么关闭 firewalld,纯用 iptables,不要混着来。
结尾
说回我自己的经历。那次排查之后,我把这四个知识点彻底串了起来:TCP 状态机解释了 TIME_WAIT 为什么产生,TIME_WAIT 解释了 bind 为什么报错,抓包验证了我对握手和挥手的理解,最后又发现防火墙能直接影响 TCP 连接的表现。现在遇到类似问题,我的第一反应已经不是“怎么办”,而是“从哪一层开始排查”。
最后分享一个我自己的土办法:抓包之前,先在一张纸上写下三个问题——我要找什么包?应该在哪台设备上抓?抓到之后怎么判断符合预期?三句话写清楚再动手,成功率翻倍。如果你也遇到“诡异”的网络问题,建议照这个流程走一遍,很多答案其实就在抓到的包里面。
