这里有一篇我从实际排查和反复踩坑中总结出来的TCP通信笔记,写的是从协议原理到实操排错的完整链路。刷到“第三十六天—TCP通信”这个标题时,我第一反应是:这哥们儿估计正在从socket入门往底层钻,刚好和我当年走过的路对上了。TCP通信这东西,说简单也简单,无非就是连接、收发、关闭;说复杂也复杂,三次握手、四次挥手、粘包拆包、重传机制、端口状态、半开连接,每一项都够你折腾几天。这篇文章我往深了写,把热门话题里大家关心的问题,比如三次握手为什么必须是三次、地址已在使用怎么解决、dup ack触发快速重传的原理、嵌入式设备里的modbus tcp和CAN通信差异、多机系统里TCP怎么配合等,全部揉进去,搞成一份既能当学习笔记又能当避坑手册的东西。适合正在学网络编程的人、做嵌入式通信的工程师,以及所有被连接不稳定、端口耗尽、收发异常恶心过的朋友。
1. 先想清楚:TCP到底在解决什么问题
1.1 为什么需要“连接”这个概念
我在学习过程中一直在想一个问题:TCP为什么要搞出“连接”这么个抽象概念?你不能像写文件一样直接往网络里丢数据吗?还真不能。底层IP协议只负责把数据包从A点送到B点,它不保证顺序、不保证不丢、不保证不重复。你用IP发消息,就好比把一百封信塞进邮筒,它们可能乱序到达、可能半路丢掉、甚至可能被复印一份同时送到。这种场景下,上层应用根本没法好好干活。
TCP的“连接”就是解决这个问题的。所谓的连接,不是一根真的电缆,而是通信双方在内核里各自维护的一套状态记录。这套记录包含序号、确认号、窗口大小、拥塞状态等一堆参数。用打电话来类比最合适:拨号、对方接听、双方说“喂,能听到吗”,然后才开始说正事。这一套确认流程,建立的就是一个逻辑上的通话链路,保证你说的话对方能按顺序听到,没听清的会让你重说一遍。
所以TCP带给你的核心能力是三个:面向连接、可靠传输、字节流。面向连接意味着收发双方有明确的状态机;可靠传输意味着丢包会重传、乱序会重组;字节流意味着应用层看到的是一个没有边界的continuous stream,你可以把它当作一个管道来读写。理解这三点,后续所有机制都围绕它们展开。
1.2 TCP/IP协议栈里,TCP到底站在哪层
很多初学者一上来就被“四层模型”“七层模型”绕晕。我自己刚学的时候也迷糊,后来发现只需要记住一条主线:数据是从上往下封装,再对端从下往上解封的。
应用层把数据交给TCP,TCP给数据加上TCP头(源端口、目的端口、序号、确认号、窗口等),变成段(Segment);然后交给IP层,IP再套上IP头(源IP、目的IP),变成包(Packet);最后通过网卡发出去,走链路层。接收端反过来一层层剥掉头,最终把原始数据交给应用。你写代码时只跟socket打交道,根本不用碰这些头,内核协议栈全帮你处理完了。
这里要特别说一下端口号。一台服务器上同时跑着N个服务,怎么区分数据是给MySQL的还是给Nginx的?靠端口。TCP头里的源端口和目的端口就是“门牌号”,IP地址决定去哪台机器,端口号决定交给机器上的哪个进程。这也是为什么TCP连接可以四元组唯一标识:源IP、源端口、目的IP、目的端口。只要四元组不一样,就可以认为是不同的连接。
我工作里经常遇到有人问“为什么一个端口只能有一个服务监听”。答案是监听时只用到目的IP和目的端口,如果两个进程绑定同一个端口,内核就不知道该把新连接交给谁了。但一个服务accept出来的连接可以有成千上万个,因为每条连接的源IP和源端口不同,四元组互不相同,内核完全分得清。理解了这一点,你就能明白为什么高并发服务可以只监听一个端口却扛住百万连接。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 三次握手和四次挥手:连接的一生
2.1 三次握手为什么不多不少正好三次
TCP建立连接的过程,教科书上写得很清楚:客户端发SYN,服务端回SYN+ACK,客户端再回ACK。但“为什么必须是三次”这个问题,很多人背了答案却不懂。
关键在于两点。第一,双方必须确认彼此的收发能力都正常。第一次握手,客户端发SYN给服务端,服务端收到后知道“客户端的发送能力OK,我的接收能力也OK”;第二次握手,服务端回SYN+ACK,客户端收到后知道“我的发送和接收都OK,服务端的发送和接收也OK”;但此时服务端还不知道客户端能不能正常接收数据,所以需要第三次握手,客户端回一个ACK,服务端收到后才知道“客户端的接收也OK”。到这里,双方才彻底确认了彼此的收发通道都没问题。
第二,防止失效的旧连接请求突然到达。假设只有两次握手,一个在网络里滞留了很久的旧SYN请求突然到达服务端,服务端直接建立连接,发数据过去,但客户端根本不知道这回事,连接就成了僵尸连接。三次握手可以
