TCP/IP 四层模型与三次握手
2025-07-05
互联网上的通信靠一套分层的协议完成。我刚开始学网络的时候,最大的困惑不是某个协议有多复杂,而是东西太多、不知道它们之间谁管谁。后来想通了:分层的意义就是“各管一摊”,每一层只关心自己的事,对上层提供服务,对下层借用服务。这篇文章把 TCP/IP 四层模型、TCP 与 UDP 的区别、三次握手和四次挥手、常见排查命令一次讲清楚,也算是给自己的笔记做个整理。
为什么要分层
可以拿寄快递来类比。你只需要把东西装进箱子、写上收件人,剩下的事交给快递公司:揽收、分拣、干线运输、派送,每个环节的人只关心自己手里的那一步,不需要知道别的环节怎么做。网络分层也是这个思路:浏览器不用关心网线是铜缆还是光纤,路由器不用关心你在看网页还是在发邮件。
分层带来两个好处。第一是可替换:把 Wi-Fi 换成网线,上层的 HTTP 完全不用改;第二是可定位问题:出了故障,可以一层一层往下排查,而不是对着一团乱麻发愁。
四层模型一览
| 层 | 作用 | 常见协议 |
|---|---|---|
| 应用层 | 面向具体应用的数据格式 | HTTP、DNS、SSH、SMTP |
| 传输层 | 端到端传输,用端口区分应用 | TCP、UDP |
| 网络层 | 寻址与路由 | IP、ICMP |
| 链路层 | 相邻设备之间传帧 | 以太网、Wi-Fi |
需要说明的是,教科书里还有七层的 OSI 模型,它把应用层再拆成应用、表示、会话三层,把链路层拆成数据链路和物理两层。OSI 更像一个理论参照,实际的互联网是按 TCP/IP 的思路实现的,日常交流里说的“二层交换机”“三层路由”“四层负载均衡”“七层网关”,用的是 OSI 的编号习惯,和四层模型并不冲突,对应起来就行。
数据是怎么一层层包起来的
当你的程序要发一段数据,会经历“封装”的过程:
- 应用层生成数据,比如一个 HTTP 请求。
- 传输层给它加上 TCP 头部,里面有源端口、目的端口、序号、确认号、标志位、窗口大小等,得到“段”(segment)。
- 网络层再加上 IP 头部,里面有源地址、目的地址、TTL 等,得到“包”(packet)。
- 链路层加上帧头和帧尾,写入下一跳设备的 MAC 地址,得到“帧”(frame),发送到网线或无线信道上。
接收方则反过来,一层层剥开,这叫“解封装”。路径上的路由器只会拆到网络层,看一眼目的 IP 决定下一跳,并不会去读你的 HTTP 内容。这也是为什么端到端的加密(比如 HTTPS)可以对中间设备保持不透明,相关内容可以看我写的 HTTPS 那篇。
IP 与端口:找到机器,再找到程序
IP 地址负责在网络里找到哪台机器,端口负责找到机器上的哪个程序。一个 16 位的端口号范围是 0 到 65535。常见的约定端口有 22(SSH)、25(SMTP)、53(DNS)、80(HTTP)、443(HTTPS)。一条 TCP 连接由“源 IP、源端口、目的 IP、目的端口”这个四元组唯一确定,所以同一台服务器的 443 端口可以同时服务成千上万个客户端,因为它们的源地址或源端口各不相同。
客户端通常使用操作系统随机分配的临时端口(Linux 默认大致在 32768 到 60999 之间,可通过 net.ipv4.ip_local_port_range 查看),所以你会在抓包里看到类似 “51234 → 443” 的连接。
TCP 与 UDP
TCP 面向连接,保证数据按序、不丢失,适合网页、文件传输;UDP 不建立连接、不保证到达,开销小,适合 DNS 查询、实时音视频等场景。
TCP 为了做到“可靠”,做了不少事情:
- 序号与确认:每个字节都有序号,接收方确认收到了哪里,没确认的会重传。
- 流量控制:接收方通过窗口大小告诉发送方“我还能收多少”,避免被撑爆。
- 拥塞控制:发送方根据网络状况调整发送速率,避免把链路挤垮。
- 按序交付:乱序到达的数据会在接收端重新排好再交给应用。
UDP 则把这些全部省掉,只保留端口和校验和,一个数据报发出去就不管了。省掉可靠性并不意味着它“不好”:像实时通话,一个丢了的包重传回来时已经没有意义,不如直接往前走;而 DNS 查询本来就是一问一答的小报文,用 UDP 更轻量(响应过大时会改用 TCP,详见 DNS 那篇)。后面提到的 HTTP/3 更是在 UDP 之上自己实现了可靠传输,这是后话。
三次握手
- 客户端发送 SYN,表示想建立连接。
- 服务端回复 SYN+ACK,表示同意并确认。
- 客户端发送 ACK,连接建立。
更细一点:第一步里客户端会随机选一个初始序号 x,发送 SYN,此时客户端进入 SYN_SENT 状态;服务端收到后回复 SYN+ACK,带上自己的初始序号 y,确认号是 x+1,服务端进入 SYN_RCVD 状态;客户端收到后回复 ACK,确认号是 y+1,客户端进入 ESTABLISHED,服务端收到这个 ACK 后也进入 ESTABLISHED。
为什么是三次,不是两次
三次握手有两个核心目的:确认双方都具备收发能力,以及双方同步各自的初始序号。如果只有两次,服务端发出 SYN+ACK 后并不知道客户端是否真的收到、是否真的想建立这个连接。典型的问题是网络里滞留的旧 SYN 报文:它在很久之后才到达服务端,服务端若直接建立连接并等待数据,就会白白占用资源,而客户端根本不认这个连接。第三次 ACK 让客户端有机会表态,发现不对可以发送 RST 拒绝。
半连接队列与 SYN Flood
服务端收到 SYN 之后、等到最后一个 ACK 之前,这个连接处于半连接状态,内核会为它保留一小块资源。如果有人伪造大量源地址发送 SYN 而不回 ACK,就能耗尽半连接队列,这就是 SYN Flood 攻击。Linux 提供了 SYN cookies 机制作为缓解手段,原理是在队列满时不保存状态,而把信息编码进 SYN+ACK 的序号里,等收到合法 ACK 时再还原。个人服务器遇到这类问题,更现实的做法是借助云厂商的防护或前置防火墙,相关的规则思路可以参考 防火墙那篇。
四次挥手
断开连接时需要四次挥手,因为两个方向的数据流要分别关闭。TCP 是全双工的,我这边说“我发完了”,不代表你那边也发完了。
- 主动关闭方发送 FIN,进入 FIN_WAIT_1。
- 被动关闭方回复 ACK,进入 CLOSE_WAIT;主动方收到后进入 FIN_WAIT_2。此时连接处于“半关闭”状态,被动方仍然可以继续发数据。
- 被动方数据发完后,发送自己的 FIN,进入 LAST_ACK。
- 主动方收到 FIN 后回复 ACK,进入 TIME_WAIT;被动方收到 ACK 后进入 CLOSED。主动方在 TIME_WAIT 等待一段时间(Linux 上固定为 60 秒)后才真正关闭。
实际抓包时,如果被动方没有剩余数据,第二步的 ACK 和第三步的 FIN 可能合并成一个报文,看起来像“三次挥手”,这是正常现象。
TIME_WAIT 为什么要等
TIME_WAIT 有两个作用:一是保证最后那个 ACK 丢失时,对方重发的 FIN 还能得到响应;二是让本连接中滞留在网络里的旧报文自然消失,避免它们被误认为是同一四元组上新连接的数据。很多人看到 ss 里一堆 TIME_WAIT 就慌,其实少量甚至几千个都属于正常,它们只占很少的内核资源。真正需要关注的是端口耗尽,通常出现在客户端对同一个目的地址和端口频繁短连接的场景,此时更好的办法是使用连接池和长连接,而不是去调内核参数硬压。
常见的状态与问题
- 大量 SYN_SENT:本机发出了 SYN 却收不到回应,多半是目标不可达、被防火墙丢弃,或者对端没有监听。
- 大量 SYN_RECV:服务端收到 SYN 但没收到最后的 ACK,要警惕 SYN Flood,也可能是网络单向不通。
- 大量 CLOSE_WAIT:对端已经关闭,但本机的程序没有调用 close。这几乎总是应用程序的 bug,比如忘记关闭连接或线程卡死,而不是网络问题。
- Connection refused:目标主机可达,但那个端口没有程序监听,内核回了 RST。
- Connection timed out:发出去的包石沉大海,常见于防火墙静默丢包、路由不通。
“refused”和“timed out”是个很有用的区分:前者说明网络是通的,问题在服务本身;后者说明路上有东西挡住了或者目标根本不在线。
排查小工具
ping example.com traceroute example.com ss -ltn
逐个说一下:ping 使用 ICMP 回显请求,能快速判断“通不通”和大致延迟,但很多网络会屏蔽 ICMP,所以 ping 不通不一定代表服务不可用。traceroute 利用递增的 TTL 逐跳探测路径,每经过一个路由器 TTL 减一,减到零的路由器会回一个 ICMP 超时消息,从而暴露自己;出现一排星号只表示那一跳没有回应探测,不一定是故障。ss -ltn 列出本机处于监听状态的 TCP 端口,l 表示 listening,t 表示 TCP,n 表示不做名字解析。
再补几个我常用的:
ss -tan state established ss -tan state time-wait | wc -l ss -tnp curl -v telnet://example.com:443 nc -vz example.com 443
第一行看已建立的连接,第二行统计 TIME_WAIT 数量,ss -tnp 加上 p 可以看到连接属于哪个进程(可能需要 root 权限)。nc -vz 是最简单的端口连通性测试:能完成三次握手就说明那个端口是开放的。如果你想亲眼看到握手过程,可以用 tcpdump -nn -i any 'tcp port 443 and host 203.0.113.10' 抓包,会看到带有 [S]、[S.]、[.] 标志的三个报文,其中 S 代表 SYN,点号代表 ACK,这里的 203.0.113.10 只是文档示例地址,请换成你自己的目标。
和 HTTP、DNS 的关系
TCP/IP 是地基,上层的应用协议都建在它上面。浏览器访问网页时,先用 DNS 把域名变成 IP,再通过 TCP 三次握手连上服务器,如果是 HTTPS 还要再做一次 TLS 握手,最后才发 HTTP 请求。这些环节每一步都会增加往返时延,所以握手的次数和位置会直接影响首屏速度。这也是 HTTP/2 复用连接、HTTP/3 把握手压缩到更少往返的原因,完整的流程我写在了 HTTP 那篇里。
总结
最后做个小结:TCP/IP 分四层,应用层管数据格式,传输层管端到端和端口,网络层管寻址与路由,链路层管相邻设备传帧;TCP 靠序号、确认、窗口和重传提供可靠传输,UDP 则轻量无连接;三次握手同步序号并确认双方收发能力,四次挥手是因为两个方向要各自关闭;排查问题时从 ping、traceroute、ss 这些小工具入手,学会读懂 SYN_SENT、CLOSE_WAIT、TIME_WAIT 这些状态,就能少走很多弯路。