兄弟们,先别急着往下翻,我先问一个问题:你们在跑服务的时候,有没有见过这种报错?
code复制error: listen tcp 127.0.0.1:11434: bind: only one usage of each socket address
或者是这种:
code复制curl: (35) tcp connection reset by peer
如果你第一反应是“这啥意思?是不是服务器炸了”,那这篇东西你值得读完。如果你能一眼看出是端口被占用了、对端把连接重置了,那我后面讲的抓包分析、TIME_WAIT、滑动窗口这些内容,你大概率也会感兴趣。
TCP(传输控制协议)是互联网的“地基级”协议,但说实话,绝大多数开发者和运维对它都停留在“三次握手、四次挥手”的面试层面。真正到了线上环境,遇到一个奇怪的TCP报错,很多人就抓瞎了。我今天就从一个实际干活的人的角度,把TCP这层皮扒开,从原理讲到实战排错,再把Modbus TCP、连接数量上限这些常被问到的边角料一起收拾了。全程不讲废话,但保证你读完能拿去用。
1. 为什么传输层离不开TCP:从一堆“五花八门”的报错说起
1.1 你以为你在用TCP,其实你天天都在跟它“吵架”
我们平时写的代码、调的接口、连的数据库,绝大多数跑在TCP之上。HTTP、HTTPS、SSH、FTP、MySQL协议、Redis协议,底层清一色是TCP。为什么?因为TCP解决了两个最让人头疼的问题:数据能不能送到,以及数据是不是完整的。
但你有没有发现,TCP报错的形式特别多?光我在生产环境里就见过的就有:
bind: only one usage of each socket address(端口被占用)tcp connection reset by peer(连接被对端重置)connect timed out(连接超时)dial tcp: lookup xxx: no such host(DNS解析失败)connection reset(连接意外重置)
这些报错本质上都是在说一件事:你和对方之间的“连接”出了问题。但具体是哪一环出了问题,你得懂TCP的连接模型才能判断。
1.2 TCP和UDP:同层两个“性格”,选错就遭罪
先别急着记命令,先把根上的概念搞清楚。传输层就两个主流协议,TCP和UDP。很多人只知道“TCP可靠,UDP不可靠”,其实这只是结果,不是原因。
TCP是有“状态”的,它会在通信之前建立一个虚拟的端到端通道,然后在这条通道上做流量控制、拥塞控制、重传、排序。你可以把它想成寄挂号信——每一封都有编号,签收要回执,丢了要再发,最后按编号顺序整理好。UDP则像喊话——你喊出去了,对方听没听到、听到的顺序对不对,你根本不管。
| 对比项 | TCP | UDP |
|---|---|---|
| 连接状态 | 面向连接,有状态 | 无连接,无状态 |
| 可靠性 | 可靠交付,不丢不重不乱序 | 尽力而为,可能丢包、乱序 |
| 传输方式 | 字节流,无边界 | 数据报,有边界 |
| 效率 | 有连接建立/释放开销,头部20字节 | 开销小,头部8字节 |
| 适用场景 | 文件传输、网页、数据库、远程登录 | 音视频直播、DNS查询、游戏同步 |
| 典型协议 | HTTP/HTTPS、SSH、FTP、Modbus TCP | NTP、DHCP、RTP、TUNNEL(隧道里的UDP) |
UDP也不是“不好”,只是它把可靠性交给你自己实现。比如有些自研长连接用的就是UDP+应用层ACK重传,这样能省掉TCP的内核状态机开销,在弱网环境下反而更灵活。但对大部分人来说,TCP是默认选择,因为它帮你扛住了80%的传输层脏活累活。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 三次握手和四次挥手:教科书之外的真实连接生命周期
2.1 三次握手为什么必须是三次,不是两次?
这是被问烂了的问题,但我还是要讲,因为理解这个,你才能看懂后面抓包里SYN和ACK的真实含义。
握手要解决的问题是:让双方都确认自己的发送能力和接收能力是正常的。我们假设客户端叫A,服务端叫B。A先发一个SYN给B,意思是“我要和你建立连接”。B收到后回复SYN+ACK,意思是“我收到你的SYN了,我的接收能力OK;同时我也发一个SYN给你,看看
