HTTPS 建立连接的耗时里,TCP 握手 + TLS 握手是大头。TLS 1.2 需要额外的 2 个 RTT 才能发第一个 HTTP 字节,TLS 1.3 减到 1 个,会话恢复场景 0 个。它是怎么省的?

1.2 的流程为什么慢

Client                          Server
  | ---- ClientHello ------------> |   1-RTT 开始
  | <--- ServerHello + 证书 + Done - |
  | ---- 密钥交换 + Finished -----> |   2-RTT 开始
  | <--- Finished ----------------- |
  | ==== 终于可以发 HTTP 了 ======== |

1.2 里客户端要先“看一眼”服务器选了什么套件,才能开始算密钥——协商在前,密钥交换在后,一来一回就多出来了。

1.3 的改动:把猜测变成标配

1.3 直接砍掉了那堆老旧套件,只留下 ECDHE 一族。既然算法没悬念,客户端第一次打招呼就能把密钥交换材料(key_share)带上:

Client                          Server
  | -- ClientHello + key_share --> |
  | <--- ServerHello + 证书 + Finished -- |  服务器已能算出密钥
  | ---- Finished + [应用数据] ---> |      客户端也能发数据了
  | ==== 1-RTT 后双向应用数据 ===== |

第二个 RTT 里客户端就能携带 HTTP 请求,握手指纹也更干净——套件少了,中间盒想搞“降级劫持”的可乘之机也少了。同时 1.3 对整个 ServerHello 之后的握手做了加密,抓包里再也看不到明文的证书链,排查问题时记得用 SSLKEYLOGFILE 配 Wireshark。

0-RTT:再快一步的代价

会话恢复时,客户端凭上次的 PSK 在第一个飞行里直接带上应用数据(early data),真正零往返。代价是这些数据可能被重放——所以 0-RTT 数据只能用于幂等请求(GET 拉取没问题),下单、支付这类写操作必须等握手完成。Nginx 里对应 ssl_early_data on;,打开前先想想自己的接口幂等吗。

验证一把

openssl s_client -connect example.com:443 -tls1_3 < /dev/null 2>/dev/null | grep -E "Protocol|Cipher"

如今主流浏览器和 CDN 默认都协商 1.3。对前端性能而言,这一 RTT 省在每一次新建连接上,配合 TCP Fast Open 和连接复用,冷启动体验差距非常直观。