HTTP 传输层 TCP 三次握手 / 四次挥手

OSI 七层模型

OSI(Open Systems Interconnection)模型是由国际标准化组织(ISO)提出的概念性网络通信框架,将网络通信分为七个层次,从下到上依次为:物理层、数据链路层、网络层、传输层、会话层、表示层、应用层。

层级 名称 主要功能 数据单元 常见协议/设备
7 应用层 为应用软件提供网络服务接口,直接与用户交互 报文(Message) HTTP、FTP、SMTP、DNS、Telnet
6 表示层 数据格式转换、加密/解密、压缩/解压 报文(Message) JPEG、ASCII、TLS(部分)、加密协议
5 会话层 建立、管理、终止应用程序之间的会话,控制同步 报文(Message) NetBIOS、RPC、PPTP
4 传输层 端到端可靠传输,流量控制,差错恢复,分段重组 段(Segment) TCP、UDP、SCTP
3 网络层 路由选择,逻辑寻址(IP),分组转发 包(Packet) IP、ICMP、ARP(争议)、RIP、OSPF
2 数据链路层 相邻节点间可靠传输,物理寻址(MAC),差错检测 帧(Frame) 以太网、PPP、交换机、网桥
1 物理层 比特流传输,定义电气、机械、过程接口 比特(Bit) 网线、光纤、集线器、中继器

TCP 报文段结构

TCP 报文由首部和数据两部分组成 。TCP 首部位于 TCP 报文段的前部,长度 20~60 字节(20 字节固定 + 最多 40 字节选项)

标志位

  1. SYN, Synchronize, 同步序列号,用于建立连接。连接请求时 SYN=1,ACK=0;连接响应时 SYN=1,ACK=1。
  2. ACK, Acknowledgment, 确认号字段有效。除初始 SYN 报文外,几乎所有报文都设置 ACK=1。
  3. FIN, Finish, 发送方已无数据,请求释放连接。用于四次挥手中关闭方向。
  4. RST, Reset, 重置连接。用于拒绝连接、响应非法报文、终止异常连接等。
  5. NS, ECN-nonce, 用于 ECN(显式拥塞通知)的 concealment protection(隐藏保护),与 CWR、ECE 配合防止恶意丢包。通常与拥塞控制相关。
  6. CWR, Congestion Window Reduced, 发送方收到 ECE 后,通知接收方已减小拥塞窗口。用于 ECN 响应。
  7. ECE, ECN-Echo, 接收方通知发送方网络出现拥塞(IP 包中 ECN 字段被标记)。当 ECN 协商成功后,接收方在 ACK 中置 ECE=1。
  8. PSH, Push ,接收方应尽快将数据交付上层应用,不等待缓冲区填满。减少数据在接收端的延迟。
  9. URG, Urgent, 紧急指针有效。表示报文段中有紧急数据,需优先处理。配合紧急指针字段使用。

TCP 首部中的 窗口(Window) 字段占 16 位 ,全称为 接收窗口(rwnd,Receiver Window) ,用于 流量控制 ,并表示 从确认号(ack)开始,接收方当前还能接收的字节数

接收窗口的大小并非固定值,它是一个由接收方根据自身处理能力和系统配置动态计算出来的变量,并在通信过程中实时调整。

TCP 报文段的数据载荷大小,主要由两个既有联系又有区别的参数共同决定,它们分别是"单次能装多少"和"总共能发多少":

  • 单次能装多少 :由 MSS (最大段大小) 确定,它限制了单个 TCP 报文段中数据部分的最大字节数。
  • 总共能发多少 :由 接收窗口 (rwnd) 确定,它限制了已发未确认数据总量的上限。

TCP 建立连接 / 三次握手过程

客户端,初始化为 CLOSED 状态, 连接完全关闭,没有任何活动。当应用程序调用 connect() 主动打开连接时,才会从 CLOSED 转变为 SYN_SENT,开始发送 SYN

服务器,初始化 LISTEN状态, 服务器进程已创建套接字并绑定端口,处于监听状态,等待客户端的连接请求。只有从 LISTEN 才能接收 SYN 并转入 SYN_RCVD

服务器必须先执行 listen() 进入 LISTEN 状态,才能接受连接。如果没有处于 LISTEN,客户端的 SYN 会被丢弃或回复 RST。

TCP 第一次握手:客户端发送 SYN=1, seq=x

在 TCP 三次握手的第一次中,客户端向服务器发送一个 TCP 报文段,其中:

  • SYN 标志位设置为 1SYN 是 TCP 报文头中的一个标志位 (控制位),它的全称是 Synchronize (同步),主要用于初始化连接时同步序列号
  • seq (序列号)字段设置为某个数值 xseq 是 TCP 报文头中的一个字段,全称为 Sequence Number (序列号)。它用于标识从发送方发出的数据字节流中的位置seq 字段的值 :表示本报文段所携带数据的第一个字节的序号

TCP 第二次握手:服务器发送 SYN=1, ACK=1, seq=y, ack=x+1

在 TCP 三次握手的第二次 中,服务器收到客户端的 SYN 报文(seq=x)后,会回复一个报文段,其中:

  • SYN 标志位 = 1
  • ACK 标志位 = 1
  • seq = y(服务器自己的初始序列号 ISN)
  • ack = x + 1(确认收到客户端的 SYN)

ack = x+1 而不是 x :因为客户端的 SYN 报文虽然不携带应用数据,但它消耗一个序列号seq=x),所以确认号必须是 x+1,表示"我已收到序列号 x,期望下一个字节是 x+1"。

TCP 第三次握手:客户端发送 ACK=1, seq=x+1, ack=y+1

在 TCP 三次握手的第三次 中,客户端收到服务器的 SYN+ACK (seq=y, ack=x+1) 后,发送一个 ACK 报文作为最终确认。报文关键字段

  • ACK = 1(确认标志位置位)
  • seq = x + 1(客户端下一个要发送的字节序号)
  • ack = y + 1(确认收到服务器的 SYN 报文,期望服务器下一个字节为 y+1)

为什么要三次握手

  1. 确认双方的收发能力正常
    • 第一次握手:服务器能收到客户端的 SYN,表明客户端的发送能力和服务器的接收能力正常。
    • 第二次握手:客户端能收到服务器的 SYN+ACK,表明服务器的发送能力和客户端的接收能力也正常。
    • 第三次握手:服务器能收到客户端的 ACK,最终确认客户端的接收能力和服务器的发送能力均正常。如果只有两次握手,服务器无法确定客户端的接收能力是否正常。
  2. 防止失效的连接请求报文段被误接受。网络中存在延迟的旧 SYN,若只有两次握手,服务器会误认为客户端想建立新连接而分配资源,造成浪费。三次握手让客户端可以拒绝旧请求(通过 RST)。
  3. 同步初始序列号(ISN)。三次握手正好交换并确认双方的 ISN。

什么是同时打开(Simultaneous Open)?

两端同时主动发起连接(即都发送 SYN)。此时双方都会收到对方的 SYN,然后各自回复 SYN+ACK,再各自回复 ACK。过程共交换 4 个报文,但最终也建立连接。

TCP 数据传输

cwnd(拥塞窗口)

发送方根据网络拥塞程度自主估算的值。此数值较小,通常表示连接刚建立(慢启动初始阶段)或刚刚发生超时重传重置了窗口。

rwnd(接收窗口)

接收方在 TCP 首部中通告的可用接收缓冲区大小。

发送窗口

实际发送窗口 = min(cwnd, rwnd)

RTT(Round-Trip Time,往返时间)

RTT 是指一个数据包从发送方发出到收到对方确认(ACK)所经过的总时间,即 网络延迟 的度量。

  • 单位:毫秒(ms)
  • 包含:发送方到接收方的传播延迟 + 接收方处理时间 + 确认包返回的传播延迟

滑动窗口

滑动窗口是 TCP 实现可靠传输流量控制的核心技术。它允许发送方在收到确认之前连续发送多个报文段,从而显著提高信道利用率,同时动态调整发送速率以适应接收方的处理能力。

为什么需要滑动窗口?早期的停止等待协议(发一个段,等一个 ACK,再发下一个)效率极低,尤其在高带宽延迟乘积(BDP)的网络中。滑动窗口允许发送方连续发送多个段,窗口大小决定了未确认数据的最大量。

发送窗口结构

  • SND.UNA:第一个未确认的字节序号(窗口左边界)
  • SND.NXT:下一个要发送的字节序号(窗口内偏移)
  • Win:当前发送窗口大小(字节数)
  • 已发未确认 = SND.NXT - SND.UNA
  • 可用窗口 = Win - (SND.NXT - SND.UNA)

TCP 流量控制

TCP 流量控制是一种端到端的机制 ,用于防止发送方发送数据过快而导致接收方缓冲区溢出,从而避免数据丢失。它通过滑动窗口协议实现,由接收方动态通告自己可用的缓冲区大小(即接收窗口 rwnd),发送方据此调整发送速率。

重传定时器(RTO,Retransmission Timeout)

RTO 是 TCP 为每个发送的报文段启动的一个定时器,当定时器超时仍未收到 ACK 时,发送方认为该段已丢失,并触发重传

快速重传

快速重传是 TCP 拥塞控制中的一项优化机制,它允许发送方在没有等到重传定时器超时 的情况下,根据接收方发回的重复 ACK 提前判断某个报文段已丢失,并立即重传该段,从而提高传输效率。

为什么需要快速重传?

  • 超时重传(RTO)往往需要等待较长时间(通常几百毫秒甚至秒级),导致传输延迟增大,吞吐量下降。
  • 当接收方收到失序报文段时,会立即发送重复 ACK(DupACK),告知发送方"我还在等前面的某个段"。利用这些重复 ACK,发送方可以快速推断丢包,避免等待超时。

快速重传的触发条件为:发送方连续收到 3 个或更多重复的 ACK (即 DupACK count >= 3)。

慢启动

慢启动是 TCP 拥塞控制的核心算法之一,用于探测网络可用带宽,避免在连接建立之初或丢包恢复后向网络注入过多数据造成拥塞。

为什么需要慢启动?

  • 连接刚建立时,发送方对网络状况一无所知(不知道可用带宽、延迟、丢包率)。
  • 如果一开始就发送大量数据,可能瞬间填满中间路由器的队列,导致丢包和拥塞。
  • 慢启动以指数增长的方式从小窗口开始,快速探测到网络的容量上限(即拥塞发生的临界点)。

慢启动的工作机制

1、核心参数

  • 拥塞窗口(cwnd) :发送方能发送但未确认的数据量上限(字节数)。
  • 慢启动阈值(ssthresh) :慢启动与拥塞避免的切换点,初始值通常很大(如 65535 字节或更大)。

2、算法规则

  1. 初始 cwnd:通常为 1~10 个 MSS(最大段大小)。
  2. 每收到一个 ACK:cwnd 增加 1 个 MSS(即 cwnd = cwnd + MSS)。
  3. 效果:每个 RTT 内,cwnd 翻倍(因为一个 RTT 内能收到约 cwnd/MSS 个 ACK,每个 ACK 增加 1 MSS,总计增加 cwnd 字节)。
  4. 终止条件 :当 cwnd >= ssthresh 时,慢启动结束,进入拥塞避免阶段(线性增长)。

TCP 断开连接 / 四次挥手过程

主动方 可以是 客户端 ,也可以是 服务端。

假设客户端为主动方,过程如下:

第一次挥手:主动方发送 FIN=1, seq=u

在 TCP 四次挥手的第一次中,主动关闭连接的一方(可以是客户端或服务器)发送一个 FIN 报文段。报文关键字段

  • FIN = 1(标志位,表示"结束")
  • seq = u(主动方当前的发送序列号,等于之前已发送数据的最后一个字节序号 + 1)

主动方状态变为 FIN_WAIT_1 。发送 FIN 后,表示"我没有数据要发了,请求关闭连接"。主动方等待被动方对该 FIN 的 ACK 确认。

第二次挥手:被动方回复 ACK=1, seq=v, ack=u+1

在四次挥手的第二次 中,被动关闭方收到主动方发送的 FIN 报文(seq=u)后,回复一个 ACK 确认。报文关键字段

  • ACK = 1(确认标志位)
  • seq = v(被动方当前的发送序列号,等于之前已发送数据的最后一个字节序号 + 1)
  • ack = u + 1(确认收到主动方的 FIN 报文,期望下一个字节序号为 u+1)

被动方法回复 ack = u+1 明确告诉主动方:"我已收到你的 FIN(序列号 u),同意你关闭从你到我的方向。" 至此,主动方的发送通道已关闭。

此时 TCP 连接处于半关闭 状态:主动方不再发送数据,但被动方如果还有数据要发送,可以继续发送(主动方的接收通道仍然是打开的)。因此被动方不会立即发送 FIN ,而是等待自己的数据发完或应用层调用 close()

被动方 收到 FIN 并回复 ACK 后,被动方进入 CLOSE_WAIT,表示"等待本地应用程序关闭连接"。主动方收到该 ACK 后进入 FIN_WAIT_2 状态,等待被动方的 FIN。

第三次挥手: 被动方发送 FIN=1, ACK=1, seq=w, ack=u+1

在四次挥手的第三次 中,被动关闭方(已进入 CLOSE_WAIT 状态)在完成剩余数据的发送后,主动发起关闭自己这一侧的连接,发送 FIN 报文。同时,由于该报文需要确认主动方之前发送的 FIN,所以也会设置 ACK 标志(合并确认)。 报文关键字段

  • FIN = 1(请求关闭从被动方到主动方的方向)
  • ACK = 1(确认主动方发来的 FIN)
  • seq = w(被动方当前的发送序列号,等于之前已发送数据的最后一个字节序号 + 1。注意 w 可能等于第二次挥手时的 v,也可能大于 v,因为被动方可能在此期间发送了额外数据)
  • ack = u + 1(重复确认主动方的 FIN,表示期望下一个字节序号为 u+1。该值与第二次挥手中的 ack 相同)

第四次挥手:主动方发送 ACK=1, seq=u+1, ack=w+1

在 TCP 四次挥手的第四次 (也是最后一次)中,主动关闭方收到被动方发来的 FIN 报文(第三次挥手:FIN=1, ACK=1, seq=w, ack=u+1)后,发送一个 ACK 作为最终确认。报文关键字段

  • ACK = 1(确认标志位)
  • seq = u + 1(主动方当前的发送序列号。注意:主动方在第一次挥手时发送了 FIN seq=u,之后没有发送任何数据,因此序列号依然是 u+1)
  • ack = w + 1(确认收到被动方的 FIN 报文,期望下一个字节序号为 w+1)

为什么需要四次?

动方收到主动方的 FIN 时,可能还有数据需要发送,不能立即关闭自己的发送通道。

  • 被动方收到 FIN 后,会立即回复 ACK,表示"我知道你要关了"。
  • 但被动方可能还有未发完的数据,需要继续发送。因此它不会马上发送 FIN,而是等到自己的数据全部发送完毕后,再主动发送 FIN
  • 这就导致了 ACKFIN分开的两个报文,所以总共需要四次。

但如果被动方在收到 FIN 标志后,也没有数据发送了,可以将FIN+ACK 一次发送,就是三次。

为什么主动方最后一次发送后要等待 2MSL?

主动关闭方在发送第四次挥手(最后一个 ACK)后,会进入 TIME_WAIT 状态,并等待 2MSL(Maximum Segment Lifetime,报文最大生存时间)后才最终关闭连接。

1)确保最后一个 ACK 能够可靠地被被动方收到,避免对方因超时重传 FIN 而无法正常关闭;

2)让本连接中所有迟到的报文段在网络中消失,防止它们干扰后续使用相同端口的新连接。

相关推荐
用户573240037231 小时前
AgentForge-WX v0.3.0:12项更新 + 框架重新定位,把微信小程序AI对话的坑全填了
前端
小lan猫1 小时前
多域 RAG 知识库:从 Vue 前端到 NestJS + PGVector 的全栈实践
前端·人工智能·typescript
酿情师1 小时前
区块链网络与跨链操作03:矿池网络协议
网络·网络协议·区块链
半个烧饼不加肉1 小时前
JS 底层探究--执行上下文
开发语言·前端·javascript
极光技术熊1 小时前
从零构建在线Excel:一个Java全栈工程师的实战记录
前端·后端
漂流技术客1 小时前
超详细!Vue3 + ECharts 快速实现地图可视化(附最新GeoJson地址)
前端·vue.js
山河木马2 小时前
无框架-原生webGL渲染-底层入门-1
前端·javascript·webgl
jingling5552 小时前
Flutter | 商城项目鸿蒙(OpenHarmony)适配实战
android·开发语言·前端·flutter·华为·harmonyos