Skip to content

模块导航: 总入口 · 总纲与方法 · 后端场景 · AI与算法工程 · 国企与金融 · 网络问答


1. TCP 三次握手(热度 654 · 核心必考) ​

Q: 描述 TCP 三次握手过程。为什么是三次而不是两次或四次?

A:

完整过程(报文 + 状态机) ​

diagram
客户端(C)                         服务器(S)
  |  CLOSED                        |  LISTEN
  |                                |
  |--- SYN=1, seq=x ------------->|  (服务器收到 SYN)
  |  SYN-SENT                      |  SYN-RCVD
  |<-- SYN=1, ACK=1, seq=y, ack=x+1 ---|  (服务器同步自己的序号 y)
  |                                |
  |--- ACK=1, seq=x+1, ack=y+1 -->|  ESTABLISHED
  |  ESTABLISHED                   |
  1. 第一次握手(SYN): 客户端 SYN=1, seq=x(x 为客户端 ISN,随机生成)。SYN 报文段不携带数据,但要消耗一个序号。客户端进入 SYN-SENT。
  2. 第二次握手(SYN+ACK): 服务器收到后回 SYN=1, ACK=1, seq=y, ack=x+1。ack=x+1 表示「我期望下次收到 x+1」。服务器进入 SYN-RCVD。该报文同样不携带数据,消耗一个序号。
  3. 第三次握手(ACK): 客户端回 ACK=1, seq=x+1, ack=y+1。第三次可以携带数据(一旦携带,seq 从 x+1 起算;不携带则下一个数据段 seq 仍是 x+1)。双方进入 ESTABLISHED。

半连接/全连接队列: 服务器收到 SYN 后为它建一个 request sock 放进半连接队列(SYN queue,长度受 tcp_max_syn_backlog 限制),回 SYN+ACK;收到第三次 ACK 后,把完成握手的 established sock 迁移到全连接队列(accept queue,长度取 min(somaxconn, backlog)),等待应用 accept() 取走。两条队列是相互独立的(半连接队列不是 accept queue 的"前半段"),SYN Flood 正是打满半连接队列。

设计动机:为什么是三次? ​

  • 同步双方 ISN: 双方都要确认「自己的发送能力」和「对方的接收能力」。一次不行(服务器不知道客户端是否收到自己的 SYN);两次时客户端知道服务器收到了,但服务器无法确认客户端收到了自己的 SYN+ACK。
  • 防止历史连接 / 旧 SYN 复活: 若只有两次,一个滞留在网络中的旧 SYN 到达服务器后,服务器单方面建立连接并分配资源,而客户端并不认这条连接——资源被白白占用。第三次 ACK 让服务器确认「客户端确实要建这条连接」。
  • 可靠信道建立的本质: 在不可靠信道上双向同步序号,最少需要「A→B、B→A、A→B」三个方向确认,使双方对「连接已建立」达成共识。

为什么不是四次? ​

服务器把 SYN 和 ACK 合并成一个报文(第二次)。SYN 表示「我也要同步我的序号」,ACK 表示「我收到了你的 SYN」——两者语义上可合并,所以 3 次足够。对照挥手:被动方的 ACK 与 FIN 不保证能合并(它可能还有数据要发完),所以一般是 4 次;若被动方恰无待发数据,两者可同段到达,线上就只有 3 个包(见第 2 题「补充状态」)。

加分点 ​

  • 第三次握手可以携带数据,但业界较少用:此时服务器尚未收到这个 ACK,仍在 SYN-RCVD,连接对服务器而言还没完全建立。若第三次 ACK 丢失,服务器会超时重传 SYN+ACK(受 tcp_synack_retries 限制);客户端发完 ACK 已进入 ESTABLISHED,不会重传 SYN,它后续写数据时要么收到服务器的 RST,要么再回一个 ACK 把服务器拽回 ESTABLISHED。
  • SYN Flood:大量伪造源 IP 发 SYN 且不回第三次 ACK,半连接队列被占满。防御:tcp_syncookies=1、调小 tcp_synack_retries、增大 tcp_max_syn_backlog、SYN Proxy。
  • 可提:TFO(TCP Fast Open)允许在 SYN 中携带 Cookie+数据,减少 1-RTT,但需两端支持。

常见追问 ​

追问 1:SYN Flood 的原理与防御?

  • 答题要点: 原理——攻击者只发 SYN,不完成第三次握手,服务器半连接队列/资源被耗尽,合法连接进不来。防御——① syncookies(不占队列,把状态编码进 SYN+ACK 序号,收到合法第三次 ACK 再建连);② 增大半连接队列、缩短 SYN-RCVD 超时;③ 防火墙限速、过滤伪造源 IP;④ 上游清洗。

追问 2:如果第三次握手丢了会怎样?

  • 答题要点: 客户端认为连接可能已建立(发完 ACK 后 ESTABLISHED),但服务器仍在 SYN-RCVD,会重传 SYN+ACK(超时重传,受 tcp_synack_retries 限制)。客户端收到重复 SYN+ACK 后再次回 ACK。若始终收不到,服务器放弃,客户端连接在发数据时才发现异常(RST 或超时)。

追问 3:ISN 为什么是随机的?可以是 0 吗?

  • 答题要点: 先给结论——协议并不禁止某个连接的 ISN 恰好取到 0,但没有实现会把初值固定为 0:固定初值等于放弃下面两项防护。随机 ISN 是为了防止「序号预测攻击」和「旧连接报文干扰新连接」——若 ISN 固定为 0,网络中滞留的旧报文 seq 恰好落在新连接窗口内,会被误收。现代实现还加入时间戳与加密随机数(RFC 6528)。

易错点 ​

易错说法纠正
「三次握手是为了可靠传输数据」握手阶段主要目的是同步序号、双方确认能力;数据可靠传输靠 seq/ack/重传等机制
「前两次都能携带数据」SYN 报文段规范上不携带数据;第三次才可以
「两次握手完全不可用」若信道绝对可靠且无历史报文问题,两次理论上可行;现实是丢包/延迟/重复网络,两次会留下历史连接漏洞
「SYN-RCVD 就是 ESTABLISHED」服务器在 SYN-RCVD 仍等待第三次 ACK;ESTABLISHED 以收到第三次 ACK(或 syncookie 验证成功)为准
「ack=x 是确认收到 SYN」应是 ack=x+1——确认的是「期望的下一个序号」,比已收序号大 1

与选择题对照 ​

  • 主库 150: 第 60 题(握手/挥手次数)、第 61 题(第二次握手 SYN=1, ACK=1)、第 117 题(SYN Flood 针对建连过程)、第 59 题(端到端可靠传输层)、第 124 题(协议顺序中 TCP 在 TLS 前)。
  • 补题 16: 补-12(三次握手而不是两次的最主要原因)、补-11(TCP 首部标志位 URG/ACK/PSH/RST/SYN/FIN)。
  • 口述联动: 面试展开时可回扣「补-12 考的是历史连接与资源浪费,不是『能不能传数据』」。

工程 / 抓包背景 ​

  • Wireshark 过滤: tcp.flags.syn==1 && tcp.flags.ack==0 看第一次;tcp.flags.syn==1 && tcp.flags.ack==1 看第二次;完整握手可看 tcp.flags.syn==1 || tcp.flags.ack==1 前三包。
  • 观察半连接: Linux ss -tn state syn-recv / netstat -ant | grep SYN_RECV;cat /proc/net/sockstat 的 TCP 行只有 inuse/orphan/tw/alloc/mem 五个数(该文件另有 UDP/UDPLITE/RAW/FRAG 行),没有队列长度字段;溢出计数看 /proc/net/netstat 的 TcpExt——全连接(accept)队列溢出是 ListenOverflows/ListenDrops,半连接(request)队列溢出另有 TCPReqQFullDrop/TCPReqQFullDoCookies(nstat -az 同名,两条队列各有计数器,别用一对覆盖两条)
  • sysctl 相关: net.ipv4.tcp_syncookies、net.ipv4.tcp_max_syn_backlog、net.ipv4.tcp_synack_retries。
  • curl 侧: curl -v --connect-timeout 3 https://example.com;若 SYN 无响应,表现 connect 超时;若被 RST,表现 connection refused。

持续学习,持续积累。