Skip to content

TCP

TCP

TCP 状态机

TCP 状态机

TCP 握手

  1. 第一层:状态转换与序列号 (The Basics) 你必须能够精准描述状态机(State Machine)的变化:

第一次握手: 客户端发送 SYN,进入 SYN_SENT 状态。

第二次握手: 服务器发送 SYN + ACK,进入 SYN_RECV 状态。

第三次握手: 客户端发送 ACK,进入 ESTABLISHED 状态。服务器收到后也进入 ESTABLISHED。

深挖点:ISN (Initial Sequence Number)

为什么序列号不是从 0 开始?

安全: 防止预测序列号进行 TCP 注入攻击。

避免冲突: 防止在网络中滞留的旧连接报文被误认为是新连接的数据。

  1. 第二层:内核队列与参数 作为生产工程师,你需要关注握手过程在 Linux 内核中占用了哪些资源。

半连接队列 (SYN Queue): 当服务器收到 SYN 但还没收到最后的 ACK 时,连接存在这里。

参数: net.ipv4.tcp_max_syn_backlog

全连接队列 (Accept Queue): 三次握手完成,等待应用程序(如 Nginx)调用 accept()。

参数: net.core.somaxconn

面试陷阱题: “如果全连接队列满了,服务器会怎么办?”

答案: 默认会丢弃最后的 ACK(看似建立了连接但发不出数据),或者发送 RST。

  1. 第三层:性能与安全优化 (High-Scale Scenarios) 在海量请求的场景下,握手的开销是巨大的。

  2. TCP Fast Open (TFO) 传统的握手需要一个往返(RTT)才能开始传数据。TFO 允许在 第二次连接 时,在 SYN 包中直接携带数据。

原理: 第一次连接时,服务器给客户端发一个 Cookie。下次客户端直接带着 Cookie 和数据发 SYN。

  1. SYN Cookies (防御 SYN Flood) 场景: 黑客发送大量 SYN 却不发最后的 ACK,试图填满服务器的“半连接队列”。

对策: 服务器不维护半连接队列,而是将连接信息通过哈希算法编码到 SYN+ACK 的序列号里。只有当客户端回传合法的 ACK 时,服务器才分配内存。

  1. 握手超时与重传 如果第一个 SYN 丢了,Linux 会重传多少次?

通常是 5 次(指数退避:1s, 2s, 4s, 8s, 16s)。

作为 NPE,你需要判断:在内网(Low Latency)环境下,这个默认值是否太长了?

TCP 四次挥手

  1. 握手是“双方约好见面”,而挥手是“各自有序收摊”。

FIN (A -> B): A 说:“我没数据发了,我要关了。”(A 进入 FIN_WAIT_1)

ACK (B -> A): B 说:“收到,但我可能还有数据没发完,你等会。”(B 进入 CLOSE_WAIT,A 进入 FIN_WAIT_2)

FIN (B -> A): B 发完数据了:“好了,我也关了。”(B 进入 LAST_ACK)

ACK (A -> B): A 说:“知道了,拜拜。”(A 进入 TIME_WAIT)

核心面试点:TIME_WAIT 状态 这是 Meta 面试中最常问的:为什么 A 最后不直接关掉,而是要等 2MSL(通常是 60s)?

防止最后的 ACK 丢失: 如果 B 没收到最后的 ACK,它会重发 FIN。如果 A 已经关了,B 就永远收不到确认,无法正常关闭。

防止“旧包干扰”: 确保本次连接产生的所有数据包都在网络中消失,不会干扰到下一个使用相同端口的新连接。

NPE 生产思考: 在 Meta 的高并发负载均衡器上,会出现数万个 TIME_WAIT。这会占用端口资源。

解决方案: 开启 net.ipv4.tcp_tw_reuse(重用处于 TIME_WAIT 的端口)。

流量控制 (Flow Control) vs 拥塞控制 (Congestion Control)

很多人会搞混这两者。简单来说:

流量控制: 是“点对点”的。接收方告诉发送方:“我没地方存了,你发慢点。”(通过 Window Size 实现)。

拥塞控制: 是“全局性”的。发送方发现网络丢包了:“看来路塞住了,我得减速。”

拥塞控制的四个阶段: - 慢启动 (Slow Start): 从 1 个指数级增长(1, 2, 4, 8...)

- 拥塞避免 (Congestion Avoidance): 到达阈值后,改为线性增长(+1, +1...)

- 快重传 (Fast Retransmit): 收到 3 个重复的 ACK,不等超时直接重发

- 快恢复 (Fast Recovery): 丢包后不直接回到 1,而是回到阈值一半

TCP 重置 (RST) 与故障排查

在生产环境中,你经常会看到 Connection Reset by Peer。

  • RST 标志位: 它是 TCP 的“紧急止损”。

触发场景:

  • 客户端尝试连接一个没有监听的端口。

  • 防火墙主动切断不安全的连接。

  • 一方已经关闭连接,另一方又发来数据

TLS

目前主流使用的是 TLS 1.2 和 TLS 1.3。TLS 1.3 像是个急性子,效率极高;而 TLS 1.2 步骤多一些,但更利于理解逻辑。

我们可以把这个过程比喻成两个特工在秘密接头:

  1. TLS 1.2 握手:经典的“四步走” TLS 1.2 通常需要 2个往返(2-RTT) 才能完成握手。

你好 (Client Hello): 客户端先打招呼,发出一串随机数(Client Random)和一张“菜单”,上面写着它支持的 TLS 版本和密码套件(比如 AES、RSA 等)。

你好与身份证明 (Server Hello & Certificate): 服务器选定一个密码套件,也发回一个随机数(Server Random)。最重要的是,它会甩出一张数字证书(由受信任的机构颁发),证明“我真的是你要找的那台服务器”。

密钥交换 (Key Exchange & Cipher Spec): 客户端检查证书没问题后,生成一个新的随机数(Pre-master Secret),并用服务器证书里的公钥加密后发给服务器。

大功告成 (Finished): 双方现在手里都有三个随机数了,他们会用同样的算法把这三个数算成一个对称密钥。从此以后,所有对话都用这个密钥加密。

  1. TLS 1.3 握手:极致的效率 到了 TLS 1.3,流程被大幅精简,只需要 1个往返(1-RTT)。

一次性把事办完: 客户端在发 Client Hello 的时候,不再只是发菜单,而是直接预判了服务器可能选的算法,把自己的密钥交换参数直接发过去。

服务器直接回应: 服务器收到后,选定算法并即时生成自己的密钥参数,在回复 Server Hello 的同时就顺便把加密通道建好了。

0-RTT 重连: 如果你之前刚访问过这个网站,TLS 1.3 甚至支持在第一个数据包里就带上加密数据。

UDP