一、TCP是什么?

        TCP 是 传输层(协议,提供 可靠的、面向连接的、基于字节流的 全双工数据传输服务。

        位于 IP 之上,下面是 IP 层,上面通常是应用层协议(如 HTTP、HTTPS、SMTP、IMAP、FTP、SSH 等)。

        和 UDP 对比:UDP 是无连接、不可靠、面向报文的,TCP 则是“尽力而为也要可靠交付”。

二、TCP报文段                   
+-------------------+-----------------------+
|     源端口号      |     目标端口号        |  
+-------------------+-----------------------+
|                  序列号(Sequence Number)          
+---------------------------------------------------+
|                确认号(Acknowledgment Number)      
+---------------------------------------------------+
|  数据偏移 | 保留 | 标志位 |       窗口大小              
+---------------------------------------------------+
|      校验和       |       紧急指针                | 
+---------------------------------------------------+
|                 可选选项(Options)                |
+---------------------------------------------------+
|                    数据(Payload)                 
+---------------------------------------------------+

序列号(Seq):本次发送数据的第一个字节在整个字节流中的偏移(初始值 ISN 随机)。

确认号(Ack):期望对方下次发送的 Seq 号(即已经正确接收到 Ack-1 为止的所有字节)。

标志位(6个):

        URG(紧急指针是否有效)

        ACK(确认号是否有效,几乎所有建立连接后的报文都置1)

        PSH(提示接收方尽快上交应用层)

        RST(复位连接,常用于异常关闭或拒绝非法请求)

        SYN(发起连接,同步序列号)

        FIN(优雅关闭,发送完毕)

        窗口大小(Window):接收窗口(rwnd),告诉对方我还能接收多少字节,用于流量控制。        

        Options:常见有 MSS(最大段大小)、Window Scale(窗口扩大因子)、SACK(选择性确认)、Timestamp(防序号绕回+RTT计算)等。

三、TCP三次握手

        客户端                     服务端
   |   SYN (seq=x)            |
   | ------------------------>|
   |   SYN+ACK (seq=y, ack=x+1)|
   | <------------------------|
   |   ACK (seq=x+1, ack=y+1) |
   | ------------------------>|

两次握手可以吗?

        不行!两次握手只能保证单向连接通畅,无法做到双向都确认。

举一个最经典的已失效连接请求:

  1. 客户端发出一个 SYN(seq=100),因为网络堵塞迟迟没到服务器。
  2. 客户端超时后重传了新的 SYN(seq=1000),这次顺利建立连接,正常通信结束后关闭。
  3. 很久以后,第一个“老的 SYN(seq=100)”突然到达服务器。
  4. 如果只有两次握手,服务器收到老 SYN 后会直接回复 SYN+ACK,然后就认为连接建立了。
  5. 但客户端根本没想建立连接,它会直接丢弃这个 SYN+ACK,服务器却傻傻地等着数据,浪费资源。

三次握手彻底解决了这个问题: 老的 SYN 到来 → 服务器回复 SYN+ACK → 客户端发现 Ack = x+1 不是自己当前期望的值 → 直接回 RST,服务器立刻断开,不会建立无效连接。

为什么是三次而不是两次?

        两次无法防止已失效的连接请求(历史连接)突然到达服务器,导致服务器误以为是新连接。

        三次确保双方都确认对方能收能发,且序列号同步完成。

握手时主要协商的内容:

        初始序列号 ISN(双方各自随机生成,防伪造和序号预测攻击)

        MSS(通常取 min(双方MTU-40))

        窗口扩大因子(Window Scale)

        是否支持 SACK 等

三次握手的过程

次数 方向 报文类型 主要字段内容
1 A → B SYN Seq = x(随机) ACK 标志 = 0 Ack = 0 Options: MSS, Window Scale, SACK Permitted, Timestamp…
2 B → A SYN + ACK Seq = y(随机) ACK 标志 = 1 Ack = x + 1 MSS, Window Scale, SACK…
3 A → B ACK Seq = x + 1 ACK 标志 = 1 Ack = y + 1

四、TCP的四次挥手

        主动关闭方                被动关闭方
   |   FIN (seq=m)            |
   | ------------------------>|
   |        ACK (ack=m+1)     |
   | <------------------------|
   |                          |  (此时被动方可能还有数据要发)
   |        FIN (seq=n)       |
   | <------------------------|
   |        ACK (ack=n+1)     |
   | ------------------------>|

次数 方向 报文类型 关键字段内容 状态变化(主动方 → 被动方)
1 A → B FIN Seq = m ACK = n FIN=1 ESTABLISHED → FIN-WAIT-1
2 B → A ACK Ack = m+1 (不带 FIN) ESTABLISHED → CLOSE-WAIT A: FIN-WAIT-1 → FIN-WAIT-2
3 B → A FIN Seq = p Ack = m+1 FIN=1 CLOSE-WAIT → LAST-ACK
4 A → A ACK Seq = m+1 Ack = p+1

FIN-WAIT-2 → TIME-WAIT →(等待 2MSL)→ CLOSED B: LAST-ACK → CLOSED

为什么是四次?为什么不能三次?

    核心原因:TCP 是全双工,发送方向和接收方向必须分别关闭!

        FIN 只表示“我这个方向发完了”

        ACK 只表示“我收到你发完了”

所以必须各自发一次 FIN + 各自确认一次对方的 FIN → 最少 4 次。

如果被动方在收到 FIN 后立刻也发 FIN(即第 2、3 次合并),理论上可以变成三次挥手,但绝大多数实现都不敢这么干,因为:

        合并后如果这个 FIN+ACK 丢了,主动方会一直重传 FIN,而被动方再也收不到重传的 FIN(因为它已经关闭了接收通道),会导致连接永远卡住。

        所以标准实现永远是“先回 ACK 再发 FIN”,安全第一。

五、TIME_WAIT

        TIME_WAIT 是 TCP 协议在连接正常关闭时,主动关闭的一方(主动发送 FIN 的一方)必须停留的一个状态。它是 TCP 四次挥手中非常重要的一环,直接影响服务器的连接复用能力和稳定性。

TIME_WAIT 存在的两个核心原因

TIME_WAIT 不是 bug,而是 TCP 协议故意设计的,目的是解决两个致命问题:

原因1:可靠地终止连接(防止迟到的包干扰新连接)

        最后一次 ACK 有可能丢失(网络抖动、丢包)

        如果服务端没收到这个 ACK,会超时重发 FIN

        这时如果客户端已经完全关闭(没有 TIME_WAIT),同一个四元组(源IP、源端口、目的IP、目的端口)可能被新连接复用

        服务端重发的旧 FIN 到达时,新连接会误以为是对方要关闭,连接直接被 RST 掉!

  TIME_WAIT 状态保留了原连接的四元组信息,并保持 2MSL(2 × Maximum Segment Lifetime,报文最大存活时间,通常 60~120 秒):

        如果在这期间收到旧连接的重复 FIN,直接回复 RST(而不是 ACK)

        保证旧连接的所有迟到包都自然消亡

原因2:确保被动关闭方正确收到最后的 ACK

        给迟到的 FIN 包留出重传时间

        让被动关闭方有足够时间收到 ACK,避免它一直处于 FIN_WAIT_2 或重传状态。

六、为什么TCP可靠?

        

序号(Sequence Number)和确认应答(ACK)

        每个字节都有唯一的序号(32位)。

        接收方收到数据后,必须回一个ACK,告诉发送方“我已经正确收到到第X字节之前的所有数据”。

         如果发送方在一定时间内(RTO)没收到ACK,就会重传那段数据。 → 解决了丢包数据损坏问题(因为有校验和Checksum,损坏的数据不会被确认)。

超时重传(Retransmission Timeout, RTO)

        发送方为每个未被确认的段设置一个定时器。

        超时没收到ACK就重传(还会启动指数退避,避免拥塞加剧)。 → 这是最核心的“丢了就重发”机制。

流量控制(Flow Control)

        接收方在每个ACK里携带“窗口大小”(rwnd,接收窗口)。

        发送方只能发送不超过这个窗口的数据量。 → 防止快发慢收导致接收方缓冲区溢出丢包。

拥塞控制(Congestion Control)

        慢启动、拥塞避免、快速重传、快速恢复四大算法(经典的AIMD机制)。

        通过cwnd(拥塞窗口)动态调整发送速率,避免把网络塞死。 → 虽然主要是提高效率,但也间接提高了可靠性(减少因拥塞导致的丢包)。

有序交付与去重

        接收方会根据序号把乱序到达的段缓存起来,等缺的段到了再按顺序递交给应用层。

        重复的段直接丢弃(因为序号已经确认过了)。 → 解决了IP数据报乱序和重复的问题。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐