TCP三次握手和四次挥手详解
2025-7-7
前言
今天使用WireShark抓包工具来深入分析TCP网络通信的详细过程。通过实际的数据包捕获,我们将完整展示TCP三次握手和四次挥手的全流程。
WireShark过滤器配置
使用以下过滤关键词来捕获TCP连接相关的数据包:
(tcp.flags.syn == 1 || tcp.flags.fin == 1 || tcp.stream eq 0 || tcp.flags.syn == 1 or tcp.flags.fin == 1 ||tcp.flags.reset == 1 ||tcp.flags.ack == 1 || tcp.len > 0 ) and tcp.port ==8090
过滤器解析:
tcp.flags.syn == 1
: 捕获SYN包tcp.flags.fin == 1
: 捕获FIN包tcp.flags.reset == 1
: 捕获RST包tcp.flags.ack == 1
: 捕获ACK包tcp.len > 0
: 捕获包含数据的包tcp.port == 8090
: 限定端口8090
完整数据包日志
以下是捕获到的完整TCP通信过程:
104 16.836236 127.0.0.1 → 127.0.0.1 TCP 68 65297 → 8090 [SYN] Seq=0 Win=65535 Len=0 105 16.836315 127.0.0.1 → 127.0.0.1 TCP 68 8090 → 65297 [SYN, ACK] Seq=0 Ack=1 Win=65535 Len=0 106 16.836324 127.0.0.1 → 127.0.0.1 TCP 56 65297 → 8090 [ACK] Seq=1 Ack=1 Win=408256 Len=0 107 16.836330 127.0.0.1 → 127.0.0.1 TCP 56 8090 → 65297 [ACK] Seq=1 Ack=1 Win=408256 Len=0 108 16.836772 127.0.0.1 → 127.0.0.1 TCP 61 65297 → 8090 [PSH, ACK] Seq=1 Ack=1 Win=408256 Len=5 109 16.836892 127.0.0.1 → 127.0.0.1 TCP 56 8090 → 65297 [ACK] Seq=1 Ack=6 Win=408256 Len=0 110 16.836992 127.0.0.1 → 127.0.0.1 TCP 62 8090 → 65297 [PSH, ACK] Seq=1 Ack=6 Win=408256 Len=6 111 16.837015 127.0.0.1 → 127.0.0.1 TCP 56 65297 → 8090 [ACK] Seq=6 Ack=7 Win=408256 Len=0 112 16.837073 127.0.0.1 → 127.0.0.1 TCP 56 65297 → 8090 [FIN, ACK] Seq=6 Ack=7 Win=408256 Len=0 113 16.837093 127.0.0.1 → 127.0.0.1 TCP 56 8090 → 65297 [ACK] Seq=7 Ack=7 Win=408256 Len=0 114 16.837147 127.0.0.1 → 127.0.0.1 TCP 62 8090 → 65297 [PSH, ACK] Seq=7 Ack=7 Win=408256 Len=6 115 16.837181 127.0.0.1 → 127.0.0.1 TCP 44 65297 → 8090 [RST] Seq=7 Win=0 Len=0
1. TCP三次握手详细分析
1.1 第一次握手 (SYN)
104 16.836236 127.0.0.1 → 127.0.0.1 TCP 68 65297 → 8090 [SYN] Seq=0 Win=65535 Len=0
数据包104分析:
- 方向:127.0.0.1:65297 → 127.0.0.1:8090
- 标志:[SYN]
- 作用:客户端发送SYN包,请求建立连接
- 序列号:Seq=0,表示初始序列号
- 窗口大小:Win=65535,表示接收窗口大小
- MSS:16344,表示最大段大小
1.2 第二次握手 (SYN+ACK)
105 16.836315 127.0.0.1 → 127.0.0.1 TCP 68 8090 → 65297 [SYN, ACK] Seq=0 Ack=1 Win=65535 Len=0
数据包105分析:
- 方向:127.0.0.1:8090 → 127.0.0.1:65297
- 标志:[SYN, ACK]
- 作用:服务器响应SYN+ACK包
- 序列号:Seq=0 Ack=1,确认收到客户端的SYN包
- 窗口大小:Win=65535,服务器的接收窗口大小
1.3 第三次握手 (ACK)
106 16.836324 127.0.0.1 → 127.0.0.1 TCP 56 65297 → 8090 [ACK] Seq=1 Ack=1 Win=408256 Len=0
数据包106分析:
- 方向:127.0.0.1:65297 → 127.0.0.1:8090
- 标志:[ACK]
- 作用:客户端发送ACK包,确认连接建立
- 序列号:Seq=1 Ack=1,确认收到服务器的SYN+ACK包
- 状态:此时TCP连接正式建立
2. TCP连接维护阶段
2.1 窗口更新 (数据包107)
107 16.836330 127.0.0.1 → 127.0.0.1 TCP 56 8090 → 65297 [ACK] Seq=1 Ack=1 Win=408256 Len=0
数据包107分析:
- 方向:服务器 → 客户端
- 标志:[ACK]
- 作用:TCP窗口更新
- 窗口变化:从初始的65535字节扩大到408256字节
- 意义:允许客户端发送更多数据,提高传输效率
2.2 客户端数据传输 (数据包108)
108 16.836772 127.0.0.1 → 127.0.0.1 TCP 61 65297 → 8090 [PSH, ACK] Seq=1 Ack=1 Win=408256 Len=5
数据包108分析:
- 方向:客户端 → 服务器
- 标志:[PSH, ACK]
- 数据长度:Len=5
- PSH标志:表示”立即推送这些数据给应用程序”
- 数据内容:通过十六进制分析,发送的是”study”
数据解析过程:

十六进制: 73 74 75 64 79
ASCII转换:
73 = 's'
74 = 't'
75 = 'u'
64 = 'd'
79 = 'y'
结果: "study"
2.3 服务器数据确认 (数据包109)
109 16.836892 127.0.0.1 → 127.0.0.1 TCP 56 8090 → 65297 [ACK] Seq=1 Ack=6 Win=408256 Len=0
数据包109分析:
- 方向:服务器 → 客户端
- 标志:[ACK]
- 作用:确认数据接收
- 序列号变化:Ack=6表示期望下一个数据包的序列号从6开始
- 窗口维持:Win=408256字节
2.4 服务器响应数据 (数据包110)
110 16.836992 127.0.0.1 → 127.0.0.1 TCP 62 8090 → 65297 [PSH, ACK] Seq=1 Ack=6 Win=408256 Len=6
数据包110分析:
- 方向:服务器 → 客户端
- 标志:[PSH, ACK]
- 数据长度:Len=6
- 作用:服务器发送响应数据
数据解析过程:

原始十六进制数据:
02 00 00 00 45 00 00 3a 00 00 40 00 40 06 00 00 7f 00 00 01 7f 00 00 01 1f 9a ff 11 bd 77 4d c9 a8 0a cc 93 80 18 18 eb fe 2e 00 00 01 01 08 0a 27 84 1a 14 b2 e5 29 71 e4 bd a0 e5 a5 bd
数据包结构解析
IP头部(前20字节):
02 00 00 00
: 可能是以太网帧头部分45
: IP版本4,头部长度5×4=20字节00 00 3a
: 总长度58字节40 00
: 标志位和片偏移40 06
: TTL=64,协议=6(TCP)7f 00 00 01
: 源IP 127.0.0.17f 00 00 01
: 目标IP 127.0.0.1
TCP头部:
1f 9a
: 源端口8090ff 11
: 目标端口65297bd 77 4d c9
: 序列号a8 0a cc 93
: 确认号80 18
: 头部长度和标志位(PSH+ACK)18 eb
: 窗口大小fe 2e
: 校验和
TCP选项:
01 01 08 0a 27 84 1a 14 b2 e5 29 71
: TCP时间戳选项
应用数据内容: 关键部分 – 实际数据载荷:
e4 bd a0 e5 a5 bd
转换为UTF-8字符:
e4 bd a0
= 你e5 a5 bd
= 好
结论: 这个数据包发送的中文内容是:”你好”
2.5 客户端确认响应 (数据包111)
111 16.837015 127.0.0.1 → 127.0.0.1 TCP 56 65297 → 8090 [ACK] Seq=6 Ack=7 Win=408256 Len=0
数据包111分析:
- 方向:客户端 → 服务器
- 标志:[ACK]
- 作用:确认服务器响应
- 序列号:Ack=7表示期望下一个数据包从序列号7开始
- 完成:一次完整的数据交换确认
3. TCP四次挥手分析
3.1 第一次挥手 (FIN)
112 16.837073 127.0.0.1 → 127.0.0.1 TCP 56 65297 → 8090 [FIN, ACK] Seq=6 Ack=7 Win=408256 Len=0
数据包112分析:
- 方向:客户端 → 服务器
- 标志:[FIN, ACK]
- 作用:客户端发送FIN包,请求关闭连接
- 序列号:Seq=6 Ack=7
3.2 第二次挥手 (ACK)
113 16.837093 127.0.0.1 → 127.0.0.1 TCP 56 8090 → 65297 [ACK] Seq=7 Ack=7 Win=408256 Len=0
数据包113分析:
- 方向:服务器 → 客户端
- 标志:[ACK]
- 作用:服务器发送ACK包,确认收到FIN请求
- 序列号:Seq=7 Ack=7
3.3 第三次挥手 (FIN)
114 16.837147 127.0.0.1 → 127.0.0.1 TCP 62 8090 → 65297 [PSH, ACK] Seq=7 Ack=7 Win=408256 Len=6
数据包114分析:
- 方向:服务器 → 客户端
- 标志:[PSH, ACK]
- 作用:服务器发送自己的FIN包,请求关闭连接
- 序列号:Seq=7 Ack=7
3.4 异常关闭 (RST)
115 16.837181 127.0.0.1 → 127.0.0.1 TCP 44 65297 → 8090 [RST] Seq=7 Win=0 Len=0
数据包115分析:
- 方向:客户端 → 服务器
- 标志:[RST]
- 异常情况:客户端发送RST包而不是正常的ACK包
- 原因:可能是因为客户端强制关闭连接
4. 测试代码
4.1 服务端代码
python import socket socket_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) print(f'socket对象:{socket}') socket_server.bind(('127.0.0.1', 8090)) socket_server.listen(5) socket_client, client_info = socket_server.accept() while True: recv = socket_client.recv(1024) print(f'服务端接收到资源:{recv}') socket_client.send('你好'.encode()) socket_server.close()
4.2 客户端代码
python import socket socket_client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) print(f'socket对象:{socket}') socket_client.connect(('127.0.0.1', 8090)) socket_client.send('study'.encode('utf-8')) recv = socket_client.recv(1024) print(f'接受数据:{recv.decode()}') socket_client.close()
5. 深入理解:为什么需要序列号(Seq)?
5.1 保证数据顺序
没有序列号的问题:
发送方: [数据1] [数据2] [数据3]
网络传输: 可能乱序到达
接收方: [数据3] [数据1] [数据2] ❌ 错误顺序
有序列号的解决方案:
发送方: [数据1-Seq=100] [数据2-Seq=150] [数据3-Seq=200]
网络传输: 可能乱序到达
接收方: [数据3-Seq=200] [数据1-Seq=100] [数据2-Seq=150]
重组后: [数据1] [数据2] [数据3] ✓ 正确顺序
5.2 检测数据丢失
发送方发送: Seq=100(50字节) → Seq=150(50字节) → Seq=200(50字节)
接收方收到: Seq=100 → Seq=200 (发现缺少Seq=150!)
接收方响应: "我收到了100和200,但是150丢了,请重发"
5.3 防止重复数据
网络延迟导致重复:
发送方: 发送Seq=100 → 超时重发Seq=100
接收方: 收到两个Seq=100 → 丢弃重复的包
5.4 实现可靠传输
发送方: 发送Seq=100-149 (50字节)
接收方: 发送Ack=150 (确认收到100-149,期望150开始)
发送方: 知道数据已安全到达,可以发送下一批数据
6. 关键观察点总结
- 本地回环连接:所有通信都在127.0.0.1上进行
- 端口使用:客户端使用临时端口65297,服务器监听8090端口
- 异常关闭:最后一个包是RST而不是正常的ACK
- 窗口管理:可以看到TCP窗口大小的动态调整过程
- 序列号追踪:序列号和确认号的递增显示了数据传输的顺序
这个抓包展示了一个完整的TCP连接生命周期,但最后以RST异常终止而不是正常的四次挥手完成。通过这个实际案例,我们可以深入理解TCP协议的工作原理和数据传输机制。
TCP和QUIC面试题大全
第六部分:常见面试题深度解析
TCP的三次握手和四次挥手是网络编程面试中的高频考点。以下结合我们的实际抓包数据,深入分析常见面试题:
三次握手相关面试题
1. 基础概念题
Q1:什么是TCP三次握手?请详细描述三次握手的过程
基于抓包数据的标准答案:
从我们的抓包数据可以看到完整的三次握手过程:
# 第一次握手 (SYN)
104 16.836236 127.0.0.1 → 127.0.0.1 TCP 68 65297 → 8090 [SYN] Seq=0 Win=65535 Len=0
# 第二次握手 (SYN+ACK)
105 16.836315 127.0.0.1 → 127.0.0.1 TCP 68 8090 → 65297 [SYN, ACK] Seq=0 Ack=1 Win=65535 Len=0
# 第三次握手 (ACK)
106 16.836324 127.0.0.1 → 127.0.0.1 TCP 56 65297 → 8090 [ACK] Seq=1 Ack=1 Win=408256 Len=0
详细过程:
- 第一次握手:客户端发送SYN包(Seq=0),状态变为SYN_SENT
- 第二次握手:服务器回复SYN+ACK包(Seq=0, Ack=1),状态变为SYN_RCVD
- 第三次握手:客户端发送ACK包(Seq=1, Ack=1),状态变为ESTABLISHED
Q2:为什么需要三次握手?两次握手不行吗?
实际案例分析: 观察数据包104-106,我们看到:
- 包104:客户端证明自己能发送
- 包105:服务器证明自己能接收和发送
- 包106:客户端证明自己能接收
如果只有两次握手,客户端无法确认服务器是否收到了自己的确认,可能导致:
- 旧的连接请求在网络中延迟到达
- 服务器误认为是新的连接请求
- 造成资源浪费和连接混乱
Q3:三次握手中的SYN、ACK、seq、ack字段分别代表什么?
基于抓包数据解释:
从包104看到:
- SYN=1:同步序列号标志,表示请求建立连接
- Seq=0:客户端的初始序列号
- ACK=0:此时还没有确认信息
从包105看到:
- SYN=1, ACK=1:同时设置同步和确认标志
- Seq=0:服务器的初始序列号
- Ack=1:确认客户端的序列号+1
2. 深入理解题
Q4:第三次握手失败了会怎么样?
基于抓包分析: 如果包106丢失,会出现:
- 客户端认为连接已建立(发送了ACK)
- 服务器仍在SYN_RCVD状态(没收到ACK)
- 客户端发送数据时,服务器会回复RST包
- 类似我们抓包中包115的RST情况
Q5:什么是SYN洪水攻击?如何防范?
攻击原理: 恶意客户端大量发送SYN包(类似包104),但不完成第三次握手,导致服务器:
- 半连接队列被占满
- 无法处理正常连接请求
防范措施:
# 调整半连接队列大小
net.ipv4.tcp_max_syn_backlog = 2048
# 启用SYN Cookie
net.ipv4.tcp_syncookies = 1
# 减少SYN+ACK重传次数
net.ipv4.tcp_synack_retries = 2
3. 状态变迁题
Q6:描述TCP连接建立过程中客户端和服务端的状态变化
基于抓包时间线分析:
时间轴:16.836236 → 16.836315 → 16.836324
客户端状态变化:
CLOSED → SYN_SENT(发送包104) → ESTABLISHED(发送包106)
服务器状态变化:
LISTEN → SYN_RCVD(发送包105) → ESTABLISHED(收到包106)
四次挥手相关面试题
1. 基础概念题
Q7:什么是TCP四次挥手?请详细描述四次挥手的过程
基于抓包数据分析:
# 第一次挥手 (FIN)
112 16.837073 127.0.0.1 → 127.0.0.1 TCP 56 65297 → 8090 [FIN, ACK] Seq=6 Ack=7 Win=408256 Len=0
# 第二次挥手 (ACK)
113 16.837093 127.0.0.1 → 127.0.0.1 TCP 56 8090 → 65297 [ACK] Seq=7 Ack=7 Win=408256 Len=0
# 第三次挥手 (FIN) - 包114实际发送了数据
114 16.837147 127.0.0.1 → 127.0.0.1 TCP 62 8090 → 65297 [PSH, ACK] Seq=7 Ack=7 Win=408256 Len=6
# 第四次挥手异常 (RST)
115 16.837181 127.0.0.1 → 127.0.0.1 TCP 44 65297 → 8090 [RST] Seq=7 Win=0 Len=0
注意:我们的抓包显示了一个异常情况,最后是RST而不是正常的ACK。
Q8:为什么连接的建立是三次握手,而关闭却是四次挥手?
关键区别分析:
- 建立连接:SYN和ACK可以合并在一个包中发送(包105)
- 关闭连接:FIN和ACK通常需要分开发送,因为:
- 收到FIN后需要立即ACK确认(包113)
- 但可能还有数据要发送(包114发送了6字节数据)
- 发送完所有数据后才能发送自己的FIN
2. TIME_WAIT相关题
Q9:什么是TIME_WAIT状态?为什么需要TIME_WAIT?
我们抓包中的异常情况: 包115显示连接被RST强制关闭,如果是正常关闭:
- 客户端发送最后的ACK后进入TIME_WAIT
- 持续2MSL(Maximum Segment Lifetime)时间
- 确保对方收到最后的ACK
TIME_WAIT的作用:
- 确保最后的ACK能够到达:如果ACK丢失,对方会重发FIN
- 防止旧连接的数据包干扰新连接:等待网络中旧数据包消失
Q10:TIME_WAIT状态过多会有什么问题?如何解决?
问题分析:
- 每个TIME_WAIT占用一个端口(如我们例子中的65297)
- 大量TIME_WAIT可能导致端口耗尽
- 内存资源占用
解决方案:
# 启用TIME_WAIT重用
net.ipv4.tcp_tw_reuse = 1
# 调整TIME_WAIT超时时间
net.ipv4.tcp_fin_timeout = 30
# 使用SO_REUSEADDR选项
socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
3. 异常情况题
Q11:什么是RST包?什么时候会发送RST包?
基于包115的RST分析:
115 16.837181 127.0.0.1 → 127.0.0.1 TCP 44 65297 → 8090 [RST] Seq=7 Win=0 Len=0
RST包的特点:
- Win=0:窗口大小为0
- 立即关闭连接,不进入TIME_WAIT状态
- 表示连接异常终止
发送RST的常见情况:
- 端口未监听:连接到未开放的端口
- 连接异常:应用程序崩溃
- 协议错误:收到意外的数据包
- 强制关闭:应用程序强制关闭连接
四、TCP vs QUIC对比题
基础对比题
Q11: TCP的三次握手和QUIC的握手有什么区别?
TCP三次握手:
- 需要3个RTT(包括TLS握手)
- 顺序:TCP握手 → TLS握手 → 数据传输
QUIC握手:
- 首次连接:1-RTT
- 重复连接:0-RTT
- 集成:传输层和安全层集成
Q12: 为什么QUIC可以实现0-RTT连接建立?
QUIC 0-RTT的实现:
- 会话恢复:复用之前的加密参数
- 预共享密钥:使用PSK(Pre-Shared Key)
- 风险控制:限制0-RTT数据的类型,防止重放攻击
性能对比题
Q13: 在连接建立速度上,QUIC相比TCP+TLS有什么优势?
TCP + TLS握手时间:
TCP三次握手: 1 RTT
TLS握手: 1-2 RTT
总计: 2-3 RTT
QUIC握手时间:
首次连接: 1 RTT
重复连接: 0 RTT
减少: 1-3 RTT
实际性能提升:
- 延迟减少:特别是在高延迟网络中优势明显
- 移动网络:网络切换时的连接恢复更快
连接管理对比题
Q14: QUIC的连接关闭机制和TCP的四次挥手有什么不同?
TCP四次挥手:
- 需要4个数据包
- 存在TIME_WAIT状态
- 可能出现RST异常关闭(如我们的例子)
QUIC连接关闭:
- CONNECTION_CLOSE帧:优雅关闭
- 无TIME_WAIT:立即释放资源
- 连接迁移:支持连接ID变更
多路复用对比题
Q15: TCP的单流阻塞问题(Head-of-Line Blocking)是什么?
TCP队头阻塞问题:
发送顺序: [包1] [包2] [包3]
网络情况: 包1丢失,包2、包3到达
TCP行为: 必须等待包1重传,包2、包3被阻塞
QUIC多路复用解决方案:
Stream 1: [包1丢失] - 只影响Stream 1
Stream 2: [包2到达] - 立即处理
Stream 3: [包3到达] - 立即处理
备注:TCP确实也有多路复用(通过多个TCP连接),但QUIC解决队头阻塞的根本原因在于流级别的独立性,而不仅仅是多路复用本身。
TCP的队头阻塞发生在传输层:
- TCP保证字节流的有序传递
- 即使应用层有多个逻辑流,它们都共享同一个TCP连接的字节流
- 当某个包丢失时,TCP必须等待重传并按序重组,阻塞整个连接
TCP连接中的数据流:
[Stream1数据][Stream2数据][丢失包][Stream3数据]
↓
由于丢失包,Stream3数据无法被应用层读取
QUIC的解决方案:
QUIC在应用层实现了真正的流独立性:
- 独立的流重组:每个QUIC流有自己的接收缓冲区和重组逻辑
- 流级别的流量控制:每个流可以独立进行流量控制
- 选择性确认:可以针对特定流的包进行确认,而不影响其他流
QUIC多流传输:
Stream 1: [包1丢失] - 只影响Stream 1,等待重传
Stream 2: [包2到达] - 立即处理并交付给应用层
Stream 3: [包3到达] - 立即处理并交付给应用层
为什么TCP多路复用不能解决:
HTTP/1.1的多个TCP连接确实能缓解问题,但:
- 资源开销大:每个连接都有独立的拥塞控制、慢启动
- 连接管理复杂:需要维护多个TCP状态机
- 仍有局部阻塞:单个连接内的多个请求仍会相互阻塞
HTTP/2虽然在单个TCP连接上实现了多路复用,但底层TCP层面的队头阻塞问题依然存在。
所以QUIC的核心优势是在单个连接内实现了真正的流级别独立性,这是TCP层面无法解决的架构问题。
五、实际应用场景题
部署考虑题
Q16: 在什么场景下选择QUIC比TCP更有优势?
QUIC适用场景:
- 移动网络:频繁的网络切换
- 高延迟网络:卫星通信、跨国连接
- 实时应用:在线游戏、视频直播
- Web应用:HTTP/3,特别是多资源加载
TCP适用场景:
- 企业内网:稳定的网络环境
- 批量传输:大文件传输
- 传统应用:现有的TCP应用生态
协议演进题
Q17: 为什么HTTP/3选择了QUIC而不是改进TCP?
TCP改进的限制:
- 内核实现:TCP在内核中实现,修改困难
- 中间设备:防火墙、NAT设备的兼容性问题
- 向后兼容:不能破坏现有的TCP应用
QUIC的优势:
- 用户空间实现:易于更新和优化
- UDP基础:绕过中间设备的TCP限制
- 快速迭代:可以快速部署新特性
总结与感想
通过本次Wireshark抓包分析,我们深入理解了TCP协议的核心机制:
关键发现
- 三次握手:确保双方都能收发数据
- 数据传输:通过序列号保证可靠性
- 四次挥手:优雅地关闭连接(本例中出现异常RST)
- 序列号机制:TCP可靠传输的基础