计算机网络
网络体系结构之TCP/IP的四层结构
1. 应用层
为应用程序提供网络服务,常见协议:
-
HTTP/HTTPS:网页访问
-
FTP:文件传输
-
SMTP/POP3/IMAP:电子邮件收发
-
DNS:域名解析
-
SSH:安全远程登录
-
Telnet:远程终端(不加密)
-
DHCP:动态主机配置(自动分配IP)
2. 传输层
提供端到端的数据传输控制:
-
TCP:面向连接、可靠、字节流(如网页、邮件)
-
UDP:无连接、不可靠但低延迟(如视频、DNS查询)
-
SCTP:流控制传输协议(用于信令)
3. 网络层
负责路由和地址解析:
-
IP(IPv4 / IPv6):核心协议,提供逻辑寻址
-
ICMP:网络控制报文协议(如ping、traceroute)
-
IGMP:互联网组管理协议(组播管理)
-
ARP:地址解析协议(IP→MAC,有时归为链路层)
-
RARP:逆地址解析协议(已淘汰)
4. 网络接口层
处理物理介质上的数据帧传输:
-
以太网(Ethernet)
-
WiFi(802.11)
-
PPP(点对点协议)
-
帧中继、ATM 等
注:ARP和RARP的位置有时有争议,但理解其功能即可。
面试中重点记住 TCP、UDP、IP、ARP、HTTP、DNS 就足够应对多数问题了。
IP协议
1. 寻址
-
为每一台连接到互联网的设备分配一个唯一的逻辑地址------IP地址 (如 IPv4 的
192.168.1.1或 IPv6 的2001:db8::1)。 -
这个地址用于标识数据包的源 和目标,就像快递单上的发件地址和收件地址。
2. 路由
-
决定数据包从源主机到目标主机所经过的路径。
-
IP协议根据路由表,将数据包从一个网络设备(路由器)转发到下一个,逐跳传递,最终到达目标网络。
3. 分片与重组
-
当数据包过大,超过下一跳网络链路能承载的最大传输单元时,IP协议会将数据包拆分成多个较小的分片。
-
到达目标主机后,再由IP层将这些分片重新组装成完整的数据包,交给上层协议(如TCP/UDP)处理。
补充说明
-
无连接:IP协议在发送数据前不需要建立连接,每个数据包独立处理。
-
不可靠 :IP不保证数据包一定到达,也不保证顺序正确或没有重复------这些可靠性通常由上层协议 TCP 来保证。
一句话总结:IP协议负责将数据包从源主机"导航"到目标主机,提供寻址和路由能力,并适配不同网络的传输限制。
ARP协议
ARP(Address Resolution Protocol,地址解析协议) 的作用是:在同一个局域网内,根据已知的 IP 地址,解析出对应的 MAC 地址(物理地址)。
为什么需要 ARP?
-
网络层使用 IP 地址 进行逻辑寻址(跨网络通信)。
-
链路层(以太网等)使用 MAC 地址 进行物理寻址(同一网络内的设备间通信)。
-
当一台主机要往同一网段的另一台主机发送数据时,它必须知道目标的 MAC 地址才能封装成数据帧。ARP 就是用来完成这个"IP → MAC"的映射。
工作流程(简化版):
-
检查缓存:主机先查看本地的 ARP 缓存表,如果有目标 IP 对应的 MAC 地址,直接使用。
-
广播请求 :如果没有,主机发送一个 ARP 广播请求(目标 MAC 为 FF:FF:FF:FF:FF:FF),内容为:"谁拥有 IP 192.168.1.5?请告诉我的 MAC 地址。"
-
单播响应 :目标主机收到广播后,发现是自己的 IP,就单播回复一个 ARP 响应,告诉请求者自己的 MAC 地址。
-
更新缓存:请求者收到响应后,将 IP-MAC 映射存入缓存,并发送数据。
常见相关知识点):
-
ARP 缓存 :存储最近解析过的 IP-MAC 映射,避免重复广播。可通过
arp -a查看。 -
ARP 欺骗/攻击:攻击者伪造 ARP 响应,使通信数据被重定向到攻击者机器,属于中间人攻击。
-
免费 ARP(Gratuitous ARP):主机主动广播自己的 IP-MAC 映射,用于通告地址变更或检测 IP 冲突。
一句话总结:
ARP 是局域网中"IP 地址翻译成 MAC 地址"的翻译官,没有它,同一网段的设备就无法直接通信。
关于HTTP
1. GET 和 POST 的区别
| 对比项 | GET | POST |
|---|---|---|
| 语义 | 从服务器获取资源 | 向服务器提交数据,处理请求(如创建/修改资源) |
| 参数位置 | 参数放在 URL 的查询字符串中(?key=value) |
参数放在请求体(body)中 |
| 长度限制 | URL 长度有限制(浏览器/服务器限制,通常 ≤ 2048 字符) | 理论上无限制(受服务器配置影响) |
| 安全性 | 参数暴露在 URL 中,易被浏览器历史、日志记录,不安全 | 参数在 body 中,相对更安全(但仍需 HTTPS 加密) |
| 幂等性 | 是(多次相同请求结果相同,不改变服务器状态) | 否(多次提交可能创建多个资源) |
| 缓存 | 可以被浏览器缓存、收藏为书签 | 默认不被缓存,不可书签 |
| 数据类型 | 只支持 ASCII 字符 | 支持任意类型(文件、二进制等) |
注意:GET 也可以带 body,但规范不推荐,实际使用很少。
2. HTTP 报文结构
请求报文
<方法> <请求URL> <版本>
<头部字段>: <值>
...
空行
<请求体>
示例:
GET /index.html HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0
Accept: text/html
响应报文
<版本> <状态码> <状态描述>
<头部字段>: <值>
...
空行
<响应体>
示例:
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 1024
<html>...
各部分说明
-
起始行:方法/URL/版本 或 版本/状态码/短语
-
头部(Header):键值对,描述元信息(如 Host、Content-Type)
-
空行:分隔头部和正文
-
正文(Body):可选,GET 通常无,POST/PUT 携带数据
3. HTTPS 工作流程
HTTPS = HTTP + TLS/SSL(加密层)。核心流程如下:
-
客户端发起 HTTPS 请求 (如访问
https://example.com) -
服务器返回数字证书:证书包含公钥、域名、颁发机构、有效期等
-
客户端验证证书:检查是否受信任、是否过期、域名是否匹配
-
客户端生成随机密钥(Premaster Secret),用服务器的公钥加密后发送
-
服务器用私钥解密,得到随机密钥
-
双方使用相同的随机密钥生成会话密钥(对称加密密钥)
-
后续通信使用对称加密(如 AES)进行加密传输,保证机密性和完整性
简单记忆:非对称加密传输对称密钥,对称加密传输数据。
4. Session 和 Cookie
| 对比项 | Cookie | Session |
|---|---|---|
| 存储位置 | 客户端(浏览器) | 服务器端(内存/数据库/文件) |
| 大小限制 | 通常 ≤ 4KB | 无大小限制(受服务器资源影响) |
| 安全性 | 较低,可被用户查看和篡改 | 较高,存储在服务器 |
| 生命周期 | 可设置过期时间(持久或会话级) | 通常由服务器设定超时时间 |
| 工作原理 | 服务器通过 Set-Cookie 响应头写入,后续请求自动携带 |
服务器生成唯一 Session ID,通常通过 Cookie 传递 Session ID |
| 典型用途 | 记住用户偏好、跟踪行为 | 保存用户登录状态、购物车等敏感信息 |
协作关系:
-
服务器创建 Session 后,将 Session ID 存入 Cookie 返回客户端。
-
客户端后续请求携带该 Cookie,服务器根据 Session ID 找到对应的 Session 数据。
5. HTTP 状态码(常见分类)
| 分类 | 范围 | 含义 | 示例 |
|---|---|---|---|
| 1xx | 100-199 | 信息响应 | 100 Continue |
| 2xx | 200-299 | 成功 | 200 OK, 201 Created, 204 No Content |
| 3xx | 300-399 | 重定向 | 301 Moved Permanently, 302 Found, 304 Not Modified |
| 4xx | 400-499 | 客户端错误 | 400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found |
| 5xx | 500-599 | 服务器错误 | 500 Internal Server Error, 502 Bad Gateway, 503 Service Unavailable |
6.一次完整的 HTTP 请求过程
以用户访问 http://www.example.com/index.html 为例,完整过程包括以下步骤:
1. DNS 解析
- 浏览器检查本地 DNS 缓存,若无则向 DNS 服务器发起查询,将域名
www.example.com解析成 IP 地址(如93.184.216.34)。
2. 建立 TCP 连接(三次握手)
- 客户端与服务器通过三次握手建立 TCP 连接,为 HTTP 数据传输提供可靠的字节流通道。
3. 发送 HTTP 请求
- 客户端构建 HTTP 请求报文(如 GET /index.html HTTP/1.1),通过 TCP 连接发送给服务器。
4. 服务器处理请求并返回响应
- 服务器解析请求,定位资源,生成 HTTP 响应报文(如状态码 200、HTML 内容等),通过 TCP 连接返回。
5. 浏览器解析渲染
- 浏览器接收响应,解析 HTML、CSS、JS,渲染页面。若页面包含其他资源(图片、CSS 等),会重复上述过程(可能复用 TCP 连接,取决于 Connection 头)。
6. 关闭 TCP 连接(四次挥手)
- 非持久连接下,请求-响应完成后关闭连接。HTTP/1.1 默认持久连接,可复用。
简记:DNS → TCP → 请求 → 响应 → 渲染 → 关闭(可选)
DNS解析过程
1.DNS 解析总体流程
以访问 www.example.com 为例,浏览器需要获得该域名的 IP 地址。完整过程如下:
-
检查浏览器 DNS 缓存
-
检查操作系统 DNS 缓存
-
检查 hosts 文件
-
向本地 DNS 服务器发起递归查询
-
本地 DNS 服务器进行迭代查询(从根域 → 顶级域 → 权威域)
-
返回 IP 地址并缓存
2、详细步骤说明
- 浏览器缓存
-
浏览器会缓存最近访问过的域名解析结果(时间较短,通常几十秒到几分钟)。
-
如果命中,直接返回 IP,结束。
- 操作系统缓存
-
若浏览器缓存未命中,浏览器调用操作系统(如 Windows、Linux)的 DNS 解析器。
-
操作系统会检查本机的 DNS 缓存(可通过
ipconfig /displaydns或systemd-resolve --statistics查看)。 -
如果命中,返回 IP。
- hosts 文件
-
若操作系统缓存未命中,则查找本地的 hosts 文件 (
/etc/hosts或C:\Windows\System32\drivers\etc\hosts)。 -
hosts 文件中的映射优先级最高(可手动配置,常用于开发或屏蔽网站)。
-
如果找到,返回 IP。
- 向本地 DNS 服务器发起请求(递归查询)
-
若以上都没有命中,操作系统会向 本地 DNS 服务器(也称首选 DNS 服务器,通常由 DHCP 分配,如路由器的 DNS 或 114.114.114.114)发出查询请求。
-
这个请求是 递归查询:本地 DNS 服务器承诺最终会返回一个结果(要么 IP,要么失败)。
- 本地 DNS 服务器进行迭代查询
本地 DNS 服务器如果没有缓存该域名的记录,会从根域名服务器开始,依次向下查询,过程如下(以 www.example.com 为例):
a) 查询根域名服务器
-
本地 DNS 服务器向 根域名服务器 (全球13组)发送请求,问:"
www.example.com的 IP 是多少?" -
根域名服务器不直接返回 IP,而是返回 顶级域(TLD)服务器 的地址,告诉它:你去问
.com的顶级域名服务器。
b) 查询顶级域名服务器(.com TLD)
-
本地 DNS 服务器向
.com顶级域名服务器发送请求,问:"www.example.com的 IP 是多少?" -
.comTLD 服务器也不返回 IP,而是返回 权威域名服务器 的地址,告诉它:你去问example.com的权威服务器。
c) 查询权威域名服务器
-
本地 DNS 服务器向
example.com的权威域名服务器发送请求,问:"www.example.com的 IP 是多少?" -
权威服务器返回该域名的 IP 地址(例如
93.184.216.34)。
注意:实际中经常使用 EDNS(0) 和 DNSSEC 等扩展,但核心逻辑不变。
- 返回 IP 并逐级缓存
-
本地 DNS 服务器获得 IP 后,将其返回给操作系统,同时本地 DNS 服务器会缓存这条记录(根据 TTL)。
-
操作系统收到 IP 后,也缓存到系统 DNS 缓存中,并返回给浏览器。
-
浏览器缓存该 IP,用于后续请求
3、图示(文字版)
用户浏览器 -> 浏览器缓存 -> 操作系统缓存 -> hosts 文件
↓(未命中)
本地 DNS 服务器(递归查询)
↓
根域名服务器(返回 .com TLD 地址)
↓
.com TLD 服务器(返回 example.com 权威服务器地址)
↓
example.com 权威服务器(返回 www 的 IP)
↓
本地 DNS 服务器缓存并返回 IP
↓
操作系统/浏览器缓存,开始 HTTP 请求
4、常见补充概念
递归查询 vs 迭代查询
-
递归查询 :客户端要求服务器必须返回最终结果(要么 IP,要么错误)。客户端只发一次请求,剩下的事情由服务器完成。
-
迭代查询:服务器返回一个"参考答案"(如下一级服务器地址),客户端需要自己去查。本地 DNS 服务器向根/顶级/权威服务器发起的查询就是迭代查询。
DNS 缓存的位置与 TTL
-
TTL(Time To Live):DNS 记录的有效期,由域名的管理者设置。缓存时间结束后,解析器会重新查询。
-
缓存层级:浏览器、操作系统、本地 DNS 服务器、中间路由器等都可能缓存。
DNS 预解析(dns-prefetch)
- 浏览器可以在页面中提前解析后续可能会用到的域名,减少用户等待时间。例如
<link rel="dns-prefetch" href="//example.com">。
关于TCP
三次握手
1、TCP 三次握手的过程
TCP 是面向连接的可靠传输协议,通信前需要通过"三次握手"建立连接。初始状态:客户端和服务器都处于 CLOSED,服务器主动监听进入 LISTEN 状态。
| 步骤 | 方向 | 报文 | 含义 | 状态变化 |
|---|---|---|---|---|
| 1 | 客户端 → 服务器 | SYN(seq=x) | 客户端请求建立连接,并告知自己的初始序列号 x | 客户端 → SYN_SENT |
| 2 | 服务器 → 客户端 | SYN + ACK(seq=y, ack=x+1) | 服务器同意连接,告知自己的初始序列号 y,并确认收到客户端的 SYN | 服务器 → SYN_RCVD,客户端 → ESTABLISHED |
| 3 | 客户端 → 服务器 | ACK(seq=x+1, ack=y+1) | 客户端确认服务器的 SYN | 服务器 → ESTABLISHED |
完成三次握手后,双方进入 ESTABLISHED 状态,可以开始传输数据。
注:seq 是序列号,ack 是确认号,都用于可靠传输和顺序控制。
2、为什么不是两次握手?
假设采用 两次握手 (客户端发 SYN,服务器回 SYN+ACK 后即认为连接建立),会带来一个严重问题:历史连接(旧的重复 SYN)可能导致错误建立连接。
场景示例:
-
客户端发送一个 SYN(seq=100),由于网络拥塞,该报文滞留。
-
客户端超时重传另一个 SYN(seq=200),服务器收到并回复 SYN+ACK,两次握手后连接建立,双方传输数据,然后关闭。
-
此时,那个滞留的旧 SYN(seq=100)终于到达服务器。服务器以为客户端想再次建立连接,于是回复 SYN+ACK。
-
如果是两次握手,服务器会立即认为连接已建立,等待客户端发送数据。但客户端根本没有发起这个连接,不会发送数据,服务器就会一直浪费资源等待,造成 半开连接 和资源浪费。
为什么三次握手能解决?
-
第三次握手中的 ACK 由客户端根据当前连接状态发送。如果客户端收到的是对旧 SYN 的响应,它会发现 ack 不正确(因为自己期望的是 seq=200 的确认,而不是 100),于是发送 RST 重置连接,服务器就不会误建立连接。
-
换句话说,第三次握手让客户端有机会拒绝过期的连接请求,保证了连接的可靠性。
3、为什么不是四次握手?
从功能上讲,三次已经足够:
-
第一次:客户端表明意愿(SYN)
-
第二次:服务器同意并表明自己意愿(SYN+ACK)
-
第三次:客户端确认服务器意愿(ACK)
双方各自都得到了对方的确认,序列号也完成了同步。四次握手并没有带来额外的好处,只会增加一次报文传输,降低效率。
实际上,TCP 的断开(四次挥手)之所以需要四次,是因为全双工通信需要双方独立关闭。而建立连接时,服务器可以把自己的 SYN 和对客户端 SYN 的 ACK 合并在同一个报文中(即 SYN+ACK),所以三次就够了。
4、补充:第三次握手可以携带数据吗?
可以。第三次握手中的 ACK 报文可以携带应用数据(从 seq=x+1 开始),这叫做"捎带应答",能提高效率。但前两次握手不能携带数据,因为连接尚未完全建立。
一句话总结:三次握手解决了历史连接重复导致的错误建立问题,且效率最优;两次不安全,四次浪费。
SYN、ACK (标志位)和 seq、ack(序号)的含义及其联系
1、SYN 和 ACK(控制标志位)
TCP 报文头部有多个标志位(每个占1比特),用来表示报文类型。其中:
| 标志位 | 名称 | 作用 |
|---|---|---|
| SYN | Synchronize(同步) | 用于建立连接。发送 SYN 表示发起连接请求,并告知自己的初始序列号。 |
| ACK | Acknowledgment(确认) | 用于确认收到数据。ACK=1 表示确认号(ack)字段有效,否则忽略。 |
其他标志位:FIN(关闭连接)、RST(重置)、PSH(推送)、URG(紧急)等。
2、seq 和 ack(序号字段)
TCP 是面向字节流的可靠传输,每个字节都有编号。序号字段用来标识数据的位置。
| 字段 | 全称 | 含义 |
|---|---|---|
| seq | Sequence Number(序列号) | 本报文段所发送数据的第一个字节的序号。 |
| ack | Acknowledgment Number(确认号) | 期望接收到的下一个字节的序号,同时表示对之前所有数据的确认。 |
注意:小写
seq/ack是字段;大写SYN/ACK是标志位。
3、三次握手中它们是如何工作的?
以客户端发起连接为例(初始 seq 随机为 x,服务器随机为 y):
| 步骤 | 方向 | 报文内容 | 含义 |
|---|---|---|---|
| 1 | 客户端 → 服务器 | SYN=1, seq=x |
请求连接,告知自己的初始序号 x |
| 2 | 服务器 → 客户端 | SYN=1, ACK=1, seq=y, ack=x+1 |
同意连接,告知自己的初始序号 y,并确认已收到客户端的 SYN(期望下一个字节是 x+1) |
| 3 | 客户端 → 服务器 | ACK=1, seq=x+1, ack=y+1 |
确认收到服务器的 SYN,期望下一个字节是 y+1 |
关键点:
-
为什么 ack = x+1? 因为客户端发来的 SYN 报文虽然没有数据,但也占一个字节的序号(即该 SYN 自身消耗一个序号)。所以服务器确认时,期望收到的下一个字节序号是 x+1。
-
同理,服务器的 SYN 也消耗一个序号 y,所以客户端回复 ack = y+1。
4、seq 和 ack 的联系(核心)
确认号 ack 总是等于对方发送的最后一个 seq + 该报文所携带的数据字节数(不含标志位,但 SYN 和 FIN 各占一个序号)。
-
对于普通数据报文:若对方发来
seq=100, 数据长度=20,则我应回复ack=120,表示我已收到 100~119,期待 120。 -
对于 SYN 或 FIN 报文:它们虽然不带数据,但也消耗一个序号,所以确认时 ack = seq + 1。
这种设计使得 TCP 能够:
-
实现累积确认(一次性确认之前所有数据)
-
检测丢包并重传
-
保证有序交付
5、简单记忆
-
SYN = 想要建立连接(握手)
-
ACK = 收到了对方的消息(点头)
-
seq = 我发送的数据编号("这是第 x 号包裹")
-
ack = 我已收到哪些,接下来请从哪个编号发("我已收到 x 号及以前,请发 x+1 号")
联系:SYN 和 ACK 是控制语义,seq/ack 是数据位置。握手时通过 seq 交换初始序号,通过 ack 确认对方序号,从而同步双方的字节流序号。
TCP 四次挥手
1、为什么挥手需要四次?
TCP 是全双工通信,双方可以独立关闭自己的数据传输方向。因此关闭连接时需要双方分别确认关闭。
| 步骤 | 方向 | 报文 | 含义 |
|---|---|---|---|
| 1 | 主动关闭方 → 被动关闭方 | FIN |
主动方说"我没有数据要发了,想关闭我这一侧的连接" |
| 2 | 被动关闭方 → 主动关闭方 | ACK |
被动方确认收到 FIN(但自己可能还有数据要发) |
| 3 | 被动关闭方 → 主动关闭方 | FIN |
被动方也发完数据了,说"我也可以关闭了" |
| 4 | 主动关闭方 → 被动关闭方 | ACK |
主动方确认被动方的 FIN,双方彻底关闭 |
为什么不能合并为三次?
-
第 2 步的
ACK和第 3 步的FIN通常不能合并,因为被动关闭方在收到 FIN 后,可能还有数据要发送,需要时间处理。只有当它所有数据都发完后才能发 FIN。 -
如果合并,意味着被动方同意立即关闭,但可能数据还没发完,导致数据丢失。
-
所以一般情况下是四次。但特殊情况下(如同时关闭或半关闭),也可能出现三次,不过标准模型是四次。
对比:建立连接时,服务器可以将
SYN和ACK合并为SYN+ACK,因为那时双方都没有待发送的数据,可以一步完成。
2、为什么需要等待 2MSL 才进入 CLOSED?
MSL(Maximum Segment Lifetime)是报文段在网络中的最大生存时间,通常为 2 分钟(实际实现中常见 30 秒、1 分钟或 2 分钟)。
主动关闭方在发送最后一次 ACK(第 4 步)后,会进入 TIME_WAIT 状态,持续 2MSL 时间,然后才进入 CLOSED。
原因有两条:
- 确保最后的 ACK 能被对方收到,避免对方超时重发 FIN
-
如果主动方发送的最后一个
ACK在网络中丢失,被动方收不到确认,会在超时后重发FIN。 -
如果主动方立即关闭(进入
CLOSED),就收不到这个重发的FIN,被动方会一直等待,无法正常关闭。 -
等待 2MSL 期间,主动方仍能接收并响应重发的 FIN(再发一次 ACK),确保被动方正常关闭。
- 让本次连接中所有残留的报文段在网络中消失
-
2MSL 足够让本连接中所有可能滞留在网络中的报文段(包括重复的、延迟的)全部过期失效。
-
这样可以防止这些旧报文段被误认为是新连接的报文段,造成数据混乱。
简单记忆:2MSL 保证了最后的 ACK 可靠送达,同时清空了网络中的残留报文。
3、补充说明
-
谁进入 TIME_WAIT? 主动发起关闭的一方(通常是客户端,但也可以是服务器)。
-
TIME_WAIT 持续多久? 2MSL(例如 Linux 中 MSL 默认 30 秒,TIME_WAIT 就是 60 秒)。
-
为什么不能缩短? 缩短可能导致上述两个问题,破坏 TCP 的可靠关闭机制。
一句话总结:
四次挥手是因为全双工关闭需要双方独立确认;等待 2MSL 是为了保证最后一个 ACK 能被收到,并让网络中的旧报文自然消亡。
TCP如何保证可靠性
1. 连接管理
-
内容:建立连接(三次握手)和断开连接(四次挥手)。
-
作用:保证通信双方在数据传输前达成一致状态,在结束后释放资源。
-
关键点:序列号同步、状态转换(CLOSED、LISTEN、SYN_SENT、ESTABLISHED、FIN_WAIT、TIME_WAIT 等)。
2. 校验和
-
内容:TCP 报文头部有一个 16 位的校验和字段,覆盖 TCP 伪头部、TCP 头部和 TCP 数据。
-
作用:检测数据在传输过程中是否发生比特错误(如翻转、损坏)。
-
关键点:如果校验和不匹配,接收方直接丢弃该报文,不发送确认,触发发送方超时重传。
3. 序列号 / 确认应答
-
内容:
-
序列号(seq):本报文段所发送数据的第一个字节的编号。
-
确认号(ack):期望收到的下一个字节的编号,同时表示对之前所有数据的确认。
-
-
作用:
-
保证数据按序到达(接收方可重排乱序数据)。
-
实现可靠传输(发送方根据 ack 知道哪些数据已被接收)。
-
用于去重(接收方可丢弃重复的 seq)。
-
4. 流量控制
-
内容 :通过 滑动窗口 机制,接收方告知发送方自己的接收窗口大小(rwnd)。
-
作用:防止发送方发送过快,导致接收方缓冲区溢出而丢包。
-
关键点:
-
接收方在每次确认报文(ACK)中携带窗口大小。
-
发送方的未确认数据量不能超过接收窗口。
-
若窗口为 0,发送方会启动持续计时器,定期探测窗口是否恢复。
-
5. 最大消息长度(MSS)
-
内容 :TCP 连接建立时,双方在 SYN 报文中协商的单个报文段能携带的最大数据长度(不含 TCP 头部和 IP 头部)。
-
作用:避免 IP 层分片,提高传输效率。
-
关键点:
-
MSS 通常 = MTU - 40(IPv4 下 TCP 头部 20 字节 + IP 头部 20 字节)。
-
以太网 MTU=1500,则 MSS=1460 字节。
-
如果路径 MTU 更小,可能通过 PMTU 发现机制调整。
-
6. 超时重传
-
内容:发送方发出一个报文段后,启动一个重传计时器,若在超时时间内未收到 ACK,则重新发送该报文段。
-
作用:处理丢包,保证可靠传输。
-
关键点:
-
超时时间(RTO)基于平滑往返时间(SRTT)动态计算。
-
每次重传会将 RTO 加倍(指数退避),避免网络拥塞恶化。
-
超时重传与快速重传(基于重复 ACK)是两种丢包恢复机制。
-
7. 拥塞控制
-
内容:发送方根据网络拥塞程度动态调整发送速率,避免网络过载。
-
四个核心算法:
-
慢启动(Slow Start):拥塞窗口(cwnd)从 1 个 MSS 开始,每收到一个 ACK 翻倍,直到达到慢启动阈值(ssthresh)。
-
拥塞避免(Congestion Avoidance):达到 ssthresh 后,cwnd 线性增加(每 RTT 增加 1 MSS)。
-
快速重传(Fast Retransmit):收到 3 个重复 ACK 就立即重传丢失的报文段,不等超时。
-
快速恢复(Fast Recovery):快速重传后,将 ssthresh 设为当前 cwnd 的一半,cwnd 设为 ssthresh + 3,然后进入拥塞避免。
-
-
作用:在保证可靠性的前提下,最大化网络吞吐量,同时避免拥塞崩溃。
总结(面试时可以这样串联)
TCP 通过 连接管理 建立和释放连接;
用 校验和 检测数据错误;
用 序列号/确认应答 实现可靠、有序、去重;
用 流量控制 避免接收方被淹没;
用 MSS 协商 避免 IP 分片;
用 超时重传 处理丢包;
用 拥塞控制 避免网络过载。
这七个机制共同保证了 TCP 的 可靠、有序、不丢不重、流量适配、网络友好 的特性。
TCP 重传机制
当报文段丢失时,TCP 需要重传。主要有两种:超时重传(RTO) 和 快速重传。
1. 超时重传
-
原理 :发送方发送一个报文段后,启动一个重传计时器(RTO,Retransmission Timeout)。如果在 RTO 内没有收到 ACK,则重传该报文段。
-
RTO 计算 :基于平滑往返时间(SRTT)和偏差动态调整,常见公式:
RTO = SRTT + 4×RTT偏差。 -
特点 :RTO 较大时重传慢;RTO 过小时可能误重传。每次重传后 RTO 会加倍(指数退避),避免网络拥塞恶化。
2. 快速重传
-
触发条件 :发送方连续收到 3 个重复的 ACK(Dup ACK),就认为该报文段丢失,立即重传,不等超时。
-
原理:接收方收到失序报文时会发送对最后一个有序报文的 ACK(即重复 ACK)。例如,期望 seq=100,却收到 seq=200,则发送 ACK=100。连续收到 3 个这样的 ACK 说明 100 丢失。
-
优势:比超时重传更快,避免长时间等待。
3.SACK(Selective Acknowledgment,选择性确认)
背景问题
标准 TCP 确认是累积确认(ack = 期望的下一个字节),只能告知"已收到连续的最大序号"。如果中间有多个报文段丢失,发送方只知道最早的丢包,但不知道后面哪些已经到达,往往只能重传从丢包开始的所有数据(可能浪费带宽)。
SACK 的作用
-
允许接收方在 ACK 中额外告知发送方:已经成功收到的非连续数据块(范围列表)。
-
发送方知道哪些数据已到达,只重传真正丢失的段,避免重复发送已收到的数据。
工作流程
-
TCP 连接建立时,双方在 SYN 中协商 SACK 选项(需要双方支持)。
-
当接收方收到失序数据时,除了发送标准 ACK(期望序号),还在 TCP 头部选项字段中添加 SACK 块 ,例如:
SACK: [1000-1499] [2000-2499]表示已收到这两个范围。 -
发送方根据 SACK 信息,精确重传缺失的段(如 1500-1999)。
示例
发送方发了 1~5 号段(每个 500 字节):
-
收到 1、2、4、5,丢失 3。
-
标准 ACK 只会告诉发送方 ack=1500(期望第 3 段)。
-
带 SACK 的 ACK 会额外告知:已收到 2000-2499 和 2500-2999(即 4、5 段)。
-
发送方只重传 1500-1999(第 3 段),而不是从 1500 开始全部重传。
好处
-
减少不必要的重传,节省带宽。
-
提高拥塞控制效率(更准确判断丢包情况)。
4.DSACK(Duplicate SACK,重复选择性确认)
背景问题
发送方有时会因误判而重传已经收到的数据(例如,ACK 丢失导致超时重传,或快速重传时重传了已被确认的段)。标准 SACK 无法告知发送方"这个段我已经收到过了"。
DSACK 的作用
-
接收方在 SACK 块中报告已经重复收到的数据范围。
-
发送方通过 DSACK 得知自己进行了不必要的重传,从而调整重传策略或拥塞控制参数。
工作流程
-
接收方收到一个 序号范围与之前已确认范围重叠 的段。
-
除了正常 ACK 外,在 SACK 选项中添加一个 DSACK 块 ,该块描述的是重复接收的范围(通常放在第一个 SACK 块)。
-
发送方解析后知道:这个段我之前已经发过且被确认了,不必再重传。
示例
-
发送方发了段 1(seq=1000-1499),收到 ACK 确认。
-
由于 ACK 丢失,发送方超时重传同样的段 1。
-
接收方收到重复段 1,在 ACK 中携带 DSACK:
DSACK: [1000-1499]。 -
发送方看到 DSACK,意识到可能是网络延迟或自身 RTO 过小,会调整重传计时器,避免后续无谓重传。
好处
-
帮助发送方检测不必要的重传,优化 RTO 和拥塞窗口调整。
-
避免因重传重复数据而浪费带宽。
-
对拥塞控制算法更友好(例如,不会因误重传而错误判断丢包)。
5.SACK 与 DSACK 对比
| 特性 | SACK | DSACK |
|---|---|---|
| 核心功能 | 告知已收到的非连续数据块 | 告知已收到重复数据块 |
| 解决的问题 | 发送方只知道最早的丢包,导致过度重传 | 发送方误重传已确认数据,浪费带宽并误导拥塞控制 |
| 信息内容 | 已成功接收但不连续的范围 | 已重复接收的范围 |
| 使用场景 | 多个报文段丢失时 | 重传了已经确认的报文段时 |
| 依赖条件 | 双方协商 SACK 选项 | 必须在启用 SACK 的基础上才能使用 DSACK |
面试中如何回答
问 :TCP 除了超时重传和快速重传,还有哪些重传优化?
答 :还有 SACK 和 DSACK。
SACK 让接收方告知发送方已收到的非连续数据块,发送方只重传真正丢失的部分,避免重复发送已到达的数据。
DSACK 让接收方告知发送方收到了重复数据,发送方可以知道自己的重传是不必要的,从而调整 RTO 或拥塞控制参数。
两者都需要在连接建立时协商支持。
TCP 流量控制
-
目的:防止发送方发送过快导致接收方缓冲区溢出。
-
机制 :滑动窗口 ,接收方在 ACK 中携带 接收窗口(rwnd),告知发送方还能接收多少字节。
-
发送方:未确认的数据量不能超过 rwnd。
-
零窗口 :若 rwnd = 0,发送方停止发送,启动持续计时器,定期发送窗口探测包,询问窗口是否恢复。
-
与拥塞控制的区别:流量控制是端到端的(接收方能力),拥塞控制是网络层面的(路径拥塞)。
TCP 拥塞控制
-
目的:避免发送方注入过多数据导致网络拥塞(路由器队列溢出、丢包、延迟增大)。
-
核心变量 :拥塞窗口(cwnd) ,发送方能发送的数据量为
min(cwnd, rwnd)。 -
四个算法:慢启动、拥塞避免、拥塞发生、快速恢复。
1. 慢启动
-
初始:cwnd = 1 MSS(或 2~4 MSS,取决于实现)。
-
增长方式:每收到一个 ACK,cwnd 翻倍(指数增长)。
-
结束条件:
-
达到 慢启动阈值(ssthresh),进入拥塞避免。
-
发生丢包(超时或快速重传)。
-
2. 拥塞避免
-
进入条件:cwnd >= ssthresh。
-
增长方式 :每收到一个 ACK,cwnd 增加
1/cwnd(即每 RTT 增加 1 MSS,线性增长)。 -
目的:缓慢增加窗口,探测网络容量。
3. 拥塞发生(处理丢包)
根据丢包类型不同,处理方式不同:
a) 超时重传导致的拥塞
-
ssthresh = cwnd / 2(至少 2 MSS)
-
cwnd = 1 MSS(重新慢启动)
-
这是最严重的拥塞,网络可能完全阻塞。
b) 快速重传(收到3个重复ACK)导致的拥塞
- 这是轻度拥塞,使用 快速恢复 算法。
4. 快速恢复
-
步骤(经典 TCP Reno):
-
ssthresh = cwnd / 2
-
cwnd = ssthresh + 3(加3是因为已收到3个重复ACK,表示有3个段已经离开网络)
-
每收到一个重复 ACK,cwnd += 1
-
当收到新的 ACK(确认新数据),cwnd = ssthresh,进入拥塞避免。
-
-
目的:避免进入慢启动,快速恢复到拥塞前的窗口大小。
注意:TCP Tahoe 没有快速恢复,遇到重复 ACK 也会将 cwnd 设为 1。
常用算法总结
| 阶段 | 算法 | 窗口变化 |
|---|---|---|
| 连接建立后 | 慢启动 | 指数增长至 ssthresh |
| 达到阈值 | 拥塞避免 | 线性增长(每RTT +1) |
| 超时 | 拥塞发生 | ssthresh = cwnd/2,cwnd=1,慢启动 |
| 快速重传 | 快速恢复 | ssthresh = cwnd/2,cwnd = ssthresh+3,线性增加至新ACK后回到拥塞避免 |
面试常见追问
Q:为什么需要拥塞控制和流量控制两种机制?
A:流量控制保证接收方不被淹没(端到端);拥塞控制保证网络不被过载(全局)。两者独立但协同。
Q:如何区分丢包是拥塞还是线路错误?
A:TCP 默认认为所有丢包都是拥塞造成的(网络路径可能出错,但设计上按拥塞处理)。在无线网络环境中,这会导致性能下降,于是有了 TCP 的改进版本(如 TCP Westwood)。
Q:快速重传后为什么还要快速恢复?
A:快速重传已经确认网络还有能力传输(因为收到了重复ACK),所以不必像超时那样降为1,只需减半窗口再线性增加,保持较高吞吐量。
TCP 和 UDP 的区别
| 对比维度 | TCP | UDP |
|---|---|---|
| 连接性 | 面向连接(需三次握手建立连接) | 无连接(发送前无需建立连接) |
| 可靠性 | 可靠传输(确认、重传、校验和) | 不可靠(尽最大努力交付,不保证到达) |
| 数据顺序 | 保证按序到达(通过序列号) | 不保证顺序,可能乱序 |
| 流量控制 | 有(滑动窗口) | 无 |
| 拥塞控制 | 有(慢启动、拥塞避免等) | 无 |
| 报文边界 | 字节流,无边界(应用需自行分包) | 数据报,保留边界(每个UDP包独立) |
| 开销 | 头部20字节,开销大 | 头部8字节,开销小 |
| 速度 | 较慢 | 较快(适合实时应用) |
| 典型应用 | HTTP、FTP、SSH、SMTP | DNS、视频直播、VoIP、游戏 |
一句话总结:TCP 是可靠、有序、有连接的字节流协议;UDP 是不可靠、无序、无连接的数据报协议,但速度快、开销小。
UDP 如何保证消息不丢失?
注意:UDP 协议本身不保证消息不丢失 ,它只提供"尽力而为"的交付。如果要在 UDP 上实现可靠传输,需要在 应用层 增加可靠性机制。
常用的应用层方案包括:
- 确认机制(ACK)
-
接收方收到 UDP 消息后,向发送方回复一个确认消息(ACK)。
-
发送方若在一定时间内未收到 ACK,则认为消息丢失,进行重传。
- 超时重传
-
发送方为每个 UDP 报文设置一个计时器。
-
超时未收到 ACK,则重传该报文(可设置重传次数上限)。
- 序列号
-
为每个 UDP 报文添加一个递增的序列号(Seq)。
-
接收方根据序列号判断是否有报文丢失,并通知发送方重传。
- 冗余发送
- 对关键数据,连续发送多次(如每次发 3 份),接收方只要收到一份即可。但这会占用更多带宽。
- 前向纠错(FEC,Forward Error Correction)
-
发送方发送原始数据的同时,发送一些冗余校验数据。
-
接收方即使丢了一部分包,也能通过冗余数据恢复原始内容,无需重传。
- 流量控制与拥塞控制
- 应用层可以模拟 TCP 的滑动窗口和拥塞控制,避免发送过快导致丢包。
实际案例:
-
QUIC 协议(基于 UDP)实现了可靠性、有序交付、拥塞控制,可视为"UDP 上的 TCP"。
-
RTP + RTCP(实时传输协议)通过 RTCP 反馈丢包信息,应用层自行重传。
-
游戏 UDP 中,只有关键状态(如位置、得分)需要 ACK 重传,非关键数据(如动画特效)可以丢失。
一句话总结 :UDP 自身不保证不丢包,但通过应用层添加 ACK、重传、序列号、FEC 等机制,可以做到类似 TCP 的可靠性,代价是增加了实现复杂度。