SNI 是 TLS 握手时,客户端提前告诉服务器:"我想访问的是哪一个域名"。
它的作用是让服务器在 TLS 握手阶段就能知道要给哪个域名的证书。
🎯 为什么需要 SNI?
因为现在很多网站是 多域名共享同一个 IP。
例如:
diff
1.2.3.4 上托管了:
- api.a.com
- img.a.com
- pay.a.com
如果没有 SNI:
客户端连接 1.2.3.4:443 → TLS 握手开始
服务器此时 不知道你想访问哪个域名的证书
→ 就没法送对证书
→ 导致握手失败
所以 TLS 扩展 "SNI" 就诞生了。
🧠 SNI 在 TLS 握手里到底发生了什么?
TLS 1.2 / 1.3 都一样,基本流程是:
markdown
ClientHello:
- TLS Versions
- Cipher Suites
- Extensions:
- SNI = "api.example.com" ← 就是这里!
- ALPN (比如 h2/h3)
- ...
服务器收到 ClientHello 后,看到 SNI:
哦,你访问的是 api.example.com!
那我给你发 api.example.com 的证书
然后握手继续。
🔥 SNI 与 "IP 直连(DNS Mapping)" 有什么关系?
这是你现在最关心的一点:
你做 DNS 优化时会把域名替换成 IP:
arduino
https://api.example.com/user
→
https://203.107.1.1/user (IP直连)
如果你 不加 Host header:
服务器会认为:
你要访问 203.107.1.1
那我发给你"203.107.1.1"对应的证书(基本不存在)
→ TLS 握手立即失败
所以你必须:
less
req.setValue("api.example.com", forHTTPHeaderField: "Host")
⚠️ 更重要的是:
"Host" header 会被 URLSession 自动用作 SNI 的域名。
所以 TLS 握手会变成:
ini
SNI = "api.example.com"
→ 即使你是连的 IP,TLS 仍然能拿到对应的域名证书
→ 握手成功
这就是 IP 直连 + HTTPS 能工作的原因。
🎯 用一句更工程化的话总结:
SNI 就是 TLS 握手阶段的 Host header。
客户端会在 ClientHello 里把这个域名告诉服务器,让服务器知道发哪一套证书。