就说 TCP 这朵云:三次握手、踩坑与调优,一次讲明白
做后端和网络的这些年,我数不清处理过多少和 TCP 相关的疑难杂症。上个月帮朋友排查一个本地服务启动失败的问题,终端里明晃晃地挂着 error: listen tcp 127.0.0.1:11434: bind: only one usage of each socket address——一看就是端口被占,但很多新手会愣住:什么是 bind?什么是 socket address?为什么"只能使用一次"?类似的还有 connection reset by peer、connect timeout、Navicat 连 MySQL 失败、ESP8266 连不上 OneNET、远程桌面分辨率异常……这些问题表面五花八门,底层全绕不开 TCP 连接的建立、维持和断开。
这篇文章我从实战角度出发,把 TCP 连接的原理、故障排查、实操抓包、常见场景调优一次性讲透。不管你是刚学 Socket 编程的学生,还是被线上连接问题折磨的运维,或者玩嵌入式、工业控制的朋友,看完都能直接拿去用。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
1. 先搞清楚:TCP 连接建立时做过什么
很多人背过"三次握手、四次挥手",但问一句"为什么"就卡住了。我先把这个基础夯实,后面排查问题才有方向。
1.1 三次握手:不只是"确认存在"
TCP 是面向连接的、可靠的传输协议。所谓"连接",本质上是通信双方各自维护一组状态,而不是真的有一条物理线路。三次握手做的最核心的一件事,是让双方确认两件事:我发的你能收到,你发的我也能收到。也就是确认各自的发送和接收能力都正常。
具体流程大家都熟:客户端发 SYN,服务端回 SYN+ACK,客户端再回 ACK。网上很多文章把它类比成"打电话互相确认",但这个类比其实不到位。我更喜欢用同事协作的说法:
- 客户端说"你好,我这边准备好了"(SYN)
- 服务端收到后心里有数了——客户端能发,我能收。于是回复"好的,我也准备好了,你确认一下"(SYN+ACK)
- 客户端回"收到"(ACK)——这时服务端也知道客户端能收,自己也能发,才敢开始传数据
为什么不能只握手两次?因为如果只有两次,服务端无法确认客户端的接收能力是否正常。比如客户端发的 SYN 因为网络重传走了一条慢链路,服务端回了 SYN+ACK 之后就认为连接建立好了,但客户端可能压根没收到,更别说回最后一次 ACK 了。与其等超时重传白白浪费资源,不如用第三次 ACK 当作最终确认信号。
注意:三次握手建立连接只解决了"可达性"问题,不代表应用层就一定能正常通信。比如服务器进程本身崩了,但内核还在响应 TCP 握手,这时
TCP connect succeed但应用层请求照样失败。排查时别只盯着握手看。
1.2 四次挥手:断开比建立更讲究
断开连接需要四次挥手,因为 TCP 是全双工的,每个方向的传输通道必须单独关闭。想象一条双向双车道:不是双向各出一辆车打个招呼就完事,而是每条车道都得独立完成"我要关了,你确定没有车还在路上吗"的确认。
流程是:
- 主动方发 FIN,告诉对方"我这边数据发完了"
- 被动方回 ACK,表示"收到你的关闭请求",但被动方可能还有数据要发,所以只是确认,不立即关闭
- 被动方数据发完后,也发 FIN 告诉主动方"我也发完了"
- 主动方回 ACK,双方才真正释放连接
这里最容易出问题的是两个状态:CLOSE_WAIT 和 TIME_WAIT。CLOSE_WAIT 出现在被动方,表示"对方要关了,我也得赶紧关";如果程序代码里没正确关闭 Socket,被动方会一直卡在这个状态,连接逐渐堆积,最后导致"文件描述符不够用"或"端口全部占满"。
1.3 连接状态机:CLOSE_WAIT、TIME_WAIT 是怎么来的
排查 TCP 问题时,netstat 或 ss 打出来一堆状态,新手最容易看晕。我把高频出现的状态整理一下:
| 状态 | 含义 | 常见原因 |
|---|---|---|
| LISTEN | 服务端在监听端口 | 正常 |
| SYN_SENT | 客户端发了 SYN,等回应 | 网络不通、服务端没监听 |
| SYN_RECV / SYN_RCVD | 服务端收到 SYN,等客户端 ACK | 半连接队列满、SYN 洪泛、客户端异常 |
| ESTABLISHED | 连接建立成功 | 正常 |
| FIN_WAIT_1 / FIN_WAIT_2 | 主动方发出 FIN 后等待 | 正常但若堆积可能被动方没关 |
| CLOSE_WAIT | 被动方收到 FIN,等待应用层 close | 最常见:代码没释放连接 |
| TIME_WAIT | 主动方收到 FIN 后进入的等待状态 | 正常,但大量堆积会影响端口复用 |
| CLOSED | 连接已关闭 | 正常 |
TIME_WAIT 是排查高频对象。主动关闭的一方发出最后的 ACK 后,会进入长达 2MSL(Maximum Segment Lifetime,报文最大生存时间)的等待期。之所以要等这么久,是因为怕最后一个 ACK 在网络上丢了,对方会重发 FIN,你如果已经关掉连接就收不到了。这是 TCP 可靠性设计的一部分,绝不能粗暴去掉,虽然可以用 SO_REUSEADDR 缓解端口占用问题,但那是另一码事。
2. TCP 还是 UDP?先别急着选
热搜词里"TCP和UDP的区别"排得很靠前,我看很多帖子解释得都太死板。说句实在话,很多场景下选哪个协议不是看性能,而是看你的信任模型和资源承受能力。
2.1 两者的核心差异:有连接 vs 无连接
TCP 有连接、可靠、有序、面向字节流;UDP 无连接、不可靠、无序、面向报文。这个基础定义很多人都会背,但实际使用中的差异才是关键。
TCP 的可靠是通过确认重传、序号、校验和、流量控制、拥塞控制一整套机制换来的。代价是头大、有延迟、有连接建立开销,结构也比 UDP 复杂。UDP 则是一把梭,发出去就不管了,头只有 8 字节,没有连接建立成本,也没有重传延迟,适合能忍受丢包的场景。
我见过一个经典选错案例:有人用 TCP 去传视频流,因为网
