写在前面:传输层是计算机网络中最重要的一章,没有之一。你想想,面试考得最多的是什么?三次握手、四次挥手、拥塞控制------全是传输层的。我当年面试的时候,几乎每场都被问到TCP,问法还千奇百怪。这篇文章我把传输层的知识点从头到尾梳理了一遍,特别是TCP的连接管理和拥塞控制,用了大量的图解和对比表格,保证你看完之后面试不慌。建议收藏,反复看。

文章目录
-
- 一、传输层基本概念
-
- [1.1 传输层的地位](#1.1 传输层的地位)
- [1.2 套接字(Socket)](#1.2 套接字(Socket))
- [1.3 复用与分用](#1.3 复用与分用)
- 问题与解答
- 二、端口号详解
-
- [2.1 端口号范围](#2.1 端口号范围)
- [2.2 常用端口号速查表](#2.2 常用端口号速查表)
- 问题与解答
- 三、UDP协议详解
-
- [3.1 UDP特点](#3.1 UDP特点)
- [3.2 UDP头部格式](#3.2 UDP头部格式)
- [3.3 UDP适用场景](#3.3 UDP适用场景)
- 问题与解答
- 四、TCP协议详解
-
- [4.1 TCP特点](#4.1 TCP特点)
- [4.2 TCP头部格式](#4.2 TCP头部格式)
- 问题与解答
- 五、TCP三次握手
-
- [5.1 握手过程详解](#5.1 握手过程详解)
- [5.2 序列号和确认号的变化](#5.2 序列号和确认号的变化)
- [5.3 为什么不能是两次握手?------旧电话留言场景](#5.3 为什么不能是两次握手?——旧电话留言场景)
- 问题与解答
- 六、TCP四次挥手
-
- [6.1 挥手过程详解](#6.1 挥手过程详解)
- [6.2 TIME_WAIT状态详解](#6.2 TIME_WAIT状态详解)
- [6.3 为什么四次挥手不能是三次?](#6.3 为什么四次挥手不能是三次?)
- 问题与解答
- 七、TCP可靠传输机制
-
- [7.1 确认应答](#7.1 确认应答)
- [7.2 超时重传](#7.2 超时重传)
- [7.3 序号与确认号](#7.3 序号与确认号)
- 问题与解答
- 八、TCP流量控制
-
- [8.1 滑动窗口机制](#8.1 滑动窗口机制)
- [8.2 零窗口探测](#8.2 零窗口探测)
- 问题与解答
- 九、TCP拥塞控制四大算法
-
- [9.1 慢启动(Slow Start)------慢慢加速试探路况](#9.1 慢启动(Slow Start)——慢慢加速试探路况)
- [9.2 拥塞避免(Congestion Avoidance)------保持匀速](#9.2 拥塞避免(Congestion Avoidance)——保持匀速)
- [9.3 快重传(Fast Rettransmit)------发现事故立即变道](#9.3 快重传(Fast Rettransmit)——发现事故立即变道)
- [9.4 快恢复(Fast Recovery)------事故处理后恢复速度](#9.4 快恢复(Fast Recovery)——事故处理后恢复速度)
- [9.5 cwnd变化过程图](#9.5 cwnd变化过程图)
- 问题与解答
- [十、TCP vs UDP核心对比](#十、TCP vs UDP核心对比)
- 十一、新手常见误区
- 十二、面试高频考点汇总
- 十三、模拟测试题
- 互动话题
- 参考资料
一、传输层基本概念
1.1 传输层的地位
传输层位于网络层之上、应用层之下,是网络分层中最关键的一层。它为应用进程提供了端到端的通信服务。
说白了,网络层(IP)负责把数据包从主机A送到主机B,但主机B上可能同时跑着几十个程序------浏览器、微信、SSH......数据到底该交给谁?这就是传输层要解决的问题。
1.2 套接字(Socket)
套接字是传输层的通信端点,由IP地址和端口号组成:
Socket = IP地址 + 端口号
比如 192.168.1.10:8080 就是一个套接字,唯一标识了一台主机上的一个进程。
用公寓楼类比来理解端口号:
IP地址 = 公寓楼的地址,比如"阳光公寓小区3号楼"。这栋楼里住着很多人(进程)。
端口号 = 房间号,比如808室、443室、22室。同一栋楼(同一个IP)有很多房间(端口),每个房间住不同的人(进程)。
邮递员(网络层)把包裹送到公寓楼(IP地址),楼管(传输层)根据房间号(端口号)把包裹交给具体的住户(进程)。如果没有房间号,楼管就不知道该把包裹给谁。
踩坑提醒 :新手常问"端口号和IP地址有什么区别?"答案是:IP地址定位哪台主机 ,端口号定位主机上的哪个进程。就像快递送到你家小区(IP),但还需要门牌号(端口)才能找到具体收件人。一个IP可以有65536个端口(0~65535),所以一台服务器可以同时提供很多服务。
1.3 复用与分用
-
复用(Multiplexing):多个应用进程的数据通过同一个传输层协议发送出去(比如多个应用都用TCP发送数据)
-
分用(Demultiplexing):传输层收到数据后,根据端口号把数据正确地交给对应的应用进程
应用层: 浏览器(:80) 邮件客户端(:25) SSH(:22)
| | |
传输层: -------- TCP复用/分用 ----------
|
网络层: ---------- IP -----------------
问题与解答
Q1:传输层和网络层的区别是什么?
A:网络层提供主机到主机 的通信(点到点),传输层提供进程到进程的通信(端到端)。网络层用IP地址标识主机,传输层用端口号标识进程。
Q2:为什么需要传输层?应用层直接用IP不行吗?
A:不行。IP只负责把包送到目标主机,但不知道该交给哪个进程。而且IP不提供可靠传输、流量控制、拥塞控制等服务,这些都需要传输层来实现。
二、端口号详解
2.1 端口号范围
端口号是16位的无符号整数,范围是 0~65535,分为三类:
| 范围 | 名称 | 说明 | 示例 |
|---|---|---|---|
| 0~1023 | 知名端口(Well-known) | 分配给常用服务,需要root/管理员权限 | HTTP(80)、HTTPS(443) |
| 1024~49151 | 注册端口(Registered) | 给已注册的应用程序使用 | MySQL(3306)、Redis(6379) |
| 49152~65535 | 动态端口(Dynamic) | 客户端临时使用 | 浏览器随机分配的端口 |
踩坑提醒 :在Linux上,绑定1024以下的端口需要root权限。如果你写了个Web服务想用80端口,记得用
sudo运行,不然会报Permission denied。这个坑我踩过不止一次。
2.2 常用端口号速查表
| 服务 | 端口号 | 协议 | 说明 |
|---|---|---|---|
| HTTP | 80 | TCP | 网页浏览 |
| HTTPS | 443 | TCP | 加密网页浏览 |
| FTP | 20/21 | TCP | 文件传输(数据/控制) |
| SSH | 22 | TCP | 安全远程登录 |
| DNS | 53 | TCP/UDP | 域名解析 |
| SMTP | 25 | TCP | 发送邮件 |
| POP3 | 110 | TCP | 接收邮件 |
| IMAP | 143 | TCP | 接收邮件(比POP3更强大) |
| Telnet | 23 | TCP | 远程登录(不安全,已淘汰) |
| MySQL | 3306 | TCP | 数据库 |
| Redis | 6379 | TCP | 缓存数据库 |
| RDP | 3389 | TCP | Windows远程桌面 |
记忆技巧:HTTP 80(8+0=8,最基础),HTTPS 443(比HTTP多一层安全),SSH 22(两个2,安全安全),DNS 53(我散,域名解析分散到各地)。
问题与解答
Q1:同一个端口能同时被TCP和UDP使用吗?
A:可以!端口号是TCP和UDP各自独立管理的。TCP的53端口和UDP的53端口是两个完全不同的套接字。DNS就是同时用TCP和UDP的53端口。
Q2:客户端的端口号是怎么确定的?
A:客户端通常使用临时端口号(49152~65535),由操作系统自动分配。每次新建连接都会随机选一个未使用的端口。
三、UDP协议详解
3.1 UDP特点
UDP(User Datagram Protocol,用户数据报协议)的特点用四个字概括:简单粗暴。
- 无连接:发送数据前不需要建立连接
- 不可靠:不保证数据到达、不保证顺序、不重传
- 面向报文:应用层交下来的报文,UDP不拆分也不合并,直接加上头部就发
- 头部只有8字节:非常轻量
3.2 UDP头部格式
0 7 8 15 16 23 24 31
+--------+--------+--------+--------+
| 源端口 | 目的端口 |
+--------+--------+--------+--------+
| 长度 | 校验和 |
+--------+--------+--------+--------+
| 数据 |
+-----------------------------------+
就这,8字节,4个字段,简单到极致。对比TCP的20字节头部(还不算选项),UDP真的是极简主义。
3.3 UDP适用场景
| 场景 | 原因 |
|---|---|
| 视频/音频通话 | 允许丢帧,但要求低延迟 |
| 直播 | 实时性比完整性更重要 |
| DNS查询 | 请求和响应都很小,不需要建立连接的开销 |
| 游戏 | 需要快速传输位置信息,丢几个包无所谓 |
| SNMP | 网络管理,简单查询 |
踩坑提醒:很多人觉得UDP就是"不靠谱"的协议,其实UDP的"不可靠"恰恰是它的优势。对于实时性要求高的场景,TCP的重传机制反而会导致延迟越来越大。选择TCP还是UDP,要看你的业务需求。
问题与解答
Q1:UDP完全不保证可靠性吗?
A:UDP本身不提供可靠性保证。但如果应用需要,可以在UDP之上自己实现可靠传输(比如QUIC协议就是基于UDP实现的可靠传输)。UDP是"不提供,但不禁止"。
Q2:为什么DNS既用TCP又用UDP?
A:DNS查询通常用UDP(因为请求/响应很小,UDP开销低)。但当响应数据超过512字节(比如区域传输)时,会切换到TCP。另外,DNS服务器之间的区域传输也用TCP。
四、TCP协议详解
4.1 TCP特点
TCP(Transmission Control Protocol,传输控制协议)是传输层的"重量级选手":
- 面向连接:通信前必须先建立连接
- 可靠传输:保证数据不丢失、不重复、按序到达
- 面向字节流:把应用层数据看作无结构的字节流
- 全双工:双方可以同时发送和接收数据
- 头部最小20字节:比UDP复杂得多
4.2 TCP头部格式
0 16 32
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 源端口号 | 目的端口号 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 序列号 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 确认号 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|数据偏移|保留|U|A|P|R|S|F| 窗口大小 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 校验和 | 紧急指针 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 选项(可选) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
关键字段:
| 字段 | 说明 |
|---|---|
| 序列号(Seq) | 标识发送的数据字节流位置 |
| 确认号(Ack) | 期望收到的下一个序列号 |
| 数据偏移 | TCP头部长度,以4字节为单位 |
| 标志位 | URG/ACK/PSH/RST/SYN/FIN,控制连接状态 |
| 窗口大小 | 接收方的接收缓冲区大小,用于流量控制 |
| 校验和 | 校验头部和数据 |
问题与解答
Q1:TCP为什么是面向字节流的?
A:TCP不保留应用层报文的边界。应用层发来1000字节,TCP可能拆成多个段发送,也可能合并多个小报文一起发送。接收方收到的也是连续的字节流,需要应用层自己解析报文边界。
Q2:TCP头部最大能有多少字节?
A:TCP头部最小20字节(无选项),最大60字节(选项部分最多40字节)。数据偏移字段4位,最大值15,乘以4就是60字节。
五、TCP三次握手
5.1 握手过程详解
三次握手是TCP建立连接的过程,目的是让双方确认彼此的发送和接收能力都正常。
用打电话类比来理解:
第一次握手 = 你拨号,对方电话响了(SYN)
你拿起电话,拨号给对方。这相当于客户端发送SYN报文:"喂,我想跟你通话,你能听到吗?"
第二次握手 = 对方接听,说"喂,我能听到你,你也能听到我吗?"(SYN+ACK)对方接起电话,先回一句"我能听到你"(ACK),再问"你能听到我吗?"(SYN)。这相当于服务端回复SYN+ACK:"我收到你的请求了,我也准备好了,你那边正常吗?"
第三次握手 = 你说"我能听到你,开始聊吧"(ACK)你听到对方的声音了,回一句"我能听到你,开始吧"。这相当于客户端发送ACK:"确认,双方都能收发,连接建立!"
客户端 服务端
| |
|--- 1. SYN (seq=x) ---------------->|
| 客户端进入 SYN_SENT 状态 |
| |
|<-- 2. SYN+ACK (seq=y, ack=x+1) ---|
| 服务端进入 SYN_RCVD 状态 |
| |
|--- 3. ACK (seq=x+1, ack=y+1) ---->|
| 双方进入 ESTABLISHED 状态 |
| |
第一步:客户端发送SYN报文
- 标志位 SYN=1
- 序列号 seq=x(随机生成)
- 客户端进入 SYN_SENT 状态
第二步:服务端回复SYN+ACK报文
- 标志位 SYN=1, ACK=1
- 序列号 seq=y(随机生成)
- 确认号 ack=x+1
- 服务端进入 SYN_RCVD 状态
第三步:客户端发送ACK报文
- 标志位 ACK=1
- 序列号 seq=x+1
- 确认号 ack=y+1
- 双方进入 ESTABLISHED 状态,连接建立
5.2 序列号和确认号的变化
用具体数字来理解:
客户端 (seq=100) 服务端 (seq=300)
| |
|--- SYN, seq=100 ------------------>|
| |
|<-- SYN+ACK, seq=300, ack=101 -----|
| |
|--- ACK, seq=101, ack=301 -------->|
| |
|--- PSH+ACK, seq=101, data="Hello"->|
| |
|<-- PSH+ACK, seq=301, ack=106 ------|
| (ack=101+5, 因为"Hello"是5字节) |
| |
踩坑提醒:注意序列号和确认号的关系------确认号 = 对方的序列号 + 已收到的数据长度。这个关系搞清楚了,三次握手和四次挥手的理解就简单多了。
5.3 为什么不能是两次握手?------旧电话留言场景
如果只有两次握手,会出什么问题?
用"旧电话留言"的场景来理解:
假设你(客户端)上周给朋友(服务端)打了一通电话,留言说"下周三我们约个饭"。但因为某种原因,这条留言在信箱里卡了很久,直到今天才送到朋友手上。
朋友收到留言后,如果只有"两次握手",他会直接认为"约饭成立",然后开始订餐厅、做准备(进入ESTABLISHED状态)。
但实际上,你早就忘了这通电话(客户端早已关闭),甚至这周已经出差了。朋友白白等了你一下午,餐厅也白订了(服务端白白维持了一个没人用的连接,浪费资源)。
三次握手怎么解决这个问题?
朋友收到旧留言后,回电给你确认:"你上周说约饭,还作数吗?"(SYN+ACK)。你接到电话一脸懵:"什么约饭?我没打过这通电话啊。"于是你不会回复确认(不发第三个ACK)。朋友就知道这条留言是旧的、失效的,不会傻傻地做准备。
技术场景还原:
客户端发送了一个SYN请求,但因为网络延迟很久才到达服务端。客户端等不及,又发了一个SYN,这次正常建立了连接并完成了数据传输。
后来那个延迟的SYN终于到了服务端,服务端以为客户端又要建立连接,于是回复SYN+ACK并进入ESTABLISHED状态。但客户端根本不会再发第三个ACK------因为它根本没发这个请求。
结果就是:服务端白白维持了一个没人用的连接,浪费资源。这就是"失效的连接请求"问题。
三次握手确保了双方都确认了彼此的收发能力,避免了这种问题。
踩坑提醒:面试官如果问"为什么不是两次握手",你不仅要回答"防止失效连接请求",还要能举出具体的场景。光背结论不够,要能讲清楚。用这个"旧电话留言"的例子,面试官一听就懂。
问题与解答
Q1:三次握手中,如果第三次ACK丢失了会怎样?
A:服务端没有收到ACK,会重传SYN+ACK。客户端收到重传的SYN+ACK后,会再次发送ACK。连接最终还是会建立成功。客户端已经进入ESTABLISHED状态,可以直接发送数据。
Q2:SYN报文中的序列号为什么要随机?
A:防止序列号预测攻击。如果序列号可预测,攻击者可以伪造TCP报文,劫持连接。随机序列号让攻击者无法猜出合法的序列号。
六、TCP四次挥手
6.1 挥手过程详解
四次挥手是TCP断开连接的过程。因为TCP是全双工的,每个方向都需要单独关闭。
用挂电话类比来理解:
第一次挥手 = A说"我说完了"(FIN)
你和朋友打电话,你说:"我要说的都说完了。"但你还没挂电话,因为朋友可能还有话要说。这相当于客户端发送FIN,表示自己没有数据要发了。
第二次挥手 = B说"好的我知道了"(ACK)朋友说:"好的,我知道你说完了。"但他还没挂电话,因为他还有几句话要补充。这相当于服务端回复ACK,确认收到了客户端的FIN。
第三次挥手 = B说"我也说完了"(FIN)朋友把剩下的话说完,然后说:"好了,我也说完了,可以挂了。"这相当于服务端发送FIN,表示服务端也没有数据要发了。
第四次挥手 = A说"好的再见"(ACK)你说:"好的,再见。"然后等一会儿再挂电话(TIME_WAIT),确认朋友听到了你的"再见"。这相当于客户端回复ACK,然后等待2MSL再关闭。
客户端 服务端
| |
|--- 1. FIN (seq=u) ---------------->|
| 客户端进入 FIN_WAIT_1 状态 |
| |
|<-- 2. ACK (ack=u+1) ---------------|
| 服务端进入 CLOSE_WAIT 状态 |
| 客户端进入 FIN_WAIT_2 状态 |
| |
| (服务端继续发送未完成的数据) |
| |
|<-- 3. FIN (seq=w) -----------------|
| 服务端进入 LAST_ACK 状态 |
| |
|--- 4. ACK (ack=w+1) -------------->|
| 客户端进入 TIME_WAIT 状态 |
| 等待 2MSL 后关闭 |
| 服务端收到后直接关闭 |
| |
第一步:客户端发送FIN报文
- 标志位 FIN=1
- 序列号 seq=u
- 客户端进入 FIN_WAIT_1 状态
- 表示客户端没有数据要发送了
第二步:服务端回复ACK报文
- 标志位 ACK=1
- 确认号 ack=u+1
- 服务端进入 CLOSE_WAIT 状态
- 客户端进入 FIN_WAIT_2 状态
- 此时客户端到服务端的方向关闭了,但服务端到客户端的方向还开着
第三步:服务端发送FIN报文
- 服务端数据也发完了,发送FIN
- 服务端进入 LAST_ACK 状态
第四步:客户端回复ACK报文
- 客户端进入 TIME_WAIT 状态
- 等待 2MSL(最长报文段寿命,通常60秒)后关闭
- 服务端收到ACK后直接关闭
6.2 TIME_WAIT状态详解
TIME_WAIT状态持续 2MSL(约1~2分钟),这是面试的高频考点。
用等快递类比来理解TIME_WAIT:
你给朋友寄了最后一个包裹(发送最后一个ACK),然后你不能马上搬家(不能马上关闭连接),而是要等一段时间。
为什么?因为你要确认朋友确实收到了这个包裹。如果包裹在路上丢了,朋友会打电话来说"没收到"(重传FIN),你在家就能补发一个(重发ACK)。
如果你马上搬家(马上关闭连接),朋友打电话来说没收到,你已经不在原地了,他就永远等不到确认,只能干着急(服务端一直重传FIN,最终异常关闭)。
另外,等一段时间还能确保路上所有跟你有关的快递都送完了(网络中残留的报文消亡),不会影响到下一个住进来的人(新连接)。
为什么需要TIME_WAIT?
- 确保最后一个ACK能到达服务端。如果ACK丢失,服务端会重传FIN,客户端在TIME_WAIT期间可以重发ACK。
- 让网络中残留的报文消亡。等待2MSL可以确保本次连接的所有报文都从网络中消失,避免影响后续的新连接。
踩坑提醒 :在高并发短连接的服务器上,大量TIME_WAIT状态的连接会占用端口资源。可以通过调整内核参数(如
tcp_tw_reuse)来缓解,但不要随便关闭TIME_WAIT,否则可能导致连接异常。
6.3 为什么四次挥手不能是三次?
因为TCP是全双工的。客户端发FIN只是表示自己不发了,但服务端可能还有数据没发完。服务端需要先ACK确认,等数据发完后再发FIN。中间这段时间服务端可能还在传数据,所以ACK和FIN不能合并------这就是四次。
当然,如果服务端收到FIN时恰好也没有数据要发了,它可以把ACK和FIN合并成一个报文,这样就是三次挥手了。但协议设计上必须支持四次,因为大多数情况下两个方向不会同时关闭。
问题与解答
Q1:CLOSE_WAIT状态过多是什么原因?
A:CLOSE_WAIT表示对方已发FIN,但本方还没有调用 close() 关闭连接。通常是因为代码中没有正确关闭连接(比如忘记关闭数据库连接、HTTP连接没有释放等)。这是服务器端常见的Bug。
Q2:如果客户端直接断电(不发送FIN),服务端会怎样?
A:服务端不知道客户端已经断开,会一直保持ESTABLISHED状态。TCP有保活计时器(Keepalive Timer) ,默认2小时后发送探测报文,如果连续多次没有响应才关闭连接。可以通过 SO_KEEPALIVE 选项调整。
七、TCP可靠传输机制
7.1 确认应答
TCP每收到一个数据段,都会返回一个确认号(ACK),告诉发送方"我已经收到了这些数据,请继续发下一个"。
确认号的含义是:我期望收到的下一个字节的序列号。
比如发送方发了 seq=1, len=1000 的数据,接收方回复 ack=1001,表示前1000字节都收到了,请从1001开始发。
7.2 超时重传
如果发送方发出数据后,在规定时间内没有收到ACK,就会重传这个数据段。
超时时间(RTO,Retransmission Timeout)的设置很讲究:
- 太短:还没等到ACK就重传,造成不必要的重复
- 太长:等太久才重传,效率太低
TCP使用自适应算法动态调整RTO,基于测量到的往返时间RTT来计算:
RTO = SRTT + 4 * RTTVAR
其中 SRTT 是平滑RTT,RTTVAR 是RTT偏差。
7.3 序号与确认号
TCP对每个字节都编号,序列号是32位的,到达 2³²-1 后会回绕到0。
确认号机制保证了数据的按序到达。如果接收方收到了乱序的数据段,会缓存起来但不确认,等缺失的数据段到了再一起确认。
踩坑提醒:TCP的序列号是针对字节的,不是针对报文的!这一点很多人搞混。如果发送了两个报文,第一个 seq=1, len=500,第二个 seq=501, len=300,那么确认号应该是 ack=801(假设都收到了)。
问题与解答
Q1:如果ACK报文丢失了怎么办?
A:发送方超时后会重传数据段。接收方收到重复数据后,会再次发送ACK。所以偶尔的ACK丢失不会导致问题,只是多了一次重传。
Q2:TCP的序号回绕问题怎么解决?
A:32位序列号最大约43亿,在高速网络中可能几分钟就回绕。TCP通过**PAWS(Protection Against Wrapped Sequence Numbers)**机制,利用时间戳选项来区分新旧报文。
八、TCP流量控制
8.1 滑动窗口机制
流量控制的目的是防止发送方发太快,导致接收方缓冲区溢出 。核心机制是滑动窗口。
用工厂流水线类比来理解:
想象一个工厂的生产线:
- 发送方 = 上游工厂,不断生产产品(数据)往下游送
- 接收方 = 下游仓库,负责接收和暂存产品
- 缓冲区 = 仓库的容量,仓库满了就装不下了
下游仓库每隔一段时间告诉上游:"我的仓库还能装500件货"(窗口大小 = 500)。上游就根据这个信息决定发多少货,不会一股脑把所有货都倒过去。
如果下游处理得快,仓库腾出了空间,就会通知上游:"现在能装1000件了"(窗口增大)。
如果下游处理不过来,仓库快满了,就会通知上游:"只能再装100件了"(窗口缩小)。
如果仓库彻底满了,就会说:"暂停发货!"(窗口 = 0),上游就必须停下来等。
接收方在ACK报文中携带窗口大小(Window Size),告诉发送方"我还能接收多少字节的数据"。
发送方缓冲区:
[已确认] [已发送未确认] [可以发送] [不能发送]
<-- 滑动窗口 -->
窗口大小是动态变化的:
- 接收方处理完数据,缓冲区腾出空间 → 窗口增大
- 接收方来不及处理,缓冲区快满了 → 窗口缩小
- 窗口为0时,发送方必须停止发送
8.2 零窗口探测
当接收方发送窗口为0(Win=0)时,发送方必须停止发送数据。但发送方会启动一个持续计时器 ,定期发送零窗口探测报文(1字节),询问接收方窗口是否已经打开。
如果接收方回复的窗口仍然是0,发送方继续等待。如果窗口打开了,就可以继续发送数据。
踩坑提醒:有一种叫"糊涂窗口综合征"(Silly Window Syndrome)的问题------接收方缓冲区只剩一点点空间就通知发送方,发送方就发一点点数据,导致网络效率极低。解决方案是:接收方等缓冲区有足够空间(比如一半或MSS)才更新窗口;发送方等积累足够数据(Nagle算法)再发送。
问题与解答
Q1:滑动窗口和拥塞窗口有什么区别?
A:滑动窗口(rwnd)是接收方告知的,反映接收方的处理能力;拥塞窗口(cwnd)是发送方自己估算的,反映网络的拥塞程度。发送方实际发送窗口 = min(rwnd, cwnd)。
Q2:Nagle算法是什么?
A:Nagle算法要求发送方在以下两个条件满足之一时才发送数据:①积累了一个MSS大小的数据;②收到了之前发送数据的ACK。目的是减少小包的数量,提高网络利用率。但某些场景(如游戏)需要禁用Nagle算法(设置 TCP_NODELAY)。
九、TCP拥塞控制四大算法
拥塞控制是TCP最重要的机制之一,也是面试必考内容。拥塞控制和流量控制不同:流量控制是端到端的(保护接收方),拥塞控制是全局性的(保护网络)。
用开车堵车类比来理解拥塞控制:
TCP发送数据就像在高速公路上开车。你(发送方)想知道这条路能跑多快,但又不想造成拥堵。于是你采取了一系列策略来试探路况、应对拥堵。
9.1 慢启动(Slow Start)------慢慢加速试探路况
连接刚建立时,发送方不知道网络的承载能力,所以慢慢探测:
初始:cwnd = 1 MSS
规则:每收到一个ACK,cwnd += 1 MSS
效果:cwnd 指数增长(1 → 2 → 4 → 8 → 16 ...)
虽然叫"慢启动",但其实增长速度是指数级的,一点都不慢。就像你刚上高速,从20km/h开始,每次加速都翻倍(20→40→80→160),很快就飙起来了。之所以叫"慢启动",是相对于一开始就飙到200km/h来说的。
堵车类比:你刚进入一条陌生的高速公路,不知道路况如何。于是你慢慢加速,观察车流量。如果一路畅通,你就越开越快(指数增长)。
9.2 拥塞避免(Congestion Avoidance)------保持匀速
当 cwnd 达到**慢启动阈值(ssthresh)**后,切换为拥塞避免算法:
规则:每经过一个RTT,cwnd += 1 MSS
效果:cwnd 线性增长(每个RTT只增加1个MSS)
线性增长比指数增长慢得多,这就是"避免"的含义------小心地探测网络极限。
堵车类比:你加速到某个速度后(比如100km/h),觉得再快可能就要追尾了,于是改为每次只加一点点速度(线性增长),小心翼翼地试探这条路的极限。
9.3 快重传(Fast Rettransmit)------发现事故立即变道
当发送方连续收到 3个重复ACK 时,不等待超时,立即重传丢失的报文段。
发送方:发送 seq=1, 2, 3, 4, 5
接收方:收到1, 2, 4, 5(3丢失了)
接收方:发送 ack=3, ack=3, ack=3(三个重复ACK)
发送方:收到3个重复ACK → 立即重传 seq=3
快重传的前提是接收方收到乱序数据时必须立即发送重复ACK,不能等。
堵车类比 :你开车时,前面3辆车都闪灯提示你"前面有事故"(3个重复ACK)。你不需要等到导航提示"前方拥堵"(超时),而是立即变道绕行(立即重传)。这样反应更快,不会傻等。
9.4 快恢复(Fast Recovery)------事故处理后恢复速度
快恢复和快重传配合使用。当触发快重传后:
ssthresh = cwnd / 2
cwnd = ssthresh(而不是回到1!)
然后直接进入拥塞避免阶段(线性增长)
快恢复的"快"体现在:不回到慢启动(cwnd=1),而是从减半后的值开始线性增长,恢复速度更快。
堵车类比:你变道绕过了事故(快重传),但没有必要回到起点重新加速(cwnd=1)。你把速度降到原来的一半(cwnd减半),然后继续保持这个速度行驶(拥塞避免)。因为你已经知道这条路大致能跑多快了,不需要从零开始试探。
9.5 cwnd变化过程图
用文字描述一下完整的cwnd变化过程:
cwnd
│ *
│ * * ← 拥塞避免(线性增长)
│ *
│ *
│ *
│ *
│ * ← 达到ssthresh,切换为线性增长
│ *
│ *
│ *
│ * ← 慢启动(指数增长)
└────────────────────────────────── RTT
当发生超时重传时:
ssthresh = cwnd / 2
cwnd = 1
重新慢启动
当触发快重传(3个重复ACK)时:
ssthresh = cwnd / 2
cwnd = ssthresh
进入快恢复,然后拥塞避免
| 事件 | ssthresh | cwnd |
|---|---|---|
| 超时重传 | cwnd/2 | 1(重新慢启动) |
| 快重传(3重复ACK) | cwnd/2 | ssthresh(快恢复) |
踩坑提醒:面试中经常问"超时重传和快重传的区别"。关键区别在于cwnd的处理:超时重传回到1重新慢启动(说明网络严重拥塞),快重传只减半然后线性增长(说明网络轻度拥塞,还有报文在流通)。
问题与解答
Q1:慢启动真的是"慢"的吗?
A:不是。慢启动阶段cwnd是指数增长的(每个RTT翻倍),1→2→4→8→16...增长速度非常快。叫"慢启动"只是相对于一开始就发送大量数据而言的。
Q2:为什么快重传需要3个重复ACK而不是1个?
A:因为1个重复ACK可能只是报文乱序到达,不代表丢失。2个重复ACK也可能是巧合。连续3个重复ACK基本可以确定中间的报文确实丢失了。这是经过实践验证的阈值。
十、TCP vs UDP核心对比
用快递服务类比来理解TCP和UDP:
TCP = 顺丰快递(保价、签收、追踪)
寄件前要先下单预约(三次握手)
每件快递都有单号,可以追踪(序列号、确认号)
如果快递丢了,顺丰会免费补发(超时重传)
收件人要签字确认(ACK确认)
顺丰会根据收件人的仓库容量调整发货速度(流量控制)
如果某条路线堵车,顺丰会换路线或暂缓发货(拥塞控制)
优点:安全可靠,东西一定不会丢
缺点:贵、慢,手续多
UDP = 普通平邮(便宜、快、不保证送达)不需要预约,直接丢进邮筒就行(无连接)
没有单号,寄出去就不管了(无确认、无重传)
如果信丢了,邮局不负责(不可靠)
优点:便宜、快、省事
缺点:信可能丢、可能迟到、可能乱序
类比总结:你要寄一份重要合同,选顺丰(TCP);你要发一张明信片,选平邮(UDP)。
| 对比维度 | TCP | UDP |
|---|---|---|
| 连接方式 | 面向连接(三次握手) | 无连接 |
| 可靠性 | 可靠传输(确认、重传) | 不可靠传输(尽最大努力) |
| 传输方式 | 面向字节流 | 面向报文 |
| 通信模式 | 一对一(全双工) | 一对一、一对多、多对多 |
| 头部大小 | 最小20字节 | 固定8字节 |
| 传输效率 | 较低(有确认和重传开销) | 高(无额外开销) |
| 流量控制 | 有(滑动窗口) | 无 |
| 拥塞控制 | 有(慢启动、拥塞避免等) | 无 |
| 适用场景 | 文件传输、网页、邮件 | 视频/音频、游戏、DNS |
选择建议:需要可靠性选TCP,需要实时性选UDP。但别忘了,很多现代协议在UDP之上实现了可靠性(如QUIC、WebRTC),所以这个界限也在模糊。
问题与解答
Q1:HTTP用TCP还是UDP?
A:传统HTTP/1.1和HTTP/2都基于TCP。但HTTP/3基于QUIC协议,而QUIC基于UDP,所以HTTP/3实际上跑在UDP上。这是一个很好的知识点,面试时提出来能加分。
Q2:TCP和UDP可以同时使用同一个端口吗?
A:可以。TCP和UDP的端口号空间是独立的。TCP的80端口和UDP的80端口互不干扰,因为它们在操作系统中是不同的套接字。
十一、新手常见误区
学完传输层,很多新手容易在以下几个概念上踩坑。这里集中梳理一下,帮你提前避雷。
误区1:认为TCP的"可靠传输"意味着数据100%不会丢失
错误理解:"TCP是可靠传输,所以用TCP发送数据绝对不会丢。"
正确理解 :TCP的"可靠"是指它会尽力保证数据不丢失------通过确认、重传、按序交付等机制。但如果网络彻底断开(比如网线被拔了),TCP也会无能为力。TCP的可靠是"在连接正常的前提下尽力而为",不是"绝对保证"。
踩坑提醒:有些同学写代码时完全依赖TCP的可靠性,不做任何应用层的容错处理。实际上,TCP连接可能异常断开、可能长时间阻塞,应用层仍然需要处理超时、重试、断线重连等逻辑。
误区2:把"三次握手"和"四次挥手"的过程搞混
错误理解:"连接建立需要三次,断开也应该三次吧?为什么断开要四次?"
正确理解:
- 三次握手:双方只需要确认"彼此都能收发",所以第二次握手把SYN和ACK合并了,省了一次交互。
- 四次挥手:TCP是全双工的,客户端发FIN只表示"我不发了",但服务端可能还有数据要发。所以ACK和FIN不能合并,必须分两次发送,就成了四次。
一句话记住:握手时双方"同时准备好",可以合并;挥手时双方"不同时说完",不能合并。
踩坑提醒:面试高频考点!记住这个核心区别:三次握手第二次可以合并(SYN+ACK),是因为双方同时进入"准备通信"状态;四次挥手中间不能合并,是因为服务端可能还有数据要发。
误区3:认为UDP完全不能用,TCP一定比UDP好
错误理解:"UDP不可靠,所以实际开发中都应该用TCP。"
正确理解:UDP的"不可靠"恰恰是它的优势。对于视频通话、直播、在线游戏等场景,实时性比完整性更重要。TCP的重传机制会导致延迟越来越大(比如视频卡顿后越卡越久),而UDP丢几个包只是画面稍微模糊一下,不会累积延迟。
踩坑提醒:现代很多高性能协议(如QUIC、WebRTC)都是基于UDP实现的。选择TCP还是UDP,要看业务需求:要可靠性选TCP,要实时性选UDP。没有绝对的好坏。
误区4:混淆流量控制和拥塞控制
错误理解:"流量控制和拥塞控制都是控制发送速度,作用一样吧?"
正确理解:
- 流量控制(滑动窗口) :是端到端 的控制,防止发送方发太快,导致接收方缓冲区溢出。窗口大小由接收方告知(rwnd)。
- 拥塞控制 :是全局性 的控制,防止发送方发太快,导致网络拥塞。窗口大小由发送方根据网络状况自己估算(cwnd)。
类比:流量控制是"下游仓库装不下了,让上游慢点发";拥塞控制是"高速路上堵车了,所有车都慢点开"。
发送方实际发送窗口 = min(rwnd, cwnd),取两者中较小的一个。
踩坑提醒 :面试常问"流量控制和拥塞控制的区别"。记住:流量控制保护接收方 ,拥塞控制保护网络。两者独立工作,最终发送窗口取两者最小值。
十二、面试高频考点汇总
面试题1:详细描述TCP三次握手的过程
答:
- 第一次握手:客户端发送SYN报文,SYN=1,seq=x(随机生成),客户端进入SYN_SENT状态
- 第二次握手:服务端收到SYN后,回复SYN+ACK报文,SYN=1, ACK=1, seq=y(随机生成),ack=x+1,服务端进入SYN_RCVD状态
- 第三次握手:客户端收到SYN+ACK后,发送ACK报文,ACK=1, seq=x+1, ack=y+1,双方进入ESTABLISHED状态
三次握手的本质是确认双方的发送和接收能力都正常。第一次握手服务端确认了客户端的发送能力;第二次握手客户端确认了服务端的发送和接收能力;第三次握手服务端确认了客户端的接收能力。
面试题2:详细描述TCP四次挥手的过程,以及TIME_WAIT的作用
答:
- 第一次挥手:客户端发送FIN,seq=u,进入FIN_WAIT_1状态
- 第二次挥手:服务端回复ACK,ack=u+1,进入CLOSE_WAIT状态,客户端进入FIN_WAIT_2状态
- 第三次挥手:服务端发送FIN,seq=w,进入LAST_ACK状态
- 第四次挥手:客户端回复ACK,ack=w+1,进入TIME_WAIT状态,等待2MSL后关闭
TIME_WAIT的作用:
- 确保最后一个ACK能到达服务端。如果ACK丢失,服务端重传FIN,客户端可以重发ACK
- 等待2MSL让网络中残留的报文消亡,避免影响后续新连接
面试题3:详细描述TCP拥塞控制的四大算法
答:
- 慢启动:cwnd从1开始,每收到一个ACK就加1(指数增长),直到达到ssthresh
- 拥塞避免:cwnd达到ssthresh后,每个RTT只增加1个MSS(线性增长),小心探测
- 快重传:收到3个重复ACK时,立即重传丢失的报文段,不等待超时
- 快恢复:快重传后,ssthresh设为cwnd/2,cwnd设为ssthresh,直接进入拥塞避免
超时重传 时:ssthresh = cwnd/2,cwnd = 1,重新慢启动
快重传时:ssthresh = cwnd/2,cwnd = ssthresh,进入快恢复
面试题4:TCP和UDP的区别是什么?各自适用什么场景?
答 :
TCP是面向连接的可靠传输协议,提供确认重传、流量控制、拥塞控制、按序交付等服务。适用于对数据完整性要求高的场景:文件传输(FTP)、网页浏览(HTTP)、邮件(SMTP/POP3)、数据库连接。
UDP是无连接的不可靠传输协议,轻量高效,延迟低。适用于对实时性要求高的场景:视频通话、直播、在线游戏、DNS查询、SNMP。
关键区别:TCP面向字节流,UDP面向报文;TCP有拥塞控制和流量控制,UDP没有;TCP一对一通信,UDP支持多播/广播;TCP头部20字节,UDP头部8字节。
面试题5:为什么三次握手不能是两次?为什么需要TIME_WAIT?
答 :
不能是两次的原因:防止失效的连接请求建立连接。如果只有两次握手,一个延迟到达的旧SYN请求会导致服务端建立无效连接,浪费资源。三次握手确保了客户端最后发送ACK确认,服务端只有在收到ACK后才会建立连接。
需要TIME_WAIT的原因:
- 确保最后一个ACK能到达服务端。如果ACK丢失,服务端会重传FIN,TIME_WAIT状态下的客户端可以重发ACK
- 让本次连接的所有报文在网络中消亡(2MSL时间),避免残留报文影响新连接
十三、模拟测试题
选择题/填空题
1. TCP三次握手中,第二次握手服务端发送的报文标志位是( )。
2. 以下哪个协议使用UDP作为传输层协议?
- A. HTTP
- B. FTP
- C. DNS
- D. SMTP
3. TCP拥塞控制中,当检测到超时时,cwnd被设为( ),ssthresh被设为( );当收到3个重复ACK时,cwnd被设为( ),进入快恢复阶段。
4. TCP四次挥手中,主动关闭方发送第一个FIN后进入( )状态,收到对方的ACK后进入( )状态,发送最后一个ACK后进入( )状态。
5. UDP头部大小为( )字节,TCP头部最小为( )字节。端口号的取值范围是( )到( )。
参考答案
1. SYN=1, ACK=1(即SYN+ACK报文)。
2. C。DNS默认使用UDP协议(端口53),在响应数据过大时才切换到TCP。HTTP、FTP、SMTP都基于TCP。
3. 1;cwnd/2;ssthresh(即cwnd/2)。超时重传时cwnd归1重新慢启动,快重传时cwnd减半后进入快恢复。
4. FIN_WAIT_1;FIN_WAIT_2;TIME_WAIT。
5. 8;20;0;65535。
互动话题
你在面试中遇到过哪些关于TCP的"刁钻"问题?或者在实际开发中有没有因为TCP的某些特性踩过坑(比如TIME_WAIT过多、粘包问题等)?欢迎在评论区分享,大家一起讨论!
参考资料
- RFC 793 - Transmission Control Protocol (TCP)
- TCP/IP详解 卷1:协议 - W. Richard Stevens
- Wireshark官方文档 - TCP分析
本文为计算机网络系列教学文章第4篇,下一篇我们将进入应用层,详细讲解HTTP、DNS、FTP等应用层协议。敬请期待!