From 9bbab6a135d603daa2c3081b7ddee35656ddb8a8 Mon Sep 17 00:00:00 2001 From: Zhengchen Tao Date: Sun, 3 May 2026 15:56:04 +0800 Subject: [PATCH] =?UTF-8?q?init:=20Hugo=20+=20Stack=20=E4=B8=BB=E9=A2=98?= =?UTF-8?q?=20+=20=E9=A6=96=E6=89=B9=203=20=E7=AF=87=E6=96=87=E7=AB=A0=20+?= =?UTF-8?q?=20Gitea=20Actions=20=E8=87=AA=E5=8A=A8=E9=83=A8=E7=BD=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Stack 主题 + 自定义 padding 与标题样式 (assets/scss/custom.scss) - 内容: HTTPS 旅程 / AI 工程师地图 / Xray Reality - 页面: 首页 / 文章 / 归档 / 关于 / 搜索 - CI: Gitea Actions push → hugo --minify → rsync 到 NAS 应用 Gitea Actions 模板 §4.4-4.5 经验: paths-ignore (注意不排除 **.md) + concurrency cancel-in-progress + summary --- .gitea/workflows/build.yml | 64 + .gitignore | 7 + .gitmodules | 3 + archetypes/default.md | 5 + assets/img/avatar.jpg | Bin 0 -> 73831 bytes assets/img/avatar1.svg | 10 + assets/jsconfig.json | 10 + assets/scss/custom.scss | 48 + content/_index.md | 6 + content/about/index.md | 36 + content/archives/_index.md | 6 + content/posts/2023-04-11-xray-reality.md | 270 ++++ content/posts/2026-04-30-https-journey.md | 1286 +++++++++++++++++++ content/posts/2026-05-02-ai-engineer-map.md | 640 +++++++++ content/search/_index.md | 6 + deploy/docker-compose.yml | 28 + hugo.yaml | 150 +++ themes/stack | 1 + 18 files changed, 2576 insertions(+) create mode 100644 .gitea/workflows/build.yml create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 archetypes/default.md create mode 100644 assets/img/avatar.jpg create mode 100644 assets/img/avatar1.svg create mode 100644 assets/jsconfig.json create mode 100644 assets/scss/custom.scss create mode 100644 content/_index.md create mode 100644 content/about/index.md create mode 100644 content/archives/_index.md create mode 100644 content/posts/2023-04-11-xray-reality.md create mode 100644 content/posts/2026-04-30-https-journey.md create mode 100644 content/posts/2026-05-02-ai-engineer-map.md create mode 100644 content/search/_index.md create mode 100644 deploy/docker-compose.yml create mode 100644 hugo.yaml create mode 160000 themes/stack diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml new file mode 100644 index 0000000..44df3eb --- /dev/null +++ b/.gitea/workflows/build.yml @@ -0,0 +1,64 @@ +name: Build and Deploy Blog + +on: + push: + branches: [main] + # paths-ignore 注意: + # ❌ 千万别加 **.md —— content/posts/*.md 是文章本身,排除等于不发文章 + # ✅ 只排除根级文档 / 部署清单 / 不影响产物的文件 + paths-ignore: + - 'README.md' + - 'LICENSE' + - '.gitignore' + - 'deploy/**' + workflow_dispatch: + +# 连续 push 只跑最新一次,旧 in-progress run 取消(参考 Gitea Actions 模板 §4.4) +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout (with submodules) + uses: actions/checkout@v4 + with: + submodules: recursive + fetch-depth: 0 + + - name: Install Hugo Extended + # 钉死版本,避免 latest 漂移导致 PaperMod / Hextra / Stack 弃用警告升级为 error + run: | + HUGO_VERSION=0.161.1 + curl -fsSL "https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.tar.gz" | tar -xz + sudo install hugo /usr/local/bin/ + hugo version + + - name: Build site + run: hugo --gc --minify + + - name: Ensure rsync available + run: which rsync || (sudo apt-get update -qq && sudo apt-get install -y -qq rsync) + + - name: Deploy to /blog-public (mounted from NAS) + # /blog-public 是 runner 容器内挂载点 + # 对应 NAS host 路径 /volume1/docker/blog/public + # blog nginx 容器只读挂载同一目录,文件系统层立即同步,无需重启 + run: rsync -av --delete public/ /blog-public/ + + - name: Build summary + if: always() + run: | + { + echo "## Build Summary" + echo "" + echo "| 项 | 值 |" + echo "|---|---|" + echo "| 触发方式 | \`${{ github.event_name }}\` |" + echo "| commit | \`$(git rev-parse --short HEAD)\` |" + echo "| Hugo 版本 | \`0.161.1 extended\` |" + echo "| 文章数 | \`$(ls content/posts/*.md 2>/dev/null | wc -l)\` |" + echo "| public/ 总大小 | \`$(du -sh public 2>/dev/null | cut -f1)\` |" + } >> "$GITHUB_STEP_SUMMARY" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..44efb3b --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +public/ +resources/_gen/ +.hugo_build.lock +.DS_Store +node_modules/ +*.log +.claude/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..22498ab --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "themes/stack"] + path = themes/stack + url = https://github.com/CaiJimmy/hugo-theme-stack.git diff --git a/archetypes/default.md b/archetypes/default.md new file mode 100644 index 0000000..0d5eebd --- /dev/null +++ b/archetypes/default.md @@ -0,0 +1,5 @@ +--- +date: '{{ .Date }}' +draft: true +title: '{{ replace .File.ContentBaseName "-" " " | title }}' +--- diff --git a/assets/img/avatar.jpg b/assets/img/avatar.jpg new file mode 100644 index 0000000000000000000000000000000000000000..121c009a34bd8c1e1490427e9a9570f4825c6700 GIT binary patch literal 73831 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 X{{VpY!n|ise + + + + + + + + + diff --git a/assets/jsconfig.json b/assets/jsconfig.json new file mode 100644 index 0000000..66be563 --- /dev/null +++ b/assets/jsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "paths": { + "*": [ + "..\\themes\\stack\\assets\\*" + ] + } + } +} \ No newline at end of file diff --git a/assets/scss/custom.scss b/assets/scss/custom.scss new file mode 100644 index 0000000..34f290e --- /dev/null +++ b/assets/scss/custom.scss @@ -0,0 +1,48 @@ +// 卡片 padding 比 Stack 默认 20/25/30 略宽一点,保证所有内容(含引用/code/图片) +// 都呆在 padding 内不顶到边 +:root { + --card-padding: 24px; +} + +@media (min-width: 768px) { + :root { + --card-padding: 28px; + } +} + +@media (min-width: 1280px) { + :root { + --card-padding: 32px; + } +} + +// 取消 Stack 默认的"标题 hanging 出 .article-content 左 padding"效果 +// 让 H1-H6 的左竖线不再顶到卡片边框,留出呼吸空间 +.article-content { + h1, h2, h3, h4, h5, h6 { + margin-inline-start: 0; + padding-inline-start: 0.6em; + + // 锚点 # 浮在标题文字左外侧,避免和竖线重叠 + a.header-anchor { + left: auto; + right: 100%; + width: 1.4em; + } + } + + // 取消 Stack 默认让 blockquote / figure / code / table 等"独立块"用负 margin + // hanging 出卡片左右边缘的设计,让所有内容统一呆在 article-content 的 padding 内 + blockquote, + figure, + .highlight, + pre, + .gallery, + .video-wrapper, + .table-wrapper, + .s_video_simple { + margin-left: 0; + margin-right: 0; + width: 100%; + } +} diff --git a/content/_index.md b/content/_index.md new file mode 100644 index 0000000..6fd3c9e --- /dev/null +++ b/content/_index.md @@ -0,0 +1,6 @@ +--- +title: 陶政辰的科普笔记 +toc: false +--- + +技术、科普、和一些不太成熟的思考。 diff --git a/content/about/index.md b/content/about/index.md new file mode 100644 index 0000000..e9fefce --- /dev/null +++ b/content/about/index.md @@ -0,0 +1,36 @@ +--- +title: 关于 +description: 关于陶政辰 +date: 2026-05-03 +--- + +## 你好 👋 + +我是**陶政辰(Zhengchen Tao)**,1996 年生人,山东济南人,现居上海。 + +C# / .NET 工程师,9 年+ 后端经验,从 .NET Framework 4.0 一路写到 .NET 10,做过金融系统(前美国 GreenDot 研发中心)、游戏后端、模块化单体架构。前端 React / Vue 也写得动。业余做点自己的小项目。 + +## 关于这个站 + +这里是我的**笔记 + 科普**外发地。Obsidian 写 markdown 推到 Gitea,Hugo 自动构建到 NAS,nginx 服文件——一条无运行时依赖的静态流水线。 + +写的内容大致几类: + +- **科普长文**——拿读者熟悉的东西打比方,把一件事的"全链路"说清楚。比如[一次 HTTPS 请求的完整旅程](/posts/https-journey/) +- **技术分析**——读源码、踩坑、做选型时的思路记录 +- **工程笔记**——HomeLab、网络架构、AI 落地的工程实践 +- **不太成熟的思考**——大部分被我自己骂回去了 + +不追热点,不写"X 分钟看懂 Y"。一篇技术文如果两小时读不完,我宁可先不发。 + +## HomeLab + +主力机是一台群晖 DS925+ NAS,跑着 NPM、Immich、Gitea + Actions、Container Registry、Passwall 透明代理、ezBookkeeping + MCP、本地 Ollama 等一堆容器。这个博客本身就跑在它上面(nginx:alpine 容器 + NAS 直挂静态目录)。 + +## 联系 + +- 邮箱:zhengchen.tao@outlook.com +- GitHub:[zhengchentao](https://github.com/zhengchentao) +- RSS:[订阅](/index.xml) + +如果文章里有事实/逻辑问题,直接发邮件糊我脸,我喜欢被纠正。 diff --git a/content/archives/_index.md b/content/archives/_index.md new file mode 100644 index 0000000..d728155 --- /dev/null +++ b/content/archives/_index.md @@ -0,0 +1,6 @@ +--- +title: 归档 +description: 文章归档 +layout: archives +slug: archives +--- diff --git a/content/posts/2023-04-11-xray-reality.md b/content/posts/2023-04-11-xray-reality.md new file mode 100644 index 0000000..13ced3f --- /dev/null +++ b/content/posts/2023-04-11-xray-reality.md @@ -0,0 +1,270 @@ +--- +title: "Xray Reality 协议:消除 TLS 指纹的现代代理方案" +date: 2023-04-11 +lastmod: 2026-05-03 +slug: xray-reality +tags: ["TLS", "Xray", "VLESS", "Reality", "X25519", "代理协议"] +categories: ["网络协议"] +description: "REALITY 协议通过 TLS 1.3 key_share 字段嵌入身份标记 + 主动探测时透明回放真站,从协议层消除 TLS 指纹特征。本文从协议设计到服务端 / 客户端完整搭建。" +draft: false +--- + +> 整理自 [bandwh.com](https://www.bandwh.com/net/994.html)(原文 2023-04-11),本文于 2026-05 重新整理发布。 +> 适用系统:Debian 11 | Xray 版本:>= 1.8.0 +> 文中所有 UUID / X25519 密钥均为示例值,实际部署务必使用 `xray uuid` / `xray x25519` 重新生成。 + +--- + +## 一、背景与原理 + +### 1.1 为什么需要 Reality? + +传统 v2ray 方案需要购买域名并生成 TLS 证书,通过各种流量伪装来规避检测。然而随着 DPI 检测能力的升级,**v2ray 的 TLS/XTLS 协议特征已可被精准识别**,导致 VPS 的 443 端口频繁被封锁或阻断。 + +Xray 1.8.0 版本推出了全新的 **REALITY 协议**,配合此前的 **Vision 流控**,组成了当前最新的协议组合: +``` +VLESS + Vision + uTLS + REALITY +``` + +### 1.2 REALITY 的核心优势 + +| 特性 | 说明 | +|------|------| +| 消除 TLS 指纹 | 消除服务端 TLS 指纹特征,令流量与真实网站无异 | +| 前向保密 | 仍保有 TLS 前向保密性,历史流量无法被解密 | +| 抗证书链攻击 | 证书链攻击无效,安全性超越常规 TLS | +| 无需域名 | 指向他人网站的 SNI,无需自己购买域名或配置 TLS | +| 中间人防御 | 即使客户端配置泄露,审查方也无法进行有效中间人攻击 | +| SNI 阻断消失 | 据实测,使用 Reality 后 SNI 阻断现象消失 | + +### 1.3 使用前提 + +- 一台可访问的 VPS(无需域名) +- 服务端与客户端 **Xray 均需 >= 1.8.0 版本** +- 443 端口不被 Nginx、Caddy 等其他程序占用 +- **不支持 CDN 代理**(如 Cloudflare 橙云,会终止 TLS 让 Reality 的端到端伪装失效)。CF **灰云(DNS only)** 只做 DNS 解析、不接管流量,等价于直连 VPS,可正常使用 + +官方 GitHub:https://github.com/XTLS/REALITY + +--- + +## 二、服务端搭建 + +### 2.1 安装 Xray + +通过官方脚本安装指定版本: +```bash +bash -c "$(curl -L https://github.com/XTLS/Xray-install/raw/main/install-release.sh)" @ install --version 1.8.0 +``` + +> `--version 1.8.0` 可按需替换为更新版本号。 +> 安装完成后,Xray 可执行文件位于 `/usr/local/bin/xray`,配置文件位于 `/usr/local/etc/xray/config.json`。 + +### 2.2 生成 UUID + +UUID 用于客户端身份认证: +```bash +cd /usr/local/bin/ +./xray uuid > uuid +cat uuid # 查看生成的 UUID +``` + +### 2.3 生成 X25519 公私钥对 + +REALITY 使用非对称密钥替代传统 UUID 认证,安全性更高: +```bash +cd /usr/local/bin/ +./xray x25519 > key +cat key # 查看生成的公钥(PublicKey)和私钥(PrivateKey) +``` + +> - **PrivateKey(私钥)**:填入服务端配置,务必保密 +> - **PublicKey(公钥)**:填入客户端配置,可多端共享 + +### 2.4 编写服务端配置文件 + +**关键要求:** 回落目标网站(`dest`)必须支持 TLSv1.3,建议使用国外知名大站,本例使用 `www.amazon.com`。 + +配置文件参数说明: + +| 参数 | 必填 | 说明 | +|------|------|------| +| `id` | ✅ | 客户端 UUID,由 `xray uuid` 生成 | +| `flow` | ❌ | 使用 TCP 时填 `xtls-rprx-vision`;H2 协议留空 | +| `dest` | ✅ | 回落的真实境外网站,格式 `域名:443` | +| `serverNames` | ✅ | 客户端可用的 SNI 列表,需与 dest 匹配 | +| `privateKey` | ✅ | 服务端私钥(Private key) | +| `shortIds` | ✅ | 客户端 ID 列表,十六进制,长度为 2 的倍数,上限 16 位 | +| `maxTimeDiff` | ❌ | 允许的最大时间差(ms),`0` 为不限,建议设 10000~60000 | +| `show` | ❌ | 是否输出调试信息,默认 `false`,排查问题时改为 `true` | + +完整配置示例: +```json +{ + "inbounds": [ + { + "listen": "0.0.0.0", + "port": 443, + "protocol": "vless", + "settings": { + "clients": [ + { + "id": "94b60beb-a0fd-4aff-9c7c-9a36f74022db", + "flow": "xtls-rprx-vision" + } + ], + "decryption": "none" + }, + "streamSettings": { + "network": "tcp", + "security": "reality", + "realitySettings": { + "show": false, + "dest": "www.amazon.com:443", + "xver": 0, + "serverNames": [ + "amazon.com", + "www.amazon.com" + ], + "privateKey": "UCWnsGnHIqsCgb10JzaL7TaC9pZKJSSax9vW-QbaVkM", + "minClientVer": "", + "maxClientVer": "", + "maxTimeDiff": 0, + "shortIds": [ + "88", + "888888" + ] + } + } + } + ], + "outbounds": [ + { + "protocol": "freedom", + "tag": "direct" + }, + { + "protocol": "blackhole", + "tag": "blocked" + } + ] +} +``` + +### 2.5 写入配置并启动 + +**写入配置文件:** +```bash +nano /usr/local/etc/xray/config.json +# 粘贴上方配置,Ctrl+X 退出,按 Y 确认保存 +``` + +**服务管理命令:** +```bash +service xray start # 启动 Xray +service xray stop # 停止 Xray +service xray restart # 重启 Xray +service xray status # 查看运行状态 +``` + +### 2.6 排错方法 + +若服务启动报错,可将配置中 `"show": false` 改为 `"show": true`,然后手动运行查看详细日志: +```bash +/usr/local/bin/xray -c /usr/local/etc/xray/config.json +``` + +常见问题排查: +- 检查 443 端口是否被其他程序占用:`ss -tlnp | grep 443` +- 检查配置文件 JSON 格式是否有误(注释需删除) +- 确认 `dest` 目标网站支持 TLSv1.3:`curl -v --tlsv1.3 https://www.amazon.com` + +--- + +## 三、可选:BBR 加速 + +VPS 到国内线路较差时,可安装 BBR 拥塞控制算法提升 TCP 吞吐量: +```bash +wget --no-check-certificate https://github.com/teddysun/across/raw/master/bbr.sh \ + && chmod +x bbr.sh \ + && ./bbr.sh +``` + +安装完成后按提示重启 VPS 即可生效。 + +--- + +## 四、客户端配置 + +### 4.1 客户端通用参数 + +连接服务端时需填写以下关键参数: + +| 参数 | 说明 | +|------|------| +| 地址(Address) | VPS 的 IP 地址 | +| 端口(Port) | `443` | +| 用户 ID | 服务端配置中的 UUID | +| 流控(Flow) | `xtls-rprx-vision` | +| 加密(Encryption) | `none` | +| 传输协议(Network) | `tcp` | +| 安全类型(Security) | `reality` | +| SNI | 与服务端 `serverNames` 一致,如 `www.amazon.com` | +| 公钥(PublicKey) | 服务端生成的 Public key | +| ShortId | 服务端 `shortIds` 中的任意一项,如 `88` | +| uTLS 指纹(Fingerprint) | 建议填 `chrome` 或 `firefox` | + +### 4.2 Windows 客户端(V2rayN) + +- 下载地址:https://github.com/2dust/v2rayN/releases +- 需使用最新版本,确保内置 Xray-Core >= 1.8.0 +- 若版本不足,进入「设置」→ 勾选「检查 Pre-Release 版本更新」后更新核心 +- 操作路径:「服务器」→「添加 [VLESS] 服务器」→ 按上表填写各参数 + +### 4.3 Android 客户端(V2rayNG) + +- 下载地址:https://github.com/2dust/v2rayNG/releases +- 同样需使用支持 Reality 的最新版本 + +### 4.4 路由器端(OpenWrt) + +- 适用于 2023 年 4 月后编译的含 ShadowSocksR Plus+ 的固件 +- 在插件设置界面中选择 VLESS 协议,填写对应的 Reality 参数 + +--- + +## 五、安全性深度解析 + +### 5.1 为什么使用公私钥而非仅 UUID? + +传统方案若使用对称密钥(UUID),攻击者一旦获取客户端配置,即可实施中间人攻击。 + +REALITY 使用 **X25519 非对称密钥 + TLSv1.3 key_share** 机制: +- 即使攻击者获取到客户端公钥,也**无法验证某条连接是否属于 REALITY** +- 更无法进行有效的中间人攻击 + +> REALITY 的设计原则是:**默认假设客户端配置已泄露**,将安全边界收敛至服务端私钥。只要服务端私钥不泄露,流量就是安全的。即使私钥泄露,攻击者也无法直接解密历史流量(前向保密),只能尝试中间人攻击,但中间人需要持有 Reality 私钥才能伪装服务端,这做不到。 + +建议:**定期更换公私钥对**,公钥可在多个客户端间安全共享。 + +### 5.2 如何解决 TLS in TLS 问题? + +"TLS in TLS" 指内层 TLS 握手特征暴露的问题(即加密套娃特征)。 + +REALITY 本身就是 TLS,可直接复用 **XTLS Vision** 的成熟解决方案:Vision 会对内层 TLS 握手包进行**填充处理(不加密,直接发送)**,从而消除 TLS 套 TLS 的可识别特征。 + +此外,HTTP/2 与 gRPC 自带多路复用,也可配合 REALITY 使用,进一步优化网络性能。 + +--- + +## 六、注意事项 + +- Reality **不支持 CDN 代理**(如 Cloudflare 橙云),请勿将域名套 CDN 代理使用;CF **灰云(DNS only)**仅做 DNS 解析不接管流量,等同直连 VPS,可以用(CF 在链路里只起 DNS 提供商作用) +- `dest` 目标网站必须支持 TLSv1.3,建议选用 `www.amazon.com`、`www.microsoft.com` 等国际知名站点 +- 服务端 443 端口在使用期间不能被其他程序(Nginx、Caddy 等)占用 +- 80 端口无特殊要求 +- 技术持续更新,请关注 Xray 官方仓库获取最新版本信息 + +--- + +*本文整理自 [bandwh.com](https://www.bandwh.com/net/994.html),如有更新请以原文为准。* \ No newline at end of file diff --git a/content/posts/2026-04-30-https-journey.md b/content/posts/2026-04-30-https-journey.md new file mode 100644 index 0000000..21b285d --- /dev/null +++ b/content/posts/2026-04-30-https-journey.md @@ -0,0 +1,1286 @@ +--- +title: "一次 HTTPS 请求的完整旅程:从硅片到光纤,从数论到博弈论" +date: 2026-04-30 +slug: https-journey +tags: ["HTTPS", "TLS", "网络", "密码学", "GFW", "科普", "Reality"] +categories: ["科普"] +description: "从指尖按下屏幕,到电子在硅里隧穿、电磁波在空中传播、光子在光纤里复制、数据在海底跨洋、密钥在素数空间里暗藏——一篇关于一次 HTTPS 请求背后整条链路的科普长文。" +draft: false +ShowToc: true +TocOpen: false +--- + +> 一篇关于"按一下手机搜索按钮,到看到结果之间这不到一秒钟,世界上发生了什么"的科普长文。 +> +> 横跨硅片、电磁波、光纤、海底电缆、量子受激辐射、椭圆曲线密码学、防火长城、TLS 拟态、博弈论。 + +## 场景设定 + +你站在上海某个咖啡馆门口,掏出手机(假设是一台开了 5G 的 iPhone),解锁,打开 Chrome,在 Google 搜索框里输入 `weather`,然后点击搜索按钮。 + +不到一秒钟,屏幕上就出现了天气预报。 + +这"不到一秒钟"里发生的事情,可能比你想象的多得多。手机里的几十亿个晶体管做了无数次开关,无线电波在空中飞了一段,光脉冲在玻璃纤维里走了几千公里,十几台路由器和交换机查了表、改了头部、转了发,服务器机房里的网卡、CPU、内存接力处理。中间还涉及好几个数学难题——如果不是它们足够"难",你的搜索内容早就被人偷看了。 + +这篇文档会顺着这个数据包,从你的指尖一路走到 Google 的服务器,再走回来。 + +### 为什么值得花两个小时读完 + +互联网是人类工程史上最大的协作产物。它能稳定运转,不是因为某一个绝顶聪明的人造了它——而是因为**几代人、来自完全不同领域、彼此往往互不相识的人,把各自学科的最难一块,刚好拼在了一起**。 + +- 物理学家说"光在玻璃里靠全反射不漏",于是有了光纤 +- 数学家说"模幂运算正算容易、反算极难",于是有了 HTTPS +- 19 世纪一个 20 岁就死于决斗的法国人(伽罗瓦)随手研究的代数结构,200 年后变成了 AES 加密的核心 +- 1917 年爱因斯坦为搞清楚光的统计行为提出"受激辐射",70 年后让海底光缆能跨越太平洋 + +读完这篇,**你不会变成网络工程师**——但你会得到一种**横向看穿整条链路**的视角:从指尖按下屏幕、电子在硅里隧穿、电磁波在空中传播、光子在光纤里复制、数据在海底跨洋、密钥在素数空间里暗藏,到 GFW 的旁路镜像、Reality 的 TLS 拟态——它们其实是同一条故事线上的不同章节。 + +每一次你打开手机搜东西,这整套机制都为你跑了一遍。看完这篇你大概会觉得——"互联网每秒能让几十亿人都查到天气这件事,本身就接近魔法"。 + +### 怎么读这篇 + +文档很长(约 1100 行),不需要一口气读完: + +- **不熟技术**:重点看[第 0 章](#第-0-章-全景)(全景)、[每章末尾的"这一章的物理 / 数学"小结]、[附录 C](#附录-c-底层物理与数学原理速览)(原理速览),其余按兴趣挑章读 +- **想看密码学怎么工作**:直奔[第 9 章](#第-9-章-tls-握手让中间人看不懂)(TLS 握手) +- **想理解翻墙原理**:直奔[第 6 章](#第-6-章-gfw-与翻墙一场-https-拟态的猫鼠游戏)(GFW 与 Reality) +- **只想看哲思**:看[场景设定](#场景设定)、[附录 D](#附录-d-那些刚刚好的巧合)("刚刚好"的巧合)、[写在最后](#写在最后) +- **要查公式**:[附录 A](#附录-a-关键公式速查)是分类速查表 + +公式不必逐条算明白。整篇文档的目标不是教你"怎么算",是让你看见"哪条物理 / 数学定律,在这条链路的哪一步,默默地起着什么作用"。 + +--- + +## 目录 + +- [第 0 章 全景](#第-0-章-全景) +- [第 1 章 起点:手机内部](#第-1-章-起点手机内部) +- [第 2 章 第一次握手:DNS 查询](#第-2-章-第一次握手dns-查询) +- [第 3 章 接入层:5G 把数据送上天](#第-3-章-接入层5g-把数据送上天) +- [第 4 章 接入网:基站到核心网](#第-4-章-接入网基站到核心网) +- [第 5 章 城域网与骨干网:光纤里的高速公路](#第-5-章-城域网与骨干网光纤里的高速公路) +- [第 6 章 GFW 与翻墙:一场 HTTPS 拟态的猫鼠游戏](#第-6-章-gfw-与翻墙一场-https-拟态的猫鼠游戏) +- [第 7 章 跨洋:海底电缆](#第-7-章-跨洋海底电缆) +- [第 8 章 TCP 三次握手:在虚拟世界里建立连接](#第-8-章-tcp-三次握手在虚拟世界里建立连接) +- [第 9 章 TLS 握手:让中间人看不懂](#第-9-章-tls-握手让中间人看不懂) +- [第 10 章 数据中心入口](#第-10-章-数据中心入口) +- [第 11 章 服务器内部](#第-11-章-服务器内部) +- [第 12 章 丢包、误码与补救](#第-12-章-丢包误码与补救) +- [第 13 章 硬件加速汇总](#第-13-章-硬件加速汇总) +- [第 14 章 返回:数据包的回家路](#第-14-章-返回数据包的回家路) +- [附录 A 关键公式速查](#附录-a-关键公式速查) +- [附录 B 涉及的硬件芯片举例](#附录-b-涉及的硬件芯片举例) +- [附录 C 底层物理与数学原理速览](#附录-c-底层物理与数学原理速览) +- [附录 D 那些"刚刚好"的巧合](#附录-d-那些刚刚好的巧合) + +--- + +## 第 0 章 全景 + +在我们钻进任何一层细节之前,先看一眼这个数据包要走完的路径: + +``` +你的手指 → 触摸屏 → CPU → 内存 → 基带芯片 → 射频前端 → 天线 + → 5G 基站(gNodeB) → 5G 核心网 → 运营商城域网 → 骨干网 + → 国际出口 → 海底光缆 → 美国登陆站 → Google 数据中心入口 + → 边缘路由器 → 负载均衡器 → 接入服务器 → 应用服务器 +``` + +物理上,这一路涉及:几十亿个晶体管、几十根毫米级天线、几公里到几千公里的光纤、几十个集成电路芯片、几台机柜大小的设备、可能还有一段几千公里长的海底电缆。 + +软件上,涉及:URL 解析、DNS 协议、TCP/IP 协议栈、TLS 加密、HTTP 协议、HTML 渲染。 + +数学上,涉及:傅里叶变换、椭圆曲线密码学、Reed-Solomon 编码、LDPC 码、Diffie-Hellman 密钥交换、AES 分组密码、矩阵运算(MIMO)、信息论(香农极限)。 + +物理上更深一层,这些技术全都建立在几条最底层的定律上:**Maxwell 方程组**(电磁波怎么传播)、**量子力学**(晶体管为什么能开关、激光为什么能发出)、**热力学第二定律**(噪声为什么消不掉)、**全反射**(光为什么不会从光纤侧面漏出去)、**受激辐射**(EDFA 怎么把光放大)。 + +数学上还有几个不那么显眼但更基础的支柱:**数论**(质数、模幂、欧拉定理——所有公钥密码的根基)、**群论 / 有限域**(椭圆曲线、AES 的 GF(2⁸) 列混合)、**计算复杂性理论**(为什么"加密容易、破解难")、**信息论**(香农定理告诉我们一个信道最多能传多少)、**线性代数**(MIMO 的信道矩阵 / 波束赋形)、**排队论**(路由器缓冲、TCP 拥塞控制)。 + +完整的底层原理速览见 [附录 C](#附录-c-底层物理与数学原理速览),不想被卡在公式里的话直接跳过去看那张分类表也行。下面我们一层一层往下看,每章末尾会用一小节点出"这一步背后是哪条定律 / 哪种数学"。 + +### 0.1 顺手把 HTTPS 拆成它的零件 + +整篇文档的标题里写着 **HTTPS**,但 HTTPS **不是一个独立的协议**,而是好几个协议**叠在一起**的总称。先把这一点讲清楚,后面所有章节的位置感才对得上。 + +| 层 | 协议 | 干什么 | 在哪一章细讲 | +|----|------|--------|------------| +| 应用层 | **HTTP** | 发的是什么内容(GET / POST、URL、headers、body) | §1.2 浏览器准备 HTTP 请求 | +| 安全层 | **TLS** | 给应用层加密 + 验证身份(HTTPS 里那个**"S"**) | 第 9 章 | +| 传输层 | **TCP** | 把字节流可靠送达(三次握手、重传、拥塞控制) | 第 8 章、第 12 章 | +| 网络层 | **IP** + 路由 | 寻址,让包能跨网络走 | 第 5 章 | +| 物理层 | 5G / 光纤 / 铜缆 | 真把电磁波 / 光子送到对面去 | 第 1-3 章、第 7 章 | + +一句话总结:**HTTPS = HTTP 跑在 TLS 里,TLS 跑在 TCP 里,TCP 跑在 IP 里,IP 跑在物理介质里**。 + +具体到你输入 `https://www.google.com` 这一刻,实际发生的是: + +1. **建管道**:你和 Google 之间用 TCP 三次握手建一条字节流通道(第 8 章) +2. **加密管道**:在这条 TCP 通道里用 TLS 握手再套一层加密 + 身份验证的"暗管"(第 9 章) +3. **送内容**:你的 HTTP 请求(`GET /search?q=weather HTTP/1.1\r\nHost: www.google.com\r\n...`)的字节流,从这条暗管里钻过去——中间路过的人能看到流量、看不到内容 +4. **拆暗管**:Google 服务器在另一端把暗管解开,看到 HTTP 请求,处理后把响应原路塞回去 + +剥掉 TLS,就是 80 端口的明文 **HTTP**,中间任何路由器、运营商、咖啡店 Wi-Fi 都能看;加上 TLS,就是 443 端口的 **HTTPS**,内容只有你和 Google 看得到。**这一字之差,养活了密码学这一整门学科,也撑起了整个第 9 章和第 6 章 GFW 与 Reality 的猫鼠游戏**——后者本质上就是一场"如何让 HTTPS 看起来更像普通 HTTPS"的工程对抗。 + +记住这张五层栈,下面的每一章你都能定位到自己在哪一层:**第 1-3、7 章在物理层底下做事,第 5 章是网络层 IP 怎么找路,第 8 章管 TCP 建管道,第 9 章管 TLS 加密,第 6 章管"管道在国境线被卡时怎么办"**。 + +--- + +## 第 1 章 起点:手机内部 + +### 1.1 触摸屏识别到了你的点击 + +你的手指按在屏幕"搜索"按钮的位置。屏幕表面是一层电容式触控层,由透明的氧化铟锡(ITO)电极组成网格。你的手指带着微弱的电荷,改变了某个交叉点的电容值。触控芯片(在 iPhone 里是苹果自研的多点触控控制器)以 120Hz 扫描整个网格,检测到了变化,把"在 (x, y) 处发生了一次触摸"这条事件通过 SPI 或 I2C 总线发给主 SoC(A 系列芯片)。 + +**底层原理**:平板电容公式 `C = ε × A / d`(电容 = 介电常数 × 极板面积 / 极板间距)。你的手指本身是个含水量很高的导体,贴近屏幕时相当于在原本的电极对上叠加了一个新的"对地电容",于是 `C` 突然变大。触控芯片本质上是用一个 RC 振荡电路或电荷转移电路在每个交叉点测电容,任何一个点的电容偏离基线就报点。所以"湿手"或者"戴手套"会失灵——前者改变了介电常数,后者切断了导电通路。 + +主 SoC 的 CPU(目前是 ARMv9 架构)收到中断,iOS 的内核唤醒 Chrome 进程,把事件传给浏览器的 UI 线程。浏览器的事件处理代码识别出这是一次"点击搜索"操作。 + +**晶体管在干什么**:这个 SoC 里有大约 200 亿个晶体管,基本结构是 MOSFET(金属氧化物半导体场效应管)。它的开关原理是:在栅极加正电压,栅极下的硅衬底会形成一层"反型层",导通源极和漏极;撤掉电压,通道消失,断开。这是量子力学允许的现象——电子在能带结构里被电场推到导带。**先进工艺(3nm)下,栅极厚度只有几个原子层**,电子有一定概率"穿过"绝缘层而不是被挡住,这叫**量子隧穿**,也是为什么再往下做工艺会有漏电墙——物理定律开始反对你了。 + +### 1.2 浏览器准备 HTTP 请求 + +Chrome 内部知道你要去 `https://www.google.com/search?q=weather`。它先做几件事,全在 CPU 和内存里完成,没有任何网络流量: + +**URL 解析**:按 RFC 3986 把 URL 拆成五部分——`scheme=https`、`host=www.google.com`、`port=443`(默认)、`path=/search`、`query=q=weather`。 + +**HSTS 检查**:浏览器内置一份"必须用 HTTPS"的网站列表(HSTS preload list),Google 在里面。即使你输入的是 `http://`,也会被强制升级到 `https://`。 + +**缓存查找**:浏览器先看自己有没有这个域名的 DNS 记录、有没有可复用的 TCP 连接、有没有可复用的 TLS 会话。如果有,就能跳过后面好几个阶段。 + +我们假设全部没命中,需要从头走一遍。 + +### 1.3 数据从 CPU 流向基带芯片 + +Chrome 调用 iOS 的 `Network.framework`,最终落到 BSD socket 接口。内核构造 TCP/IP 数据包,但还不能直接发——还得先解析域名。 + +域名解析的请求(一个 DNS 查询包)被内核交给"网络硬件抽象层",最终经过内部总线送到基带芯片(modem)。 + +iPhone 的基带芯片目前是高通 X 系列(比如 X75 调制解调器),它是一块独立的大芯片,通过 PCIe 总线和主 SoC 通讯。基带芯片专门负责"和无线世界打交道",它内部有自己的 ARM 处理器、DSP(数字信号处理器)、专用的物理层加速电路。 + +主 SoC 把要发的数据通过 DMA(直接内存访问)的方式写到基带芯片能读的内存区域,然后给基带一个中断信号说"有新数据了"。 + +**这里多拆一层:数据是怎么"在芯片间走"的**。SoC 和基带之间是 PCIe 总线,本质上是几对差分信号线(每对两根,一正一负,值靠两根之间的电压差判读,抗干扰),每秒切换几十亿次,每次切换就是一个比特。芯片内部 0 和 1 是 CMOS 电路里两个电压电平(典型 0V 和 0.8V),靠 PMOS / NMOS 互补开关把电平翻过去——这又回到 MOSFET 的事。 + +到这里,数据还是数字形式,以 0 和 1 的电信号在芯片间传递。下一步,要变成无线电波了。 + +**这一章的物理 / 数学**:电容物理(`C = εA/d`)→ 触摸识别;半导体能带理论 + MOSFET 反型层 → 晶体管开关;量子隧穿 → 工艺微缩的物理边界;CMOS 互补对称 → 数字电路的电平表示。 + +--- + +## 第 2 章 第一次握手:DNS 查询 + +### 2.1 为什么要查 DNS + +互联网上的服务器是用 IP 地址定位的,比如 `142.250.71.46`。但人记不住这种数字,所以发明了域名,需要一个"翻译系统"把域名翻译成 IP——这就是 DNS(Domain Name System)。 + +### 2.2 查询过程 + +你的手机里装着运营商分配的 DNS 服务器地址(在 5G 时代经常是 `223.5.5.5` 阿里 DNS 或 `114.114.114.114`)。手机把"www.google.com 的 IP 是多少?"这个问题发给这台 DNS 服务器,这台服务器叫"递归解析器"。 + +如果递归解析器自己缓存里没有答案,它会代你去问: + +1. 先问根服务器(全球 13 组,实际有几百台,用 anycast 技术让你访问最近的一台):"www.google.com 谁管?"根服务器答:"我不知道,但 .com 顶级域归这堆服务器管,你去问它们。" +2. 再问 .com 顶级域服务器:"www.google.com 谁管?".com 答:"google.com 这个域是这堆权威服务器管,问它们去。" +3. 最后问 google.com 的权威服务器(由 Google 自己运营):"www.google.com 的 IP 是多少?"权威服务器答:"142.250.71.46"。 + +整个过程,递归解析器记下答案缓存若干分钟,然后把结果返回给你的手机。 + +### 2.3 DNS 包长什么样 + +DNS 是一个非常紧凑的二进制协议,默认走 UDP 53 端口。一个标准查询大约 60-80 字节: + +- **12 字节头部**:16 位事务 ID(用于匹配请求和响应)、标志位(查询/响应、递归/迭代等)、问题数、答案数等。 +- **问题段**:把域名编码成"长度+内容"的形式。`www.google.com` 编码为 `\x03www\x06google\x03com\x00`。 +- **类型和类**:2 字节类型(A 记录=1,代表 IPv4 地址)、2 字节类(IN=1,互联网)。 + +UDP 头再加 8 字节、IP 头再加 20 字节、以太网/无线帧头再加几十字节,这个 DNS 查询就准备好出发了。 + +### 2.4 现代加密 DNS + +传统 DNS 是明文的,运营商和中间任何人都能看见、能篡改。所以现在有了加密 DNS: + +- **DoT(DNS over TLS)**:走 853 端口,在 TLS 加密通道里发 DNS 查询。 +- **DoH(DNS over HTTPS)**:走 443 端口,把 DNS 查询封装在 HTTPS 请求里。Google、Cloudflare 都提供。 + +iPhone iOS 14 之后默认支持 DoH。我们假设你的手机用了 DoH,那么 DNS 查询本身就要先做一次完整的 TLS 握手——不过这不重要,我们就当它已经查到了 `www.google.com → 142.250.71.46`。 + +--- + +## 第 3 章 接入层:5G 把数据送上天 + +现在重点来了。手机要把"打开 142.250.71.46 的 443 端口"这件事告诉基站。 + +### 3.0 先把"无线电波"是什么搞清楚 + +无线电波、可见光、X 射线、伽马射线,都是一种东西——**电磁波**,只是频率不同。它们的存在与传播,被一组方程组完全描述,这就是 1865 年 **詹姆斯·克拉克·麦克斯韦(James Clerk Maxwell)** 写下的 **Maxwell 方程组**:四条偏微分方程,告诉你电场和磁场怎么互相激发、怎么在真空里以光速 `c ≈ 3 × 10⁸ m/s` 向四周扩散。整个无线通信、光纤通信、雷达、GPS、Wi-Fi、5G,都建立在这四条方程上,Maxwell 当年并不知道这些会被发明出来——直到 1887 年 **赫兹(Heinrich Hertz)** 才在实验室里第一次造出了无线电波。 + +频率(`f`,Hz)和波长(`λ`,m)的关系简单到只有一个公式: + +``` +c = λ × f +``` + +代进去:5G n78 频段载波 3.5 GHz,对应波长大约 8.6 厘米。毫米波 28 GHz 对应波长 1 厘米。Wi-Fi 5GHz 对应 6 厘米。可见光 500 THz 对应 600 nm。手机里的天线长度是波长的 1/4 或 1/2 量级——这就是为什么 5G 频段越高,手机里的天线就越小,基站的天线阵列也越能塞进更多根。 + +电磁波从天线发出去后能量按 1/r² 扩散(球面),所以越远信号越弱。**Friis 路径损耗公式**给出"理想条件下接收功率": + +``` +P_r = P_t × G_t × G_r × (λ / 4πr)² +``` + +`P_t / P_r` 是发射 / 接收功率,`G_t / G_r` 是天线增益,`r` 是距离。可以记住一条直觉:**距离翻倍,功率衰减 4 倍(6 dB)**。这条公式给运营商规划基站密度。 + +### 3.1 基带芯片在干什么 + +基带芯片(modem)拿到要发的数据后,要做一系列复杂的处理才能交给天线发出去。这套处理流程叫"物理层处理"(PHY processing),大致顺序是: + +``` +原始数据 + → CRC 加校验码 + → 信道编码(纠错码) + → 速率匹配 + → 加扰 + → 调制(把比特变成复数) + → 资源映射(放到时频网格里) + → OFDM 调制(变成时域信号) + → 数模转换(DAC) + → 射频信号 +``` + +每一步都有专门的硬件电路或 DSP 加速。下面挑几个最关键的展开。 + +### 3.2 信道编码:5G 用的是 LDPC 和 Polar + +无线信道是出了名的不可靠——会衰减、会反射、会被其他信号干扰、会被一辆开过去的卡车挡住。要在这种信道上可靠地传数据,必须加冗余。这就是"信道编码"或叫"前向纠错"(FEC,Forward Error Correction)。 + +**LDPC 码**(低密度奇偶校验码)用于 5G 的数据信道。原理是给原始数据加上一堆奇偶校验位,这些校验位之间的关系可以用一个稀疏矩阵 H 描述。接收端如果发现某些位变了,可以通过迭代算法(比如置信传播算法)推算出原始数据。LDPC 码的性能可以非常接近"香农极限"。 + +**Polar 码**(极化码)是华为主导推动进入 5G 标准的,用于控制信道。原理是利用"信道极化"现象,把多个并行信道转换成"几乎完全可靠"和"几乎完全不可靠"两类,然后只在可靠的那些信道上传数据。 + +**香农极限**的公式是: + +``` +C = B × log₂(1 + SNR) +``` + +其中 C 是信道容量(bps),B 是带宽(Hz),SNR 是信噪比。这个公式 1948 年由 **克劳德·香农(Claude Shannon)** 提出,被称为**香农定理(Shannon-Hartley Theorem)**,告诉我们一个信道理论上能传多少数据。LDPC 和 Polar 码就是为了尽可能逼近这个极限。 + +### 3.3 调制:把比特变成复数 + +调制就是把数字比特"塞"到电磁波里。最简单的方式是 **BPSK**(二相相移键控),用波形相位 0° 表示比特 0,180° 表示比特 1,每个符号传 1 比特。 + +5G 用的是 **QAM**(正交幅度调制),把幅度和相位结合起来。比如 64-QAM 把信号点摆在 8×8 的网格上,每个点对应 6 个比特;256-QAM 是 16×16 网格,每个点对应 8 个比特;5G 最高支持 1024-QAM,每个符号传 10 比特。 + +调制阶数越高,传得越快,但对信噪比要求也越高——网格点越密,稍微一点噪声就会被识别成相邻的点,出错。所以基站会根据你的信号质量动态选用不同的调制方式:信号好就用 1024-QAM,信号差就降到 64-QAM 或 16-QAM。 + +### 3.4 OFDM:把数据撒在好几个频率上同时发 + +**OFDM**(正交频分复用)是 4G/5G 的核心物理层技术。它的思路是:不要在一个频率上以非常高的速率发数据,而是把可用带宽分成几百几千个"子载波",每个子载波以较低的速率发,所有子载波同时发。 + +这样有几个好处:每个子载波速率慢,符号周期长,不容易被多径反射干扰;可以根据每个子载波的信道质量动态分配功率和数据;频谱利用率高。 + +OFDM 的核心数学工具是 **IFFT(快速傅里叶逆变换)**。基带芯片把要发的几千个子载波数据组成一个频域向量,做一次 IFFT,就得到要发的时域信号。这一步是计算密集型的,基带芯片里有专门的 FFT 硬件加速电路。再往上一层数学根是 **傅里叶变换**——任何信号都能拆成一堆不同频率正弦波的叠加,这条 1822 年由 **约瑟夫·傅里叶(Joseph Fourier)** 在研究热传导时提出的工具,后来撑起了几乎所有的数字信号处理。 + +公式上,OFDM 信号是: + +``` +s(t) = Σ Xₖ × exp(j2πkΔf·t) (k 从 0 到 N-1) +``` + +其中 Xₖ 是第 k 个子载波上的复数符号,Δf 是子载波间隔(5G 中可以是 15kHz、30kHz、60kHz 等),N 是子载波总数。 + +### 3.5 射频前端和天线 + +OFDM 信号还是数字的(IQ 两路实数信号)。基带芯片把它送到射频收发器(RF transceiver),DAC(数模转换器)把它变成模拟信号,混频器把它搬到目标载频(比如 5G n78 频段是 3.5GHz),然后送到射频前端。 + +射频前端是手机里专门处理"高频小信号"的一组芯片,包括: + +- **功率放大器(PA)**:把信号放大到几百毫瓦,才有能力发到几公里外的基站。手机的 PA 通常是砷化镓(GaAs)或氮化镓(GaN)工艺,因为硅在高频下效率不行。 +- **滤波器**:去掉杂散信号,只保留目标频段。手机里塞了几十个滤波器,因为要支持各种 4G/5G 频段。常见的是声表面波(SAW)和体声波(BAW)滤波器。 +- **天线开关**:在多根天线之间切换。 + +iPhone 的射频前端芯片以前主要来自 Skyworks 和 Qorvo,现在苹果在自研。 + +最后,信号驱动天线。iPhone 里有四根主要天线,分布在边框里(那些"白带子"就是塑料填充,让金属边框断开做天线)。5G 毫米波版本(主要是美版)还有专门的毫米波天线模组。 + +电磁波就这样从你的手机辐射出去了,以光速向四面八方传播。 + +**这一章的物理 / 数学**:Maxwell 方程组(电磁波存在的基础)→ 一切无线通信;`c = λf`(频率 / 波长换算)→ 决定天线尺寸;Friis 公式(自由空间路径损耗)→ 基站规划密度;傅里叶变换 / IFFT(时频转换)→ OFDM 的核心;香农定理 `C = B log₂(1+SNR)`(信道容量上限)→ 编码 / 调制设计的目标;LDPC / Polar(信道编码)→ 在嘈杂信道里保住数据;QAM 星座图(几何上把比特排到复平面)→ 高阶调制。 + +--- + +## 第 4 章 接入网:基站到核心网 + +### 4.1 基站收到了你的信号 + +你周围可能有好几个 5G 基站,你的手机会持续测量哪个信号最好,优先连那个。基站(在 5G 里叫 gNodeB,简称 gNB)是一组安装在铁塔或楼顶的设备,主要由两部分组成: + +- **AAU(有源天线单元)**:装在铁塔顶部,集成了天线阵列和射频电路。5G 基站用 64T64R(64 收 64 发)甚至更大规模的 **Massive MIMO** 天线阵列。 +- **BBU(基带处理单元)**:装在铁塔下面的机房或机柜里,是真正做数字信号处理的地方。 + +AAU 和 BBU 之间用光纤连接,接口标准叫 **CPRI** 或 **eCPRI**。 + +### 4.2 Massive MIMO 和波束赋形 + +5G 基站为什么要这么多根天线?因为要做"波束赋形"(beamforming)。 + +普通天线是全向辐射,能量浪费在不需要的方向上。Massive MIMO 通过让 64 根天线发出相位略有差异的信号,让信号在空间中"叠加"出一个指向你手机方向的窄波束。其他方向上信号会因为相位抵消而变弱。 + +**底层原理是波的干涉**:两列相位相同的波叠加,振幅加倍(相长干涉);相位相反,完全抵消(相消干涉)。这是 1801 年 **托马斯·杨(Thomas Young)** 用双缝实验展示的经典波动现象。Massive MIMO 把它放大到 64 维:每根天线的相位是一个旋钮,调好这 64 个旋钮,就能让所有波在你这个点相加,在别的点互相抵消。手机之所以收得到信号,是因为 64 个射出去的波恰好在你手机这一寸空间里"相长干涉"。 + +这本质上是数学上的相干叠加。基站要根据测得的信道矩阵 H,计算出一个加权矩阵 W,使得发射信号 s 在你的位置上能量最大: + +``` +y = HWs + n +``` + +其中 y 是你的手机收到的信号,n 是噪声。要选合适的 W,常用的方法有: + +- **最大比合并(MRC)**:W = H^H(H 的共轭转置) +- **迫零(ZF)**:W = (H^H × H)⁻¹ × H^H +- **MMSE**(最小均方误差):在 ZF 基础上考虑噪声,对小奇异值不那么"狠" + +这些都是矩阵运算,基站里有专门的 DSP 或 FPGA 加速。 + +### 4.3 BBU 里发生的事 + +BBU 收到 AAU 送来的信号后,做的事情和你的手机基带芯片做的事情正好反过来:解调、解扰、信道解码、CRC 校验、最后还原出原始的数据包。 + +如果 **CRC 校验**失败,基站会发一个 **NACK**(否定确认)给手机,触发 **HARQ**(混合自动重传请求):手机不会简单地把整个包重新发一遍,而是把不同的"冗余版本"发过去。基站把多次收到的版本合并解码——这叫"软合并",比单纯重传效率高得多。 + +CRC 校验通过后,数据包从物理层往上走,经过 MAC 层、RLC 层、PDCP 层、SDAP 层(这些都是 5G 协议栈的层级),最终交给 5G 核心网。 + +### 4.4 5G 核心网 + +5G 核心网(5GC)是一组运行在云上的网络功能(NF),包括: + +- **AMF**(接入和移动性管理):管你的手机注册、鉴权、移动切换 +- **SMF**(会话管理):管你的数据会话 +- **UPF**(用户面功能):真正转发你的数据包 + +你的数据包经过基站之后,会走一条 GTP 隧道(GPRS 隧道协议)到达 UPF。UPF 把外层的隧道头剥掉,露出原本的 IP 包(目的地是 `142.250.71.46`),然后通过运营商的路由器转发出去。 + +到这里,你的数据包终于"进入了互联网"。 + +**这一章的物理 / 数学**:波的相干干涉(双缝实验级别的物理) → Massive MIMO 波束赋形;线性代数 / 矩阵伪逆 → MRC、ZF、MMSE 的求解;CRC 多项式除法(GF(2) 上)→ 错误检测;HARQ 软合并 → 多次接收信号的统计累积(信噪比相加,在信息论里叫"互信息累积")。 + +--- + +## 第 5 章 城域网与骨干网:光纤里的高速公路 + +### 5.1 数据包出了 UPF 之后 + +UPF 的位置通常在运营商的城域网入口。从这里开始,你的数据包要经过几层路由器: + +``` +UPF + → 城域网汇聚路由器 + → 城域网核心路由器 + → 省级骨干网路由器 + → 国家骨干网路由器 + → 国际出口 +``` + +每经过一台路由器,有几件事发生: + +1. 路由器收到一个以太网帧,剥掉以太网头,看到 IP 头。 +2. 看 IP 头里的目的地址 `142.250.71.46`。 +3. 在自己的路由表里查这个地址应该往哪里转发。 +4. 把 IP 头里的 TTL(生存时间)减 1,重算 IP 头校验和。 +5. 重新封装一个新的以太网头(下一跳的 MAC 地址),从对应的端口发出去。 + +这个过程叫"路由转发",每经过一跳大约耗时几十微秒。 + +### 5.2 路由器是怎么做到线速转发的 + +一台运营商核心路由器(比如华为 NE9000、思科 ASR 9000)的处理能力是恐怖的:每秒处理几亿到几十亿个数据包。这种速度普通 CPU 是绝对达不到的,靠的是专用的转发 ASIC(专用集成电路)。 + +路由器内部一般是这样的架构: + +- **线卡**:每张线卡有几十个端口,每个端口连一根光纤。线卡上有自己的转发 ASIC 和内存。 +- **背板**:把所有线卡连起来的高速交换矩阵。 +- **主控板**:跑路由协议(BGP、OSPF 等),计算路由表。 +- **TCAM**(三态内容寻址存储器):一种特殊的内存,可以并行匹配大量规则,做"最长前缀匹配"非常快。 + +路由表查找的核心算法叫"最长前缀匹配"(Longest Prefix Match)。比如目的 IP 是 `142.250.71.46`,路由表里可能有: + +- `142.250.0.0/16` → 出口 A +- `142.250.71.0/24` → 出口 B +- `0.0.0.0/0`(默认路由) → 出口 C + +应该选最长(最具体)的那条,也就是 `142.250.71.0/24` → 出口 B。TCAM 能在一个时钟周期内并行比较所有条目,找出最匹配的。 + +### 5.3 路由协议:路由表怎么来的 + +路由器的转发表不是手动配的,而是路由协议自动算出来的。运营商内部用 **OSPF** 或 **IS-IS**(都是基于 **Dijkstra 最短路径**的链路状态协议),运营商之间用 **BGP(边界网关协议)**(本质上是一种"路径向量"协议,加了大量人为策略)。 + +简单说:整个互联网是几万个"自治系统"(AS,Autonomous System)拼起来的,每个 AS 有一个号(中国电信是 AS4134,Google 是 AS15169)。BGP 让 AS 之间互相宣告"我能到哪些 IP 段",每个 AS 根据收到的信息选出"到任意目的地的最佳路径"——所以整个互联网的路径选择,本质是**图论里的最短 / 最优路径问题**叠上了商业关系策略层。 + +到这里,你的数据包正在从中国的某个城市的一台路由器,跳着跳着前往海岸线。 + +### 5.4 光纤里的物理传输 + +路由器之间用光纤连接,长距离链路用 **DWDM(密集波分复用)** 技术——把好几十个不同波长的激光复用在同一根光纤里同时传输,容量倍增。 + +光纤本身是一根头发丝粗细的玻璃纤维,中心是高折射率的纤芯,外层是低折射率的包层。光在纤芯里靠**全反射**传播,几乎不损耗——好的单模光纤衰减只有 0.2 dB/km(意思是 100km 后信号还剩约 1%)。 + +全反射靠的是 **斯涅尔定律(Snell's Law)**: + +``` +n₁ × sin(θ₁) = n₂ × sin(θ₂) +``` + +当光从高折射率介质(n₁)射向低折射率介质(n₂)时,如果入射角足够大,折射角会变成 90° 甚至更大,光就被"反射"回来了。这个临界角是 `θ_c = arcsin(n₂/n₁)`。 + +**光到底是什么**:**Maxwell 方程组**给出的视角是"传播中的电磁波";**量子力学**给出的视角是"一份份能量包,叫光子(photon)"。一个光子的能量是 `E = h × f`(**普朗克常数** × 频率,**马克斯·普朗克(Max Planck)** 1900 年提出),通信常用的 1550 nm 波长,单个光子能量约 0.8 eV ≈ 1.3 × 10⁻¹⁹ 焦耳。一根 100 Gbps 光纤每秒大约要发 10²⁰ 量级的光子。光的"波动性"(Maxwell)和"粒子性"(光子)在不同尺度上各自适用——光纤里的传播用 Maxwell 算最方便,EDFA 放大的过程必须用量子语言才能讲清。 + +光信号传几十公里就会衰减,所以每隔一段距离要加一个**光放大器**。最常用的是 **EDFA(掺铒光纤放大器)**,原理是给一段掺了铒元素的光纤通入泵浦激光,让铒离子被激发,经过的信号光会触发**受激辐射**,从而被放大——整个过程是纯光学的,不需要"光转电再转光",速度快、噪声低。 + +**EDFA 的量子物理再拆一层**:铒离子(Er³⁺)的电子有几个能级。泵浦激光(典型 980nm 或 1480nm)把电子从低能级"打"到高能级,这叫**粒子数反转**——正常情况下电子都呆在低能级,反转之后高能级反而塞满了。当一个 1550nm 的信号光子穿过这段光纤时,它会"诱导"高能级的电子掉下来,掉下来时多放出一个**和入射光子一模一样的光子**(频率、相位、方向、偏振全相同)——这就是 **阿尔伯特·爱因斯坦(Albert Einstein)** 1917 年提出的**受激辐射(stimulated emission)**,激光的"LASER"里那个 SE 就是它。一来一回,光子数翻倍。所以 EDFA 不是把光"加亮",而是把光子"复制"。这条原理同样支撑着所有激光器、光通信、原子钟。 + +### 5.5 海底电缆登场 + +如果你的目的地是 Google,而 Google 的服务器在美国,那你的数据包要通过海底电缆出国。 + +中国大陆有几条主要的海底电缆出口,大部分集中在上海(崇明、南汇)、青岛、汕头。通往美国的主要电缆有 TPE(跨太平洋快线)、APG(亚太直达海缆)、NCP(新跨太平洋海缆)等。 + +**这一章的物理 / 数学**:Maxwell 方程组(光是电磁波)+ 折射定律(斯涅尔)→ 光纤全反射;光子能量 `E = hf`(量子)→ 单光子级别的极限灵敏度;受激辐射(爱因斯坦,1917)→ EDFA 全光放大;BGP 路径选择 → 图论上的最短/最优路径问题;TCAM 最长前缀匹配 → 字典树(trie)的硬件并行化版本。 + +--- + +## 第 6 章 GFW 与翻墙:一场 HTTPS 拟态的猫鼠游戏 + +> 前 5 章讲的是全球通用的链路;这一章专门讲中国大陆的特殊关口——**国际出口处的 GFW**,以及它催生出的一整套"HTTPS 拟态"工程。如果你只关心通用原理,可以直接跳第 7 章;但里面有几个工程巧思(用真实 TLS 握手做掩护、对探测请求原样转发)非常值得一读,密码学之外还能看到博弈论是怎么落地的。 + +### 6.1 GFW 是什么、装在哪里 + +**GFW(Great Firewall,防火长城)** 不是一台机器,而是一组分布式部署在中国国家级骨干网与国际出口处的旁路审查设备 + 主动探测系统的总称。 + +它的物理位置在 §5.1 那张转发链的尾端——城域网汇聚到骨干网,骨干网再汇聚到国际出口路由器,GFW 就接在这些出口路由器**旁边**。注意是"旁边"不是"路上":真正的转发不经过 GFW(否则一坏整个国家断网),GFW 用光分路器把所有过境流量复制一份去做镜像分析。它检测到违规就在路径上**注入伪造的 RST 包**把 TCP 连接撕掉,或者通知上游设备做 BGP 黑洞、ACL 丢包。 + +它的能力大致这几样: + +- **DNS 污染 / 投毒**:对敏感域名,GFW 比真权威 DNS 服务器**更早一步**把伪造答案塞回来。你查 `www.google.com`,可能秒收到一个根本不是 Google 的 IP(比如某个保留地址或随机大站 IP);真答案要么晚到、要么压根到不了。原理是 GFW 在路径上"抢答"——DNS 默认走 UDP 53,无连接、无加密、无认证,谁的回包先到谁就赢 +- **IP 黑名单 / 路由黑洞**:已知敏感 IP 段在国家路由器上直接 BGP 黑洞或 null-routed,丢包率 100% +- **SNI 探测**:你 §9.1 学到的 ClientHello 里的 **SNI(Server Name Indication)是明文**的——GFW 直接读 SNI 字段,看到 `www.google.com` 就发 RST。这是当前最主要的拦截手段 +- **协议指纹 / TLS ClientHello 指纹**:即使加密了内容,不同 TLS 实现(Chrome / Firefox / OpenSSL / Go / Python)产生的 ClientHello 在字段顺序、扩展项、密码套件列表上会留下"指纹",GFW 用这个识别"非主流客户端" +- **主动探测**:对可疑的服务器 IP,GFW 会主动去握手——如果你的服务器对一个伪造的探测请求"露馅"(响应了不该响应的协议格式),就被加进黑名单 +- **时序与流量分析**:即使内容完全加密、协议完全正常,流量的包大小分布、发包间隔、上下行比例、连接持续时间本身就是特征——视频流、网页浏览、SSH、代理各有典型形态 + +把它当成一个**国家级的分布式入侵检测系统(IDS)** 来看就好——本质上是 DPI(深度包检测)+ 主动探测 + BGP 黑洞 联动。 + +### 6.2 不翻墙时,访问 Google 会被打在哪几层 + +如果不做任何处理,直接在国内访问 `www.google.com`,前面那条链路会被 GFW 在三个不同层面打断: + +- **DNS 层**:`www.google.com → ?` 查询往外发,GFW 投毒,你拿到一个错的 IP。如果你想绕开用 8.8.8.8(Google 公共 DNS),DNS over UDP 53 同样被污染,DNS over HTTPS / DoT 走 443 / 853 又会因为 SNI / IP 被另外打 +- **IP / TCP 层**:就算你硬编码了 Google 真实 IP,§8.2 的 SYN 包出去后,GFW 在路径上注入 RST,TCP 连接刚握手就被切断 +- **TLS 层**:就算 SYN 侥幸通了,§9.1 的 ClientHello 一发出去,SNI 里写着 `www.google.com`,GFW 立刻从旁路注入 RST + +三层任意一层中招就完蛋。所以**翻墙的本质就是在这三层都让 GFW 看不出你想去哪**。 + +### 6.3 翻墙小史:从 Shadowsocks 到 Reality + +每一代翻墙协议都是对当时 GFW 的针对性回应,演化轨迹本身就是一段精彩的猫鼠游戏: + +- **Shadowsocks(2012)**:把流量用对称加密包一层,看起来像"无规律高熵字节流"。早期 GFW 只看明文特征,SS 一度无敌。但后来 GFW 学会了识别"过于随机的流量"——正常协议都有协议头,SS 没有,这本身就是一种特征 +- **V2Ray VMess(2016)**:加更多伪装,但 VMess 自己设计的协议头有可识别特征,后来也被打 +- **Trojan(2017)**:思路反转——别假装"不是协议",直接**假装是标准 HTTPS**。Trojan 服务端跑一个真 nginx,认证失败就把流量真的转发给 nginx 返回正经网页。这一招让 GFW 的主动探测失效。但 Trojan 客户端的 TLS 库指纹(Go 标准库)和主流浏览器不一样,被识别 +- **VLESS + TLS + Vision(2022)**:用 **uTLS** 库精确模拟 Chrome 的 TLS 指纹,XTLS Vision 减少加密层数提升性能。已经很难被识别,但需要自己有域名 + 申请 Let's Encrypt 证书 +- **Reality(2023)**:**把伪装推到了极致——不再自己造 TLS 证书,直接借一个真实大网站的握手**。从 GFW 的角度看,你和服务器之间在密码学层面看到的握手内容,和你直接访问那个大网站完全一致;Reality 自己的认证发生在数据流深处,GFW 看不到。 + +每一代都是对前一代被识别点的修补。Reality 是这条线上目前的"最优解"。 + +### 6.4 Reality 工作原理:借壳的 TLS + +Reality 的核心思路一句话:**让代理流量在 TLS 层面和"访问一个真实大站"无法区分**。具体怎么做的—— + +**1. 借壳:握手目标写真实大站的 SNI** + +部署 Reality 时要选一个"借壳目标"(target),通常是一个**网络上必然存在、政治上不可能被封、TLS 实现强壮**的大站,比如 `dl.microsoft.com`、`www.cloudflare.com`、`addons.mozilla.org`。 + +客户端发起连接时,§9.1 那个 ClientHello 里的 SNI 字段写的就是 `dl.microsoft.com`——**注意,这是真的写,不是假装**。GFW 看到这个 SNI,行为上和你真的去访问微软完全一样,放行。 + +**2. X25519 暗号:对得上才解密,对不上原样转发** + +客户端 ClientHello 里塞一个用预先共享的服务端公钥(X25519 椭圆曲线)算出来的"暗号"(藏在 ClientHello 的随机字段里,从外面看就是普通的随机数)。服务器收到后,用自己的私钥验证这个暗号: + +- **验证通过**:这是合法 Reality 客户端,服务器进入 Reality 协议,后面解密承载的真实代理流量 +- **验证失败**(或者根本没暗号——主动探测最常见):**服务器把这条 TCP 连接整体透明转发给真实的 `dl.microsoft.com`,自己什么都不做**。GFW 主动探测看到的会是**微软的真实响应、真实证书、真实页面** + +这一步是 Reality 最聪明的地方:**主动探测是 GFW 的杀手锏,Reality 直接把它变成"探测必看到合法网站",从而无效化**。 + +**3. 证书链也是真的** + +更狠的是,Reality 服务器返回给客户端的 TLS 证书链,**就是从那个真实大站抓来的真证书**(只不过签名部分被替换,客户端跳过验证而已)。所以即使 GFW 想做"证书指纹"——比如检测自签证书、检测某些可疑 CA——它看到的也是微软的合法证书,无懈可击。 + +**4. uTLS 模拟主流浏览器指纹** + +ClientHello 的扩展顺序、密码套件列表用 **uTLS 库**精确模拟 Chrome / Firefox / Safari 的指纹,GFW 没法靠"TLS 实现指纹"判别这是不是浏览器流量。 + +**5. 加上 Reality 后,数据包的真实路径** + +``` +浏览器 + → Reality 客户端(本地 127.0.0.1:1080,SOCKS/HTTP 代理) + → Reality 客户端构造一条"看起来是去 dl.microsoft.com 的 HTTPS" + → 国内骨干网 → 国际出口 + → GFW 检查:SNI = dl.microsoft.com,放行 + → 跨洋海底光缆 + → 你部署在境外的 Reality 服务器(任意境外 VPS) + → 服务器验证 X25519 暗号:通过,进入 Reality 协议 + → 解密出真正的目标:www.google.com + → 服务器代你向 Google 发起新的 TCP+TLS 连接(从境外发,完全不经 GFW) + → Google 返回数据 + → 服务器把响应加密塞回 Reality 流量 + → 沿原路返回到本地 Reality 客户端 + → 解密后交给浏览器 +``` + +注意一个关键事实:**真正的目标(Google)在 GFW 视野里从未出现过**。GFW 看到的全程都是"国内某 IP 在和 dl.microsoft.com 进行一次正常 HTTPS 通信",该 SNI、该证书、该指纹一切合法。 + +### 6.5 Reality 的安全建立在哪里 + +如果纯从密码学讲,Reality 的"暗号"机制并不比常规 TLS 更强。它真正的安全性建立在一个**非密码学事实**上: + +**GFW 不敢真断借壳目标(像 microsoft.com)的访问**。 + +只要这条假设成立,GFW 就不能简单地"宁错杀一千"——封 Reality 等于连带把微软封了,这是政治和商业上都不可接受的。Reality 把"是不是代理流量"这个判别问题,转化成了"是不是 microsoft.com 流量",而后者是 GFW 主动放过的。 + +这是一个**密码学 + 工程 + 博弈论**三者结合的设计。单看任何一层都讲不全。 + +### 6.6 还在继续的猫鼠游戏 + +Reality 不是终点。审查与反审查的对抗还在每一层继续: + +- **TLS ECH(Encrypted Client Hello)**:IETF 推动的标准,把 SNI 也加密,从根上解决 SNI 明文泄露的问题。原理是用一个公开的"外层 SNI"(指向 CDN 大入口)做 TLS,再在里面加密承载真实 SNI。听起来很美好,但 **GFW 已经在 2024 年开始对启用 ECH 的连接做选择性丢包**——一手反制就是"启用 ECH = 可疑指纹",直接掐。这一步把 ECH 想解决的问题(SNI 明文)反而变成了"启用 ECH 就被识别"的新问题 +- **流量整形 / 时序分析**:即使内容完全像 microsoft.com,实际行为模式可能不像——比如 Reality 隧道开了 30 分钟没断、上下行比例稳定 1:8 像在看视频,但真去微软网站的人很少这么干。GFW 在做这类**行为指纹**分析。Reality / Xray 的应对是引入**流量填充(padding)** 和**包间隔随机化**,让流量分布更接近真实浏览。但这是博弈不是定论 +- **量子计算的远期威胁**:§9.2 提到过——Shor 算法理论上能破 X25519 等基于离散对数的密码原语。如果有一天大规模量子计算成熟,Reality 用的暗号机制要跟着升级到后量子算法。这是未来 5-15 年的悬剑 +- **机器学习侧**:近年 GFW 据传也在用机器学习模型做流量分类——给定一段加密流量,模型直接输出"这是浏览网页 / 视频 / SSH / 代理"的概率分布。这是一条全新的战线,反制的思路必然涉及对抗样本、流量混淆生成模型这些更复杂的工具 + +**这一章的物理 / 数学**:这一章的"敌人"不再是物理定律,而是另一组工程师。所有底层数学还是同一套(TLS、椭圆曲线、AES、SHA),但**真正的胜负发生在协议设计、流量模式、博弈假设、政治成本**这些层面。读完前面 8 章你知道"互联网怎么工作",读完这一章你才知道"它在敌对环境下怎么活下来"。 + +--- + +## 第 7 章 跨洋:海底电缆 + +### 7.1 海底电缆的样子 + +一根典型的跨洋光缆直径只有 2-3 厘米。从外到内的结构是: + +- 聚乙烯外护套 +- 聚酯树脂或沥青层 +- 钢丝铠装(在浅海段更厚,防渔船拖网和鲨鱼咬) +- 铝制防水层 +- 聚碳酸酯绝缘层 +- 铜或铝管(用来给海底中继器供电) +- 凡士林防水 +- 中心是几对光纤(一般 4-16 对) + +### 7.2 海底中继器 + +跨洋距离动辄几千甚至上万公里,光信号即使损耗很低也得放大。海底每隔 50-100 公里就有一个中继器,装在一个一两米长的金属圆柱里,沉在海底。 + +中继器内部主要是 EDFA 光放大器,靠从岸上通过光缆里的铜管供电——电压可以高达 ±15000V,这样长距离才不会损耗太多功率。 + +### 7.3 光纤里发生了什么 + +你的数据包是一组激光脉冲。每个脉冲代表 0 或 1(实际上 100Gbps 以上的高速光通信用的是相干调制,比如 QPSK、16-QAM,一个符号编码多个比特)。 + +光传播速度在光纤里大约是真空光速的 2/3,也就是 20 万公里每秒。从上海到美国西海岸大约 1 万公里光纤距离,单程就要 50 毫秒。再加上路由器排队、光电转换等,实际跨洋 RTT(往返时间)通常在 150-200 毫秒。 + +光纤通信的关键挑战之一是色散——不同波长的光速度略有差别,长距离传输后脉冲会被"展宽",导致符号间干扰。现代相干光通信用 DSP(数字信号处理)在接收端补偿色散,这又是一堆复杂的数学。 + +### 7.4 物理层纠错:Reed-Solomon 和后续 + +海底光通信也需要前向纠错。常用的是 **Reed-Solomon 码**,典型配置是 RS(255, 239)——意思是每 239 字节数据加 16 字节校验,接收端可以纠正最多 8 个字节的错误。 + +**Reed-Solomon** 的数学原理是:把数据看作多项式的系数,在多个点求值得到码字;接收端如果某些点的值错了,根据剩下足够多的点重建多项式,从而恢复原始数据。这套方法是 **Irving S. Reed** 和 **Gustave Solomon** 1960 年发表的,后来在 CD/DVD/QR 码、卫星通信、深空探测里被反复用上。 + +现代高速光通信(400G/800G)用更高级的码,比如 SD-FEC(软判决 FEC),净编码增益接近 11dB。 + +**这一章的物理 / 数学**:光纤色散(不同波长光速不同 → 脉冲展宽)→ 接收端 DSP 数字补偿,本质是反卷积;Reed-Solomon(代数:把数据当多项式系数,在多个点求值,用拉格朗日插值反推)→ FEC 纠错的数学根;偏振 / 相干光通信(QPSK / 16-QAM 移植到光波)→ 光的"频谱效率"逼近香农极限;光放大噪声(自发辐射 ASE)→ 限制每段无中继距离。 + +--- + +## 第 8 章 TCP 三次握手:在虚拟世界里建立连接 + +### 8.1 数据包到了美国 + +经过基站、运营商网络、海底电缆,你的数据包到达美国西海岸某个登陆站,然后通过美国国内的骨干网到达 Google 的某个数据中心(可能是俄勒冈州 The Dalles 的数据中心)。 + +但其实在数据包能"传输 HTTP 内容"之前,先要建立 TCP 连接,然后做 TLS 握手。 + +### 8.2 TCP 三次握手 + +**TCP(传输控制协议)** 由 **Vint Cerf** 和 **Bob Kahn** 在 1974 年的论文里定义,它在不可靠的 IP 网络上提供可靠的、有序的、字节流式的通信。在传 HTTP 数据之前,要先用**三次握手**建立连接。 + +**第一次:客户端 → 服务器,SYN** + +你的手机发一个 TCP 包,标志位 SYN(同步)= 1,带一个初始序号 `seq = x`(x 是随机数)。 + +**第二次:服务器 → 客户端,SYN+ACK** + +Google 的服务器收到 SYN,回一个包,标志位 SYN = 1, ACK = 1,自己的初始序号 `seq = y`,确认号 `ack = x + 1`(意思是"我确认收到了你的 x")。 + +**第三次:客户端 → 服务器,ACK** + +你的手机回最后一个包,标志位 ACK = 1,确认号 `ack = y + 1`(意思是"我确认收到了你的 y")。 + +到这里,连接建立。注意整个过程花了一个 RTT(往返时间)——跨洋是 150ms 左右。 + +### 8.3 ISN 为什么是随机数 + +初始序号(ISN)如果总是从 0 开始,攻击者就能预测、伪造连接。所以 RFC 6528 规定 ISN 要随机化,推荐的算法大致是: + +``` +ISN = M + F(本地IP, 本地端口, 远端IP, 远端端口, 密钥) +``` + +其中 M 是一个微秒级递增的计数器(每 4 微秒加 1),F 是一个带密钥的哈希函数(比如 MD5)。这样既保证了同一个连接的 ISN 是单调递增的,又保证攻击者不知道密钥就猜不出 ISN。 + +### 8.4 SYN flood 攻击 + +服务器收到 SYN 后,会在内存里分配一个"半连接"条目,等待客户端的 ACK。攻击者可以伪造大量源 IP 发 SYN 包但永远不发 ACK,把服务器的半连接队列塞满,正常用户就连不上了——这就是 SYN flood。 + +防御方法之一是 SYN cookie:服务器收到 SYN 时不立即分配资源,而是把连接信息编码成 ISN(用密码学方法),等收到 ACK 时再验证、分配资源。 + +**这一章的物理 / 数学**:这一段从硬件抬到了"逻辑信道"层。底层数学不再是物理,而是**协议语义+概率与排队**:三次握手是分布式系统里"两军问题"的近似解(完全可靠的握手在异步网络里被证明不可能,TCP 选择"足够好");ISN 用密码学哈希 → 信息论意义上的不可预测性;SYN flood / SYN cookie → 状态机被滥用与防御。 + +--- + +## 第 9 章 TLS 握手:让中间人看不懂 + +TCP 通了之后,还不能直接发 HTTP 请求——HTTPS 要求所有应用数据都加密。所以接下来要做 TLS 握手。 + +### 9.1 TLS 1.3 的握手流程 + +我们以最新的 TLS 1.3 为例(它把握手压缩到 1 个 RTT,比 1.2 快一半)。 + +**ClientHello**:你的手机发出第一条消息,内容包括: +- 支持的加密套件列表(比如 TLS_AES_256_GCM_SHA384) +- 一个随机数(32 字节) +- 一个临时的 ECDH 公钥(下面解释) +- SNI(Server Name Indication),告诉服务器你想访问 `www.google.com`(因为同一个 IP 可能托管多个域名) + +**ServerHello + Certificate + Finished**:Google 服务器收到后,选定一个加密套件,生成自己的临时 ECDH 公钥,把它和自己的证书一起发回来。证书里包含 Google 的公钥,以及由 CA(证书颁发机构,比如 GTS)签的数字签名。 + +**客户端验证证书 + 派生密钥 + Finished**:你的手机用预装的根证书验证 Google 证书的签名是真的(意味着真的是 Google 控制这个域名),然后用 ECDH 算出共享密钥,再用它派生出对称加密密钥。 + +到这里 TLS 握手完成。后续的 HTTP 数据全部用对称密钥加密发送。 + +### 9.2 ECDHE:密钥怎么"凭空"协商出来的 + +这是整个 TLS 最神奇的部分。两个素不相识的人,在公开的信道上交换信息,中间人能看见全部交换内容,但他们俩能算出一个共享密钥而中间人算不出。 + +原理是 **Diffie-Hellman 密钥交换**——1976 年由 **惠特菲尔德·迪菲(Whitfield Diffie)** 和 **马丁·赫尔曼(Martin Hellman)** 在《密码学的新方向》里提出。简化版本: + +1. 双方公开商定一个大素数 p 和一个生成元 g。 +2. 你的手机随机选一个秘密数 a,算 `g^a mod p`,把结果(g^a)发给服务器。 +3. 服务器随机选一个秘密数 b,算 `g^b mod p`,把结果(g^b)发给你。 +4. 你的手机算 `(g^b)^a mod p = g^(ab) mod p`。 +5. 服务器算 `(g^a)^b mod p = g^(ab) mod p`。 + +两边算出的结果相同——这就是共享密钥。 + +中间人看到的是 g、p、g^a、g^b。要算出 g^(ab),他必须从 g^a 反推出 a(或从 g^b 反推出 b)。这就是"离散对数问题":知道 g 和 g^a mod p,求 a。 + +在 p 是 2048 位以上的大素数时,目前没有任何已知算法能在合理时间内解决**离散对数问题(DLP)**。这就是 Diffie-Hellman 的安全基础。 + +实际中 TLS 用的是椭圆曲线版本(**ECDHE**),把"模幂"换成"椭圆曲线上的点乘",数学结构更紧凑,256 位的椭圆曲线密钥相当于 3072 位的 RSA 安全强度。 + +**底层的数学:为什么这玩意儿是安全的**。再多拆一层,公钥密码学这一整套魔法,根基是几条互相咬合的数学事实: + +- **质数是无穷多的**(**欧几里得(Euclid)**,公元前 300 年)——所以选一个 2048 位的大质数 `p` 永远有得选。 +- **群论 / 循环群**:`{1, 2, ..., p-1}` 在模 `p` 乘法下构成一个有限循环群,生成元 `g` 能"转"出整个群。`g^a mod p` 就是在这个圆环上转 `a` 步。 +- **欧拉定理 / 费马小定理**:`g^(p-1) ≡ 1 mod p`(p 是质数时)——这是模幂运算每一步的合法性保证。**皮埃尔·德·费马(Pierre de Fermat)** 1640 年提出特例,**莱昂哈德·欧拉(Leonhard Euler)** 1763 年推广到合数。 +- **离散对数难题(DLP)**:正向算 `g^a mod p` 用快速幂只要 `O(log a)` 步,反向找 `a` 目前最好的算法是 **GNFS 数域筛**,复杂度仍是亚指数级。**正算秒级完成,反算要宇宙年龄,这条不对称性就是公钥密码的"魔法泉源"**。 +- **椭圆曲线**:把"群"从 `(整数, 模乘)` 换到"椭圆曲线上的点和切线相加"。曲线方程 `y² = x³ + ax + b (mod p)`,点之间定义一种"加法"满足群公理。点乘 `[k]P` 正向快、反向(已知 `[k]P` 求 `k`)同样指数级困难——这叫**椭圆曲线离散对数难题(ECDLP)**。优势是"群结构更紧凑",同样安全级别需要的位数小一个量级。 +- **量子计算的威胁**:**Shor 算法** 1994 年由 **彼得·肖尔(Peter Shor)** 提出,理论上量子计算机能在多项式时间内解决离散对数和大整数分解。所以 NIST 已经在推**后量子密码(PQC)**(基于格、码、多变量、哈希),TLS 1.3 已经能跑混合方案(ECDHE + Kyber)。这是一根悬在头顶的剑。 + +一句话总结:**整个 HTTPS 的"私"字,本质上压在"模幂 / 点乘"这两个数学操作的不对称性上**。 + +### 9.3 对称加密:AES-GCM + +握手完成后,所有数据用 AES-GCM 加密。AES 是分组密码,把数据按 128 位分块,每块用密钥经过 10/12/14 轮(取决于密钥长度)的字节替换、行移位、列混合、轮密钥加,变成密文。 + +GCM 模式在加密之外还加了认证——不仅保证别人看不懂,还保证别人改不了内容(改了能被发现)。 + +**AES 背后是有限域**(**Galois Field**,GF(2⁸))——这一支代数结构以 19 世纪法国早夭天才 **埃瓦里斯特·伽罗瓦(Évariste Galois)** 命名,他 20 岁因决斗去世,死前一夜赶写出来的论文奠定了群论。每一字节被当作 GF(2⁸) 上的一个元素,加法是按位异或,乘法是带模约的多项式乘法。AES 的"列混合"(MixColumns)是这个域上的矩阵乘法,"S-box"是 `x → x⁻¹` 求逆再做仿射变换,这两步专门让线性 / 差分密码分析很难奏效。说白了,AES 之所以经得住二十多年的攻击,是因为它把"扩散 / 混淆"两个目标转化成了一个高度非线性的代数对象上的操作——纸面上简单,但破译它要解一个看起来非线性、实则缺乏可利用结构的方程组。 + +AES 的"麻烦"在于 S-box(字节替换表)的查表操作,软件实现速度有限。所以 Intel 自 2010 年起在 CPU 里加入了 AES-NI 指令集,把 AES 的一轮做成单条指令(`AESENC`、`AESDEC` 等),性能提升 5-10 倍。ARM 也有等价的指令(`AESE`、`AESMC`)。手机和服务器现在都用硬件 AES。 + +GCM 的"认证"那一半,靠的是 GHASH——又是一个 GF(2¹²⁸) 上的多项式求值。所以"AES-GCM"这五个字背后,其实压着两层有限域代数。 + +### 9.4 证书验签:RSA 或 ECDSA + +证书的签名验证是另一类密码学操作。CA 用自己的私钥对(域名 + 公钥 + 有效期 + ...)做哈希,然后签名。你的设备用 CA 的公钥验签。 + +签名算法常用 **RSA-PSS**(基于**大整数分解难题**——由 **Rivest / Shamir / Adleman** 1977 年发表,所以叫 RSA)或 **ECDSA**(基于**椭圆曲线离散对数难题**)。验签涉及大量大数模幂运算,服务器侧通常有专门的硬件加速(QAT、专用加密卡)。 + +### 9.5 哈希函数:看不出原文,改一位就翻天 + +证书签名、HMAC、TLS 派生密钥、TCP SYN cookie、git commit、区块链——只要听到"摘要"二字,后面跟着的多半是哈希函数。常用的是 SHA-256 / SHA-384(SHA-2 家族)和近年标准化的 SHA-3(基于海绵结构 Keccak)。 + +一个好的哈希函数同时满足三件事: + +- **抗原像**:给定哈希值 `H(x)`,反推 `x` 不可行 +- **抗第二原像**:给定 `x`,找一个 `x' ≠ x` 使 `H(x) = H(x')` 不可行 +- **抗碰撞**:找任意两个不同的 `x ≠ y` 使 `H(x) = H(y)` 不可行 + +外加一个直观但关键的性质——**雪崩效应**:输入改一比特,输出大概有一半比特会翻。这就是为什么改一个空格 git commit 哈希就完全变样。 + +底层数学不像 RSA / ECC 那样压在某个"难题"上,更像是一种"足够混乱以至于看起来随机"的工程构造——SHA-2 用一堆"加 / 异或 / 移位 / 模加"反复迭代,SHA-3 用海绵结构吸 / 挤数据。安全性靠的是几十年没人能找到捷径,不是数学定理保证的(这一点和 RSA / ECC 不同)。 + +**这一章的物理 / 数学(整章总结)**:数论(质数、模幂、欧拉定理)→ DH / RSA;群论 + 椭圆曲线代数 → ECDHE / ECDSA;有限域 GF(2⁸) / GF(2¹²⁸) → AES + GHASH;计算复杂性(单向函数 + 难题假设)→ 整个公钥密码的安全感;雪崩效应 + 抗碰撞 → 哈希函数。这一整层是"用数学算出别人算不出来的东西",是 HTTPS 之所以叫 HTTPS 的全部原因。 + +### 9.6 HTTPS = HTTP + TLS:把零件拼起来 + +到这一章结束,你已经把 HTTPS 的所有零件全部走了一遍——是时候把它们拼起来,看清楚 HTTPS 到底长什么样。 + +**HTTPS 的"洋葱"结构** + +回想一下 [§0.1](#01-顺手把-https-拆成它的零件) 那张表,每一层在哪一章讲过、干什么: + +| 层 | 协议 | 作用 | 在哪讲 | +|---|---|---|---| +| 应用层 | **HTTP** | 请求方法、URL、headers、body——要表达什么意图 | §1.2 | +| 安全层 | **TLS** | 握手协商密钥 + 之后整段加密 | 本章 §9.1-§9.5 | +| 传输层 | **TCP** | 三次握手 + 可靠字节流 + 拥塞控制 | 第 8 章 + 第 12 章 | +| 网络层 | **IP** | 路由、寻址 | 第 5 章 | +| 物理层 | 无线 / 光纤 | 真正把比特送出去 | 第 1-3 章、第 7 章 | + +**真正发出去的字节是这样套娃的:** + +``` +你想发的 HTTP 请求(明文,只有你和 Google 看): + GET /search?q=weather HTTP/1.1 + Host: www.google.com + User-Agent: Mozilla/5.0 ... + + ↓ TLS 加密(AES-GCM)+ 加 TLS 记录头 + +TLS Record: + [TLS 头][加密后的密文……] ← 中间人能看到这段,但解不开 + + ↓ TCP 切段 + 加 TCP 头(序号、确认号、端口 443) + +TCP Segment: + [TCP 头][TLS Record] + + ↓ IP 封装 + 加 IP 头(源 IP、目的 IP、TTL) + +IP Packet: + [IP 头][TCP Segment] + + ↓ 物理层(5G OFDM / 光纤光脉冲 / 以太网帧) + +电磁波 / 光子在物理介质里飞 +``` + +**关键观察**:**每一层只看自己的头**。路由器只看 IP 头(它根本不知道里面是 TCP 还是 UDP);TCP 协议栈只看 TCP 头(它不知道里面是不是 TLS 加密内容);TLS 库只解密 TLS 内容(它不关心里面是 HTTP 还是 SMTP);浏览器只看 HTTP(它不操心下面有没有加密)。这种**严格的分层 + 接口稳定**,是互联网能让几十种技术互不干扰拼在一起的根本原因。 + +**接收端反向拆"洋葱"**:物理层把帧还原成 IP 包,IP 层把 TCP 段交给 TCP 协议栈,TCP 层重组出 TLS 加密字节流,TLS 层用握手协商出来的密钥解密出 HTTP 明文,HTTP 层把请求交给应用——也就是 Google 搜索服务。 + +**HTTPS 里的"S"到底加了什么** + +| 没有 S 的 HTTP(80 端口) | 加了 S 的 HTTPS(443 端口) | +|---|---| +| 中间人能**看**到所有内容(URL、headers、body) | 中间人**看不到**内容,只看到加密字节流 | +| 中间人能**改**内容(注入广告、篡改下载) | 中间人**改不了**(GCM 认证位会失败) | +| 你不知道**对面真的是 Google** | 你**确认**对面是 Google(证书验签) | +| 任意 ISP 都能做"中间人攻击" | 中间人攻击需要伪造证书,正常情况下不可能 | + +第三条尤其关键:**HTTPS 不只是加密,还做身份验证**。光加密不验证,你可能加密地连到一个伪装成 Google 的钓鱼网站;光验证不加密,内容会被偷看。两者必须同时有。这两件事都靠这一章讲的密码学:**ECDHE 协商密钥**(防偷看)+ **证书验签 / RSA 或 ECDSA**(防冒充)+ **AES-GCM**(加密 + 防篡改一体)+ **哈希**(把"完整性"压缩成 32 字节摘要)。 + +**所以,HTTPS 这三个字到底是什么** + +一句话:**HTTP 这个最普通的请求-响应协议,被装进 TLS 这条加密+验证身份的暗管里,这条暗管又架在 TCP 这条可靠通道上**。 + +更短的一句话:**HTTPS 就是"在不可信的互联网上,装出一条可信的通道"**。 + +而这条"可信通道",其实是用前面整篇文档的全部内容堆出来的——物理层把比特送到位、TCP 让字节流不乱、TLS 让内容看不见也改不了、HTTP 让两边知道在聊什么、CA 体系让你确信对面是真 Google、GFW 试图把这条通道掐掉、Reality 又试图让这条通道看起来像别人的通道。 + +下一章开始,我们看 Google 的服务器在另一端是怎么把这个洋葱一层层剥开的。 + +--- + +## 第 10 章 数据中心入口 + +### 10.1 Google 的网络入口 + +你的加密数据包到达 Google 的某个数据中心入口。Google 全球有几十个数据中心,加上几百个边缘节点(POP)。请求会被引导到离你最近、负载较轻的一个。 + +入口处会经过: + +**边缘路由器**:跑 BGP 协议,和外部互联网互联。 +**DDoS 清洗**:Google 自研的 DDoS 防护系统(以及部分外部商用方案),识别和过滤恶意流量。 +**Maglev 负载均衡**:Google 自研的软件负载均衡器,运行在普通 Linux 服务器上,用一致性哈希把请求分配给后端服务器集群。 + +### 10.2 一致性哈希 + +负载均衡器要决定:这个请求该发给哪台后端服务器?如果用简单的"哈希取模",一旦后端服务器数量变化(扩容、缩容、宕机),大量连接会被重新分配,影响用户体验。 + +**一致性哈希(Consistent Hashing)** 的思路是把所有后端服务器和所有请求都映射到一个圆环上(用哈希函数),每个请求顺时针找到的第一台服务器就是它的归属。这样添加或删除一台服务器,只影响圆环上相邻的一段,绝大多数连接不受影响。这套算法 1997 年由 **David Karger 等人** 在 MIT 提出,后来在 Cassandra、DynamoDB、CDN、Maglev 里反复出现。 + +Google Maglev 用的是改进版"Maglev hashing",在保持一致性的同时让负载更均衡。 + +### 10.3 TOR 交换机和 Spine-Leaf 架构 + +数据中心内部的网络架构是 Spine-Leaf: + +- **Leaf 交换机**(也叫 TOR,Top of Rack):每个机柜顶部一台,连接机柜内所有服务器。 +- **Spine 交换机**:每台 Leaf 交换机都和所有 Spine 交换机相连,形成一个全连接的两层结构。 + +这种架构的好处是任意两台服务器之间最多两跳,延迟和带宽可预测。Google 自研的数据中心交换机(代号 Jupiter)用商用的转发 ASIC(博通 Tomahawk 或类似芯片)做线速转发,集群带宽可以达到 PB 级。 + +--- + +## 第 11 章 服务器内部 + +### 11.1 网卡到 CPU + +你的数据包终于到达 Google 一台具体的服务器。服务器的网卡(可能是 NVIDIA Mellanox ConnectX-6 或 Google 自研的)收到光信号,光模块转换成电信号,网卡的 MAC 控制器解析以太网头,做以下处理: + +**校验和卸载**:网卡硬件验证 IP 和 TCP 校验和,正确才往上交。 +**RSS(接收侧扩展)**:网卡根据数据包的四元组(源IP、源端口、目的IP、目的端口)做哈希,把不同的连接分配到不同的 CPU 核心,避免单核成为瓶颈。 +**DMA 写内存**:网卡通过 PCIe 总线,用 DMA 把数据包直接写到指定的内存区域,完全不打扰 CPU。 +**触发中断**:写完后,网卡给对应的 CPU 核心发一个 MSI-X 中断,告诉它"有新数据了"。 + +### 11.2 内核协议栈 + +CPU 收到中断,进入中断处理程序。Linux 内核的网络协议栈处理顺序大致是: + +1. **硬中断处理**:网卡驱动响应中断,把这个数据包"挂"到该 CPU 的待处理队列上,然后立即返回(尽快释放硬中断)。 +2. **软中断处理(NAPI)**:稍后,内核的 ksoftirqd 线程批量处理待处理队列,一次拉一批包(典型 64 个),减少中断开销。 +3. **IP 层**:检查 IP 头,如果是分片包则重组。 +4. **TCP 层**:根据四元组找到对应的 socket,把数据按序号放入 socket 接收缓冲区。如果序号有跳跃,就缓存等后续。 +5. **唤醒应用**:如果有进程在 epoll 里等这个 socket,内核唤醒它。 + +### 11.3 高性能场景:DPDK 和内核旁路 + +对于 Google 这种规模,内核协议栈的开销都嫌大。所以高性能服务用 DPDK(数据平面开发工具包)或 XDP(eXpress Data Path)做"内核旁路",让用户态程序直接和网卡打交道,避开内核拷贝和上下文切换。 + +更激进的还有 RDMA(远程直接内存访问),让一台服务器的网卡直接读写另一台服务器的内存,完全不经过 CPU。 + +### 11.4 应用层:Google 的搜索服务 + +数据包最终到达运行在这台服务器上的某个进程,可能是 Google Front End(GFE)。GFE 解密 TLS,解析 HTTP 请求,看到 `/search?q=weather`,把它转发给后端的搜索集群。 + +搜索集群是一个非常复杂的分布式系统:倒排索引、查询理解、排序、广告系统、知识图谱、个性化、垂直搜索(天气模块)等等。这些处理在几十毫秒内完成,生成 HTML 响应。 + +天气查询比较特殊——Google 检测到你查的是天气,会调用专门的天气模块,根据你的 IP 估算位置(或者用浏览器报的地理位置),从天气数据提供方(比如 weather.com)获取数据,渲染成搜索结果顶部的天气卡片。 + +响应数据回到 GFE,被压缩(gzip 或 brotli),用 TLS 加密,封装成 TCP 包,发回网卡。 + +### 11.5 顺便补一层:服务器内部的"内存层级" + +服务器收包之后,数据要在不同存储层之间跑一遍,这一段决定了"为什么能这么快": + +``` +寄存器(<1ns,KB 级) + ← L1 cache(~1ns,数十 KB) + ← L2 cache(~5ns,数百 KB) + ← L3 cache(~20ns,数十 MB,多核共享) + ← DRAM(~80ns,几十 GB) + ← NVMe SSD(~50μs,几 TB) + ← 网络存储(~ms,几 PB) +``` + +每往下一层,延迟差一到三个数量级。CPU 之所以"快",80% 的功劳是 cache 命中得好,而不是核心频率高。处理你这一个 HTTPS 请求的时候,数据从网卡 DMA 落到 DRAM,再被 CPU "拉"到 L3 → L2 → L1 → 寄存器,一路上都靠 cache line 预取、分支预测在掩盖延迟。 + +这一层的"数学"是排队论 + 概率:cache 命中率本质是一个时间局部性 / 空间局部性的概率分布问题。 + +**这一章的物理 / 数学**:总线 / DMA(本质是异步生产者-消费者)→ 网卡到内存零拷贝;一致性哈希(把节点摆到圆环上)→ 负载均衡的"最少扰动"性质;排队论(Little's Law: `L = λ × W`)→ 延迟 / 吞吐 / 队长的硬约束;Spine-Leaf 拓扑 → 图论上的 Clos 网络,任意两点最多两跳。 + +--- + +## 第 12 章 丢包、误码与补救 + +### 12.1 各层都在干补救的事 + +这条几千公里的旅程上,数据出错或丢失是常态。各层都有自己的补救机制: + +**物理层**:用前向纠错码(FEC)。光纤用 Reed-Solomon、SD-FEC,5G 用 LDPC、Polar。原理是加冗余位,接收端通过代数运算修正错误。 +**数据链路层**:用 CRC 检错(发现错就丢),或在 WiFi/5G 里用 ARQ 重传(链路层重传比传输层更快)。 +**传输层**:TCP 用序号 + 确认 + 超时重传 + 快速重传。 +**应用层**:QUIC 在每个流上独立重传;实时音视频用 FEC + NACK 混合。 + +### 12.2 TCP 拥塞控制:不是丢包不报警,而是别让网络堵车 + +TCP 不光要可靠,还要"友好"——不能一个连接占满整条带宽。所以有拥塞控制算法。 + +**TCP Reno**(经典):慢启动时拥塞窗口指数增长,达到阈值后线性增长;一旦丢包,窗口减半,从头慢慢涨。 + +**TCP CUBIC**(Linux 默认):窗口按三次曲线变化,先快后慢再快,适合高带宽场景。 + +**TCP BBR**(Google 推出,2016 年):颠覆传统思路,不以丢包为信号,而是主动测量**带宽**和 **RTT**,直接控制发送速率匹配链路实际带宽。 + +BBR 的核心思想是估计: + +``` +BDP = 带宽 × RTT +``` + +BDP 叫**带宽延迟乘积(Bandwidth-Delay Product)**,是链路在途数据的最大量。BBR 让在途数据保持在 BDP 附近,既不浪费带宽,又不在路由器队列里堆积。 + +YouTube、Google 搜索都用 BBR,显著降低了延迟和重传率。 + +### 12.3 SACK 选择性确认 + +经典 TCP 的累积确认有个问题:如果你发了 1-10 号包,丢了 5 号,接收方只能确认到 4 号。发送方不知道 6-10 是不是也丢了,可能要重传 5-10 全部。 + +SACK(选择性确认)允许接收方说"我收到了 1-4 和 6-10,只缺 5"。发送方就只重传 5,效率高得多。现代 TCP 默认开启 SACK。 + +### 12.4 5G HARQ + +物理层的 HARQ(混合自动重传)前面提过——简单说就是"重传不是简单重发,而是发不同的冗余信息,接收端把多次接收的数据合并解码"。这样即使每次都有错误,合并后也能正确解码。这是物理层的智慧。 + +**这一章的物理 / 数学**:整层都是"在不可靠的物理世界上模拟一个可靠通道"。底层是**信息论**(香农 1948)——只要信道容量大于信息率,就存在一种编码让错误率任意低;实现路径分两条:**前向纠错**(代数构造冗余,数学上是有限域 / 多项式 / 稀疏图)和**反馈重传**(利用反向链路通知重发,数学上是状态机 + 概率重试)。**TCP 拥塞控制**则是把"网络拥堵"建模成排队系统,每代算法(Reno / CUBIC / BBR)都是对"在途量 vs 实际带宽 / 排队延迟"这个动力系统的不同控制策略。 + +--- + +## 第 13 章 硬件加速汇总 + +整条链路上,各种"加速硬件"在默默干活。汇总一下: + +**手机里**: +- 基带芯片(高通 X75)的 LDPC/Polar 解码电路、FFT 引擎 +- 主 SoC 的 AES、SHA 硬件加速指令(ARMv8 Crypto Extensions) +- 射频前端的 PA、SAW/BAW 滤波器 + +**基站里**: +- BBU 的 FPGA / 专用加速卡处理 OFDM、MIMO、信道编码 +- AAU 里的 64 通道收发器和数字波束成形 + +**路由器里**: +- 转发 ASIC(博通 Tomahawk、思科 Silicon One) +- TCAM 高速查找表 +- 高速串行接口(SerDes)做端口间数据传输 + +**光通信里**: +- EDFA 光放大器 +- 相干光收发器(集成 DSP 做色散补偿、极化解复用) +- DWDM 复用器 + +**服务器里**: +- 网卡的校验和卸载、TSO/LRO、RSS、TLS 卸载 +- CPU 的 AES-NI、SHA-NI、AVX-512(用于密码学和数据处理) +- DPU 智能网卡(NVIDIA BlueField、AWS Nitro)接管网络、存储、加密任务 + +如果没有这些专用硬件,所有事情都靠 CPU 软件实现,整个互联网的吞吐量大概只能达到现在的 1%——而且功耗会爆炸。 + +--- + +## 第 14 章 返回:数据包的回家路 + +Google 服务器生成的响应数据,要走和来时差不多的路径回到你的手机: + +``` +应用进程 → 内核协议栈 → 网卡 → TOR → Spine + → Maglev → 边缘路由器 → 美国骨干网 + → 海底电缆 → 中国登陆站 → 中国骨干网 + → 城域网 → UPF → 基站(下行)→ 你的手机基带 + → 主 SoC → Chrome → 解密 → 解压 → 解析 HTML + → JavaScript 引擎 → 排版 → GPU 合成 → 屏幕显示 +``` + +下行链路上,基站到手机方向也是用 OFDM、Massive MIMO、LDPC,但参数和上行略有不同(下行通常用更高的调制阶数,因为基站发射功率大)。 + +手机收到 HTTP 响应后,Chrome 解密 TLS,解压 gzip/brotli,把 HTML 交给渲染引擎(Blink),解析 DOM 树,执行 JavaScript,布局,光栅化,合成,最后通过 GPU 把像素送到屏幕。 + +屏幕的 **OLED** 像素接到驱动信号,每个红绿蓝子像素发出对应亮度的光——这一步又是量子物理:OLED 是有机半导体,电子和空穴在有机层里复合释放光子。你看到了天气预报。 + +整个过程从你点击到看到结果,如果一切顺利,大概 300-800 毫秒。 + +--- + +## 附录 A 关键公式速查 + +> 不必逐条记。这张表的用法是:碰到链路里某一步,知道"是哪条公式 / 哪条定律在起作用"就够了。 + +### 物理层(电磁、光、半导体) + +| 名称 | 公式 | 在哪一章用上 | +|------|------|------| +| **Maxwell 方程组** | ∇·E=ρ/ε₀, ∇·B=0, ∇×E=−∂B/∂t, ∇×B=μ₀J+μ₀ε₀∂E/∂t | 一切电磁波的存在与传播(Ch3, Ch5, Ch7) | +| **光速 / 频率 / 波长** | c = λ × f | 决定天线尺寸、频段选用(Ch3) | +| **Friis 自由空间路径损耗** | P_r = P_t × G_t × G_r × (λ/4πr)² | 基站规划密度(Ch3) | +| **平板电容** | C = ε × A / d | 电容触摸屏识别(Ch1) | +| **斯涅尔定律 / 临界角** | n₁ sin θ₁ = n₂ sin θ₂; θ_c = arcsin(n₂/n₁) | 光纤全反射(Ch5) | +| **光子能量** | E = h × f | 光通信的量子尺度极限(Ch5, Ch7) | +| **受激辐射(爱因斯坦,1917)** | 入射光子诱导高能级粒子放出同模光子 | EDFA 全光放大(Ch5, Ch7) | + +### 信号处理 / 通信 + +| 名称 | 公式 | 在哪一章用上 | +|------|------|------| +| **香农信道容量** | C = B × log₂(1 + SNR) | 信道理论上限(Ch3, Ch12) | +| **OFDM 信号** | s(t) = Σ Xₖ × exp(j2πkΔf·t) | 多子载波时域合成(Ch3) | +| **MIMO 信道模型** | y = H W s + n | 波束赋形 / 多天线(Ch3, Ch4) | +| **波束加权(MRC / ZF / MMSE)** | W = H^H, (H^H H)⁻¹ H^H, ... | Massive MIMO 加权矩阵选择(Ch4) | +| **CRC 校验** | 数据多项式 mod 生成多项式 | 错误检测(Ch3, Ch11) | + +### 密码学(数论 / 群论 / 有限域) + +| 名称 | 公式 / 概念 | 在哪一章用上 | +|------|------|------| +| **欧拉 / 费马小定理** | g^(p−1) ≡ 1 (mod p),p 为质数 | 模幂运算的代数基础(Ch9) | +| **Diffie-Hellman 共享密钥** | K = g^(ab) mod p | 双方公开协商出共同密钥(Ch9) | +| **离散对数难题(DLP / ECDLP)** | 已知 g, g^a 求 a;椭圆曲线上同理 | 公钥密码安全的根(Ch9) | +| **椭圆曲线** | y² = x³ + ax + b (mod p) | ECDHE / ECDSA(Ch9) | +| **AES 一轮** | SubBytes → ShiftRows → MixColumns → AddRoundKey | 对称加密的基本单元(Ch9) | +| **AES 的有限域** | GF(2⁸) 上的多项式运算 | S-box 求逆、列混合(Ch9) | +| **GHASH(GCM 认证)** | GF(2¹²⁸) 上多项式求值 | AES-GCM 的认证位(Ch9) | +| **哈希雪崩** | 输入差 1 bit → 输出差 ~50% bit | 哈希 / HMAC / SYN cookie(Ch8, Ch9) | + +### 网络 / 系统 + +| 名称 | 公式 / 概念 | 在哪一章用上 | +|------|------|------| +| **TCP 初始序号** | ISN = M + F(四元组, 密钥) | 防止序号预测(Ch8) | +| **带宽延迟乘积** | BDP = 带宽 × RTT | BBR 拥塞控制的核心(Ch12) | +| **最长前缀匹配** | 路由表里取最具体的前缀 | 路由器转发(Ch5) | +| **一致性哈希** | 节点和请求映射到环 | 负载均衡(Ch10) | +| **Little's Law(排队论)** | L = λ × W(队长 = 到达率 × 平均等待时间) | 路由器队列、TCP / 服务器延迟分析(Ch11, Ch12) | + +--- + +## 附录 B 涉及的硬件芯片举例 + +| 位置 | 芯片/设备 | 厂商/型号举例 | +|------|----------|---------------| +| 手机主 SoC | A 系列 | Apple A17 Pro 等 | +| 手机基带 | 5G 调制解调器 | Qualcomm Snapdragon X75 | +| 手机射频 PA | 功率放大器 | Skyworks、Qorvo | +| 手机滤波器 | SAW/BAW | Murata、Qorvo | +| 5G 基站 BBU | 基带处理 | 华为、爱立信、诺基亚自研 + Intel/AMD CPU | +| 5G 基站 AAU | 大规模天线阵 | 64T64R 通常自研 | +| 城域网路由器 | 核心路由 | 华为 NE40E/NE9000、思科 ASR9000、Juniper MX | +| 路由器转发芯片 | NPU/ASIC | 博通 Jericho 系列、思科 Silicon One | +| 数据中心交换机 | 商用交换芯片 | 博通 Tomahawk 系列、Tofino | +| 海底中继器 | 光放大器 | EDFA(SubCom、NEC、华为海洋) | +| DWDM 设备 | 长距离光传输 | Ciena、华为 OptiX、中兴 ZXMP | +| 服务器网卡 | 高性能 NIC | NVIDIA Mellanox ConnectX、Intel E810、Google 自研 | +| 服务器 DPU | 智能网卡 | NVIDIA BlueField、AWS Nitro、Intel IPU | +| 服务器 CPU | x86 / ARM | Intel Xeon、AMD EPYC、Ampere Altra、Google Axion | + +--- + +## 附录 C 底层物理与数学原理速览 + +> 这一节是为了让你**以"原理"为索引、横向看穿整条链路**。每一行只用一句话讲清楚"它在哪一章用上、它告诉我们什么"。需要细节再回正文找。 + +### 一、电磁与光(物理学) + +| 原理 | 一句话 | 用在哪 | +|------|--------|------| +| **Maxwell 方程组**(1865) | 电场和磁场互相激发,以光速向四周扩散——所有电磁波的存在凭据 | Ch3 5G、Ch5 光纤、Ch7 海底光缆、Wi-Fi、雷达、GPS | +| **光速恒定** `c ≈ 3×10⁸ m/s` | 真空中电磁波速度的硬上限 | RTT 下限的物理根 | +| **频率 / 波长 `c = λf`** | 频率越高,波长越短,天线越小 | 5G 频段选择、毫米波天线阵 | +| **全反射(斯涅尔定律)** | 光从高折射率介质射向低折射率,角度足够大就被弹回去 | 光纤几乎不漏光的原因 | +| **波的相干叠加(Young,1801)** | 同相位相加,反相位抵消 | Massive MIMO 波束赋形 | +| **多普勒效应** | 相对速度造成频率偏移 | 高铁 / 卫星通信里要补偿 | + +### 二、量子与半导体(物理学) + +| 原理 | 一句话 | 用在哪 | +|------|--------|------| +| **能带理论 / MOSFET 反型层** | 电场把电子推到导带,通道形成 / 消失 → 数字 0/1 | 所有芯片里的晶体管开关 | +| **量子隧穿** | 电子有概率"穿过"足够薄的绝缘层 | 工艺微缩到 3nm 时的漏电墙;闪存擦写 | +| **光子(普朗克 / 爱因斯坦)** | 光以 `hf` 大小一份份的能量传递 | 光纤接收灵敏度的物理下限 | +| **受激辐射(爱因斯坦,1917)** | 一个入射光子诱导出一个完全相同的新光子 | 激光器、EDFA 全光放大 | +| **粒子数反转** | 高能级电子比低能级多,才能净放大 | EDFA 工作前提 | + +### 三、信号处理与信息论(数学) + +| 原理 | 一句话 | 用在哪 | +|------|--------|------| +| **傅里叶变换** | 任何信号都能拆成不同频率正弦波的叠加 | OFDM、调制解调、色散补偿、数字滤波 | +| **快速傅里叶变换(FFT)** | 把 O(N²) 算法降到 O(N log N) | 让 OFDM 在硬件里实时跑得动 | +| **采样定理(奈奎斯特)** | 采样频率至少是最高频率的 2 倍才能不失真 | ADC / DAC 的设计基础 | +| **香农信道容量** `C = B log₂(1+SNR)` | 一个信道每秒最多能传多少信息 | LDPC / Polar / 编码增益的天花板 | +| **前向纠错(代数构造)** | 加冗余,接收端用代数运算修正错误 | Reed-Solomon、LDPC、Polar、SD-FEC | +| **CRC** | 多项式除法做校验,极快、检错强 | 各层错误检测 | + +### 四、密码学(数论 + 群论 + 有限域 + 复杂性) + +| 原理 | 一句话 | 用在哪 | +|------|--------|------| +| **质数无穷性(欧几里得)** | 我们永远找得到 2048 位的大质数 | 所有公钥密码的根材料 | +| **模幂运算 / 欧拉定理** | `g^a mod p` 用快速幂可在毫秒内算完 | RSA、Diffie-Hellman 的"正向" | +| **离散对数难题(DLP)** | 反推 `a` 目前还没有多项式时间算法 | 让 RSA / DH 安全的"反向"难度 | +| **椭圆曲线 / ECDLP** | 把群结构换到曲线上,同样难 + 更紧凑 | TLS 1.3 的 ECDHE 默认选项 | +| **有限域 GF(2⁸) / GF(2¹²⁸)** | 字节运算被定义在一个非常对称的代数结构里 | AES 列混合、GHASH 认证 | +| **单向函数 / 哈希雪崩** | 正向算容易,反向找原像不可行;改 1 比特 → 输出全变 | SHA-256、HMAC、SYN cookie、git commit | +| **计算复杂性假设** | 我们"相信"P ≠ NP,某些问题没有捷径 | 整套现代密码安全的隐含前提 | +| **Shor 算法的威胁** | 量子计算机能在多项式时间破 RSA / ECC | 后量子密码、混合密钥交换的推动力 | + +### 五、网络与系统(图论 / 排队论 / 概率) + +| 原理 | 一句话 | 用在哪 | +|------|--------|------| +| **图论最短路径** | BGP / OSPF / IS-IS 算的就是图上的"最优路径" | 互联网路由 | +| **最长前缀匹配** | 选最具体的那条规则,数据结构上是 trie | 路由器转发表查找 | +| **一致性哈希** | 节点 / 请求都丢到一个圆环上,加减节点扰动最小 | Maglev 负载均衡、分布式存储 | +| **Little's Law** `L = λ × W` | 队长 = 到达率 × 平均等待时间——任何稳态队列都满足 | 路由器、网卡队列、TCP 拥塞、服务器延迟 | +| **指数退避** | 失败次数 n,重试间隔翻倍 | Wi-Fi、TCP 重传、限流 | +| **分布式共识(两军 / FLP)** | 完全可靠的握手在异步网络里被证明不可能 | TCP 三次握手为何"够用就好" | + +### 六、工程上的"快速做出来" + +| 原理 | 一句话 | 用在哪 | +|------|--------|------| +| **缓存层级 + 局部性** | 越靠近 CPU 越快,概率上多次访问"附近"地址 | L1/L2/L3 cache、TCP 重组缓冲 | +| **流水线 + 并行** | 把单步串行的工作分阶段同时跑 | CPU、转发 ASIC、OFDM 处理流水 | +| **专用电路(ASIC / FPGA)** | 把一种特定运算硬化成电路,牺牲灵活换性能 | 路由器转发、矿机、AI 加速、AES-NI | + +把这六张表横着读,可以看到一件事:**这条链路不是 6 个领域的加法,而是 6 套数学 / 物理体系互相咬合的产物**。少了任意一个,你今天的天气查询就跑不通——香农定理给你"理论上能多快",量子力学给你"硅片为什么能开关",数论给你"为什么别人偷不走",图论给你"包应该往哪走"。 + +--- + +## 附录 D 那些"刚刚好"的巧合 + +这条链路能跑得这么稳、这么快、这么便宜,有一些是工程师推动的,但也有几样是物理世界本身"恰好"有的安排——少了任何一项,今天的互联网都会长得很不一样。 + +### 一、1550 nm 恰好处于光纤的"最低损耗窗口" + +二氧化硅(石英玻璃)对不同波长的光有不同的衰减:可见光大约 100 dB/km(完全没法跑长距离),近红外 850 nm 大约 2 dB/km,1310 nm 大约 0.35 dB/km,**1550 nm 只有约 0.2 dB/km**——这是一个由石英玻璃的瑞利散射(短波端损耗主因)+ 红外吸收(长波端损耗主因)共同决定的**自然窗口**,不是工程师选的。 + +更"凑巧"的是,**EDFA(掺铒光纤放大器)的工作波段恰好在 1530-1565 nm**——铒离子的能级跃迁波长和石英玻璃的最低损耗窗口几乎完美重合。所以一根纯光学放大的长距离光纤天然有效。如果铒离子的能级结构落在别处,海底光缆的成本和容量都会差几个数量级——**EDFA + 1550 nm 这一对组合,是物理给我们的一个免费午餐**。 + +DWDM 把几十个波长塞进 1530-1610 nm 窗口同时跑,本质就是"在物理给的这个窗口里,把每个色块都用上"。 + +### 二、地球曲率半径恰好让低频电波"贴地传几百公里" + +电磁波在地球上的传播路径,取决于频率: + +- **高频(GHz 量级,5G、Wi-Fi、卫星)**:几乎是直线传播,被建筑遮挡就没,需要密集基站补盲 +- **中频(MHz 量级,中波广播)**:可以绕地表传几百公里,白天靠地波,夜晚靠电离层 D 层吸收减弱后的天波 +- **低频 / 短波(几 MHz - 几十 MHz)**:能在地表和电离层之间反射,跳几次传几千上万公里 +- **极低频(VLF / ELF)**:能透入海水几十米,潜艇通信靠这个 + +**地球半径 6371 km 给了一个特定的曲率,大气层的电离层(60-1000 km 高度)恰好在这个尺度上方反射特定频段的电波**。这两个尺度共同决定了"哪些频段能贴地传、哪些能反射、哪些只能直线传"。 + +如果地球小一倍、大一倍,或者电离层位置不同,无线电传播规律就完全不一样——HF 短波"全球覆盖"的便宜手段可能根本不存在。5G 工程师选载频时其实没多少自由度:选低了波长太长、天线没法做小;选高了大气吸收太重、传不了几米。**能用的窗口是地球大气和地表曲率给的**。 + +### 三、硅(Silicon)的能带宽度恰好适合做晶体管 + +硅原子最外层 4 个电子,能带间隙 1.12 eV——**这个数值大到常温下不会"自激活"乱开关,小到电场容易控制让它开关**。 + +- 间隙更大(比如金刚石,5.5 eV):开关需要更高电压,功耗爆炸 +- 间隙更小(比如锗,0.67 eV):漏电严重,温度稍高就失控 + +地球地壳里 28% 是硅,几乎是仅次于氧的第二多元素。**地球恰好给了我们一种性质刚好、储量极大、提纯不太难的半导体材料**——整个微电子工业能这么便宜地做出来,这是前提。 + +### 四、真空光速给的 RTT 是"刚好可以做实时交互"的尺度 + +光速 30 万公里 / 秒。地球周长 4 万公里。**绕地球一圈最快也要 130 ms**(实际跨洋链路加上路由器排队,普遍 150-200 ms)。 + +这个数字恰好在人类感知的边缘:130-200 ms 的延迟,人脑能感觉到"有点延迟"但还能接受;真要是光速慢一千倍,远程操作、在线游戏、视频通话都会变得不可能;真要是快一千倍,GPS 卫星和地面的同步都不需要广义相对论那么复杂的修正了。 + +**我们这个时代能搞出"全球实时通信"的互联网,本质上是因为光速给的全球 RTT 上限恰好压在人类感知边界上**——快一点没必要、慢一点不可用,刚刚好。 + +### 五、大整数分解 / 离散对数恰好"做容易、算难" + +这一条不是物理凑巧,是**数学凑巧**——而且更脆弱: + +- 至少从今天的数学水平看:**找两个 1024 位质数相乘**用现代 CPU 几毫秒就行;**反过来,把它们乘出来的 2048 位数分解回去**,目前最强的算法(GNFS)需要全球算力跑几十年 +- 这条不对称性是 §9 整套公钥密码学的**全部根基**。如果数论里有人找到一个高效分解算法,整个 HTTPS 当晚就崩 +- **Shor 算法已经证明:量子计算机能在多项式时间内分解大整数**。所以这条"刚好"是有有效期的——它有效是因为足够大的量子计算机还没造出来 + +更深一层:**P ≠ NP** 这个支撑现代密码安全的核心猜想,我们其实没有证明,只是"几十年没人找到反例就当成立"。整个互联网的"私"字,压在一条**未被证明的数学猜想**上。 + +### 六、可见光带的位置:恰好不被大气吸收、恰好能被生物利用 + +这条不直接和互联网有关,但顺手一提: + +太阳光谱大部分被大气吸收(紫外被臭氧、远红外被水汽和 CO₂),**唯独可见光 + 近红外波段是"大气透明窗口"**——所以光合作用、人类视觉、光纤通信(1550 nm 在近红外里)都聚集在这个频段附近。 + +这不是巧合的工程后果——是**生物从一开始就利用了这个窗口进化出视觉**(所以叫"可见光"),而工程师们后来又把光纤通信塞到了同一个窗口的红外侧。物理世界给的窗口决定了生命的视觉范围,也决定了我们的通信范围。 + +--- + +**总结**:互联网看起来是"人造的奇迹",但仔细一层层剥开,会发现下面几乎每一层的可行性,都依赖一个"宇宙恰好这样"——光纤恰好在 1550 nm 损耗最低,地球曲率恰好让无线电能绕弯,硅的能带恰好能做晶体管,光速恰好快得够实时通信但慢得需要拥塞控制,数论里恰好存在"做容易算难"的不对称性。 + +少任何一样,这条链路就根本不会以今天的方式存在。**人类的工程努力是把这些"刚刚好"接起来——而不是创造它们。** + +--- + +## 写在最后 + +下次你打开手机搜个天气、刷个微博、看个视频,可以想想:这个看似平淡的动作,背后有几十亿个晶体管在切换、有激光脉冲在万里之外的海底光纤里飞驰、有几十种数学算法在保证你的数据准确无误地到达、有几代工程师设计的协议层层协作。 + +互联网真正神奇的地方不是它有多快,而是它居然能这样工作——而且对绝大多数用户来说,完全透明。 + +--- + +### 未完待续:接下来十年的链路会怎么变 + +这条链路不是完成态。几乎每一层都在继续演化,你这代人的有生之年会亲眼看到它们换一遍: + +- **物理层**:更高频段(亚 THz、光通信)、低轨卫星互联网(Starlink 这类)正在重塑"无线"。**空芯光纤**(光子晶体光纤)可能让光速重新回到接近真空中的 30 万 km/s,而不是现在的 20 万——跨洋 RTT 因此可能下降三分之一 +- **网络层**:**IPv6** 终于在普及,IPv4 还死不掉,两者会长期并存。BGP 的安全顽疾(路由劫持、泄漏)正用 **RPKI** 慢慢补 +- **传输层**:**HTTP/3 + QUIC** 把 TLS、TCP、多路复用揉成一个新协议,正在大量替换 HTTPS over TCP。对手机用户来说,这意味着弱网和切换网络时加载更稳 +- **加密层**:**后量子密码(PQC)** 已经进入 TLS 1.3 实测,Cloudflare 2024 年起在全网启用混合 ECDHE+Kyber。我们这代人会在自己的有生之年亲历一次**大规模密码体系迁移**——上一次是 RSA→ECC,下一次是 ECC→PQC,触发因素是量子计算的逐步成熟 +- **服务器层**:CPU 让位给 **DPU + AI 加速器**。更深一层的变化是**流量本身的形态变了**——AI 推理流量(ChatGPT 这种)单次请求消耗的算力、产生的下行流量,和传统网页搜索差几个数量级,数据中心的设计正在被重写 +- **审查与反审查**:GFW 加上 ECH 拦截、机器学习流量识别,Reality 加上流量混淆、对抗样本生成——猫鼠游戏会在更高维度继续 + +如果 30 年后有人再写这篇文档,**章节大概还是这些章节,但每一章里的具体技术都会不同**。物理定律不变(Maxwell、量子、香农),数学根基不变(数论、群论、有限域),工程层面会换好几代人。 + +--- + +### 一条线上的几代人 + +这条链路最让人感慨的地方,不只是它涉及多少种技术,而是——这条链上的每一环,是不同的人、在不同的时代、在世界不同的角落做出来的。他们彼此往往**素不相识**,甚至**互不知道对方在做什么**。 + +**克劳德·香农(Claude Shannon)** 1948 年在贝尔实验室发表《通信的数学理论》,推出了那个简洁的**香农信道容量公式**。他不知道这个公式七十年后会决定你的手机能不能在地铁里加载视频。 + +**惠特菲尔德·迪菲(Whitfield Diffie)** 和 **马丁·赫尔曼(Martin Hellman)** 1976 年发表 **Diffie-Hellman** 论文,他们的初衷是"密码学不应该只属于政府和军方"。他们没法预见三十年后全世界几十亿人每一次刷网页都在用他们的数学。 + +设计 TCP 的 **Vint Cerf** 和 **Bob Kahn**,1974 年在斯坦福写下那份协议草案的时候,根本没想过它会演化出 **BBR** 这种拥塞控制算法,更不会想到它会跑在一万米深的海底光缆里。 + +发明**掺铒光纤放大器(EDFA)**的 **David Payne**,1986 年在南安普顿大学搞研究的时候,大概也没想到这个东西会沉在太平洋海底——让你在咖啡馆里查个天气,顺顺当当。 + +更早一点的几位,自己根本没看到这条链路: + +- **詹姆斯·克拉克·麦克斯韦(James Clerk Maxwell)** 1865 年写下那四条方程,他相信电磁波存在,但 1879 年去世时世界上还没人造出过无线电 +- **阿尔伯特·爱因斯坦(Albert Einstein)** 1917 年提出**受激辐射**,纯粹是为了把光的量子统计算清楚,激光器是 43 年后才被造出来的 +- **欧拉(Leonhard Euler)** 18 世纪研究的费马小定理 / 欧拉定理,两百多年后成了 RSA 和 Diffie-Hellman 的代数地基 + +更有意思的是,这些人很多都不是为了"造互联网"而工作的: + +- 香农是为了搞清楚"通信的本质是什么" +- 迪菲是为了"对抗政府对密码学的垄断" +- **Reed 和 Solomon** 1960 年发明纠错码,是为了一个"理论上有趣的代数问题" +- **椭圆曲线**密码学的源头可以追溯到 19 世纪数学家研究椭圆函数,纯粹的"无用之学" + +然后某一天,某个工程师翻到一篇几十年前的数学论文,说"嘿,这个东西好像可以解决我手头的问题"——于是这些散落在各个角落的智慧,就被一点一点拼到了一起。 + +这条链上还有无数没有名字的人:画 ASIC 版图的工程师、调试基站射频的工程师、运维海底光缆的水手、写 Linux 内核网络栈那几万行代码的志愿者、第一次把 BGP 路由表配通的网管。他们大部分人不会出现在任何历史书里。 + +但你点一下手机,他们几代人的工作,就全部在那 300 毫秒里默默完成了一遍。 + +科幻作家 **Arthur C. Clarke** 说过一句话:"任何足够先进的技术,都与魔法无异。"我们今天习以为常的事情——按一下屏幕,半个地球外的信息就出现在眼前——如果让 100 年前的人看到,他们大概率会认为这是某种神迹。 + +但它不是神迹。它是一群人,在一百多年的时间里,一砖一瓦垒起来的。 + +我们这代人是站在一座大山上的。这座山是无数前人用毕生心血堆出来的——他们中的大多数,从未谋面,也未必都能等到自己的那一砖被用上的那一天。 + +能在这个时代生活,本身就是一件很幸运的事。 + +--- + +### 尾声:回到那个咖啡馆门口 + +那个站在上海咖啡馆门口的你,看完天气,锁屏,把手机塞回口袋,推门走进去,点了一杯咖啡。 + +店里的 Wi-Fi 自动连上了——后台又跑了一遍 DHCP、ARP、DNS、TCP、TLS、HTTP。你点单的小程序加载出来,扫码付款的二维码刷新了一下——后台又跑了一遍 HTTPS、签名验证、银行接口、风控模型。咖啡店的音响里放着一首歌,从某个 CDN 边缘节点流过来——后台又跑了一遍 QUIC、自适应码率、CDN 路由。 + +每一件你已经完全不会去想的事情,都还是这同一个奇迹的延续。 + +只是你已经不再注意了。 + +**这就是好工程的最终形态——它好到让你忘了它存在**。 + +而这篇文章,只不过是把那一两秒钟里你忘掉的东西,慢慢地、再讲一遍。 diff --git a/content/posts/2026-05-02-ai-engineer-map.md b/content/posts/2026-05-02-ai-engineer-map.md new file mode 100644 index 0000000..be51e84 --- /dev/null +++ b/content/posts/2026-05-02-ai-engineer-map.md @@ -0,0 +1,640 @@ + +--- +title: "一份 AI 工程师的知识地图(2026 版)" +date: 2026-05-02 +slug: ai-engineer-map +tags: ["AI", "LLM", "Prompt", "RAG", "MCP", "Agent", "Claude", "Cursor", "Ollama"] +categories: ["AI"] +description: "从大模型 / Prompt / RAG / MCP / Agent / 多模态 / 成本控制 / 编码工具一路捋下来,适合有技术背景的开发者快速建立 AI 知识框架。" +draft: false +--- + +> 适合有一定技术背景的开发者快速建立 AI 知识框架。涵盖核心概念、工程实践、工具选型,持续更新。 + +--- + +## 一、基础层:大模型 + +一切的起点。Claude(Anthropic)、GPT(OpenAI)、Gemini(Google)、DeepSeek、Qwen(阿里)都是"引擎",通过 API 对外提供服务,上层所有应用都建立在这些模型之上。 + +**主流模型对比:** + +| 模型 | 厂商 | 特点 | +|------|------|------| +| Claude 系列 | Anthropic | 长上下文强、指令遵循准确、代码能力突出 | +| GPT-4o / o 系列 | OpenAI | 生态最成熟、多模态能力强、工具链完善 | +| Gemini 系列 | Google | 原生多模态、超长上下文(1M token)、深度集成 Google 工具 | +| DeepSeek | 深度求索 | 推理能力强、API 价格极低、开源友好 | +| Qwen 系列 | 阿里 | 中文效果好、有本地部署版本、国内访问友好 | + +模型能力差距在收窄,但复杂推理、超长上下文、低幻觉率这几个维度顶尖模型依然领先。选型时不能只看价格,要结合实际任务类型判断。 + +--- + +## 二、理解模型的工作方式 + +在用 AI 之前,有两件事必须先搞清楚,否则会踩很多莫名其妙的坑。 + +### 上下文窗口(Context Window) + +模型每次能"看到"的文本总量是有上限的,这个上限叫上下文窗口,输入和输出加在一起不能超过这个数。 + +**各模型上下文窗口:** + +| 模型 | 上下文窗口 | +| ----------------- | ---------- | +| GPT-4o | 128K token | +| Claude Sonnet 4.6 | 200K token | +| Gemini 1.5 Pro | 1M token | +| DeepSeek-V3 | 128K token | + +1 token 大约是 0.75 个英文单词,中文每个字大约 1~2 token。200K token 大概是一本 30 万字的书。 + +**两个重要的坑:** + +第一,超出窗口后,模型不会报错,而是"忘掉"最早的内容。如果你把一段很长的代码库塞给 AI,它可能已经把最开始的文件内容忘了,给出的建议会出现前后矛盾。 + +第二,"Lost in the Middle"问题——研究发现,模型对窗口开头和结尾的内容记忆最好,中间部分最容易被忽略。所以关键信息要放在 prompt 的开头或结尾,而不是埋在中间。 + +### AI 幻觉 + +模型生成文字的本质是**预测下一个概率最高的 token**,不是在查找事实。这意味着它在不确定的时候不会说"我不知道",而是倾向于生成一段"听起来合理"的内容。 + +**减少幻觉的主要手段:** + +- **RAG**:把真实文档片段塞进 prompt,给模型"答题材料"(详见第四节) +- **降低 temperature**:temperature 越低,输出越保守、越确定;越高,越有创意但越容易编造 +- **Chain of Thought**:让模型先一步步推理,再给结论,减少跳步错误 +- **引用溯源**:要求模型回答时标注来源段落,可验证 +- **RLHF 训练**:厂商通过人类反馈训练模型,让它学会说"我不确定" + +幻觉目前无法彻底消除。法律、医疗、财务等高风险场景无论模型多强,都必须有人工审核兜底。 + +--- + +## 三、Prompt Engineering + +Prompt 是和 AI 沟通的唯一渠道。写得好和写得差,效果差距可以很大。几个立竿见影的技巧: + +### 系统提示词(System Prompt) + +在对话开始前,用系统提示词定义 AI 的角色、能力边界和输出要求。这是最基础也最重要的一步。 + +``` +你是一个游戏后端开发专家,熟悉 .NET 和 SQL Server。 +回答时: +- 使用 C# 代码示例 +- 指出潜在的性能问题 +- 如果不确定,直接说不确定,不要猜测 +``` + +### Few-shot 示例 + +与其描述你想要什么,不如直接给 2~3 个输入-输出的例子,模型会自动理解规律。 + +``` +将以下日志条目格式化为 JSON: + +输入:[2026-03-18 14:23:01] ERROR UserService 用户登录失败 uid=10234 +输出:{"time":"2026-03-18 14:23:01","level":"ERROR","service":"UserService","msg":"用户登录失败","uid":10234} + +输入:[2026-03-18 14:25:43] INFO PayService 支付成功 orderId=88765 +输出: +``` + +### Chain of Thought(思维链) + +在 prompt 里加上"请一步一步思考",让模型把推理过程写出来再给结论。对复杂问题效果显著,错误率明显下降。 + +``` +请一步一步分析这段 SQL 的性能问题,然后给出优化建议。 +``` + +### 指定输出格式 + +明确告诉模型输出的结构,否则格式会很随意,后续解析麻烦。 + +``` +请用以下 JSON 格式返回结果,不要有其他内容: +{ + "issue": "问题描述", + "severity": "high|medium|low", + "suggestion": "修复建议" +} +``` + +### 反面示例(告诉模型不要做什么) + +光说"要做什么"有时候不够,同时说"不要做什么"往往更有效。 + +``` +分析这段代码,不要重复我已知的内容,不要给出明显的建议, +直接定位最可能导致线上 bug 的地方。 +``` + +### 常见误区 + +- **Prompt 越长越好**:不对,冗余信息会稀释关键指令,模型容易抓不住重点 +- **"请帮我"、"谢谢"有用**:没有,礼貌词不影响输出质量 +- **一次写好 prompt**:Prompt 是需要反复调试的,像调代码一样迭代 + +--- + +## 四、核心技术 + +### RAG(检索增强生成) + +**解决的问题:** AI 不知道你的内部数据,也不了解你业务的最新状态。 + +解法不是训练模型(成本高、周期长、数据泄露风险大),而是在每次查询时,把相关文档片段检索出来,临时塞进 prompt 一起发给 AI。 + +**完整流程:** + +``` +【离线】文档切片 → Embedding 向量化 → 存入向量数据库 + +【在线】用户提问 → 问题向量化 → 检索相关片段 → 拼 prompt → AI 生成回答 +``` + +**检索方式要按场景选:** + +| 场景 | 推荐方式 | +|------|---------| +| 语义模糊查询 | 向量检索 | +| 精确关键词匹配 | 全文检索(ES / BM25)| +| 结构化数据 | 直接 SQL | +| 实时状态数据 | 直接调接口 | + +生产环境通常用**混合检索**(向量 + 关键词并行),再加 **Reranker** 对两路结果重排融合,效果比单一检索稳定得多。 + +**RAG 效果差的常见原因:** +- 切片粒度不合适:太大检索不精准,太小上下文断裂 +- Embedding 模型语言不匹配:中文内容要用中文模型 +- 缺 Reranker:向量相似度不等于语义相关,需要二次排序 + +--- + +### Function Calling / Structured Output + +**解决的问题:** 默认情况下模型输出自由文本,开发者要从中解析结构化数据很麻烦,而且不稳定。 + +Function Calling 让模型直接输出结构化的函数调用参数,或者严格按 JSON Schema 输出。这是开发者在系统里接入 AI 时几乎必用的能力。 + +**三种形式:** + +**JSON Mode**:告诉模型必须输出合法 JSON,但不约束具体字段。 + +**Function Calling**:你预先定义一组函数和它们的参数 Schema,模型自己判断什么时候调哪个函数,以什么参数调用。 + +```csharp +// 定义函数供模型选择调用 +var tools = new[] { + new Tool("get_player_info", "查询玩家信息", new { + type = "object", + properties = new { + playerId = new { type = "string", description = "玩家ID" } + } + }) +}; + +// 模型判断需要查询玩家时,会返回: +// { "name": "get_player_info", "arguments": { "playerId": "10234" } } +// 你的代码执行后,把结果再传回给模型 +``` + +**Structured Outputs**:最严格的形式,模型输出必须完全符合你指定的 JSON Schema,字段和类型都有保证,不会多也不会少。 + +**适合使用的场景:** +- 从非结构化文本中提取信息(日志分析、邮件解析) +- 让 AI 决策后直接触发业务逻辑 +- 任何需要程序化处理 AI 输出的场景 + +--- + +### 多步骤编排 + +AI 作为整个流程的指挥者,自主判断下一步做什么、调哪个工具,直到任务完成。 + +``` +用户:"分析上个月的流失情况并发报告给运营" + ↓ +AI → 调 GetChurnData(month=3) + ↓ +AI → 调 GetChurnByServer() + ↓ +AI 整合数据,生成分析报告 + ↓ +AI → 调 SendEmail(to="运营组") +``` + +Semantic Kernel 的 Plugin 机制就是干这件事的。 + +--- + +### Fine-tuning(微调)vs RAG + +两者经常被混淆,但解决的是不同问题: + +| 维度 | RAG | Fine-tuning | +|------|-----|-------------| +| 解决的问题 | 模型不知道你的数据 | 模型不擅长你的任务风格 | +| 数据要求 | 文档即可 | 需要大量高质量的输入-输出对 | +| 更新成本 | 低,随时更新文档 | 高,每次更新需要重新训练 | +| 适合场景 | 知识库问答、文档检索 | 特定领域语气/格式/专业术语 | +| 费用 | 低 | 高 | + +**结论:** 绝大多数企业场景先上 RAG,Fine-tuning 只在 RAG 效果不够好、且有大量标注数据的情况下考虑。 + +--- + +## 五、接入方式 + +### 直接调 API + +本质就是一个 HTTPS POST,传 prompt,拿结果。简单、可控、成本透明。 + +适合的场景:活动文案生成、内容翻译、用户评论分析、客服自动回复、日志摘要生成等固定业务场景。 + +如果公司只用一个模型、场景固定,直接封装一个 `AiService` 类就够了,不需要引入额外框架。 + +--- + +### Semantic Kernel(编排框架) + +微软出品,支持 .NET / Python / Java,对 .NET 技术栈的团队非常友好。 + +类比为 AI 领域的 EF Core——屏蔽不同模型间的 API 差异,业务代码面向接口编程,换模型只改配置。 + +```csharp +// Program.cs +builder.Services.AddKernel() + .AddAnthropicChatCompletion("claude-sonnet-4-6", apiKey); + +// Service 层注入使用 +var result = await kernel.InvokePromptAsync("分析这个玩家的充值行为:{{$input}}"); +``` + +适合场景:多步骤 AI 编排、RAG、需要支持多模型切换的团队项目。 + +--- + +### MCP(Model Context Protocol) + +Anthropic 于 2024 年 11 月发布的开放协议,定义了"AI 如何标准化调用外部工具",现已成为全行业事实标准。 + +- 2025 年 3 月 OpenAI 全面跟进 +- 2025 年 12 月捐给 Linux 基金会,OpenAI、Google、微软均为成员 +- 目前 10,000+ MCP Server,月下载量 9700 万 + +``` +AI 客户端(Claude / Cursor / Antigravity) + ↓ MCP 协议 + MCP Server ← 你来实现这一层 + ↓ + 你的业务系统 / 数据库 / 内部接口 +``` + +MCP Server 是独立进程,和现有系统完全解耦,任何语言都能写。写好之后,所有支持 MCP 的 AI 客户端都能调用你的系统。 + +**和直接调 API 的本质区别:** 直接 API 是"你的代码决定每一步,AI 只是执行节点";MCP 是"AI 自己决定走几步、调哪个工具"——控制权从代码转移到了模型。 + +--- + +### 本地部署(Ollama) + +**解决的问题:** 数据不能出公司网络,或者不想持续付 API 费用。 + +Ollama 是一个工具,把主流开源模型打包成可以在本地直接运行的形式,接口和 OpenAI API 完全兼容,切换成本接近零。 + +```bash +# 安装后一行命令拉模型并启动 +ollama run qwen2.5:14b + +# 用标准 OpenAI 格式调用本地模型 +curl http://localhost:11434/v1/chat/completions \ + -d '{"model":"qwen2.5:14b","messages":[{"role":"user","content":"你好"}]}' +``` + +**可运行的主流模型:** + +| 模型 | 参数量 | 最低显存 | 特点 | +|------|--------|---------|------| +| Qwen2.5 | 7B / 14B | 8GB / 16GB | 中文效果好,阿里出品 | +| DeepSeek-R1 | 7B / 14B | 8GB / 16GB | 推理能力强,开源 | +| Llama 3.3 | 70B | 48GB+ | Meta 出品,综合能力强 | +| Mistral | 7B | 8GB | 速度快,适合简单任务 | + +**适合的场景:** +- 处理公司内部敏感数据(数据库连接串、用户信息) +- 代码补全类任务(质量接近商业模型) +- 高频调用、成本敏感的场景(本地跑不计 token 费用) + +**不适合的场景:** 复杂推理、多语言翻译、需要最新知识——这些目前本地模型和顶尖商业模型还有明显差距。 + +--- + +## 六、多模态 + +多模态指模型能同时处理多种类型的数据。目前最成熟的是**文本 + 图像**,主流模型(GPT-4o、Claude 3.5 Sonnet、Gemini 1.5 Pro)都已全面支持。 + +**实际能做什么:** + +- **截图转文字**:把 UI 截图发给 AI,让它描述问题或生成对应代码 +- **图表分析**:把折线图、柱状图截图发给 AI,它能读懂数据并给出分析 +- **文档图片解析**:扫描件、截图中的表格、合同内容提取,不需要 OCR 前处理 +- **设计稿转代码**:把 UI 设计图发给 AI,让它生成 HTML/CSS 框架(不是完美的,但能省很多时间) + +**在代码里调用视觉能力:** + +```python +import anthropic, base64 + +with open("screenshot.png", "rb") as f: + img_data = base64.standard_b64encode(f.read()).decode("utf-8") + +client = anthropic.Anthropic() +message = client.messages.create( + model="claude-sonnet-4-6", + messages=[{ + "role": "user", + "content": [ + {"type": "image", "source": {"type": "base64", "media_type": "image/png", "data": img_data}}, + {"type": "text", "text": "这个页面的布局有什么问题?"} + ] + }] +) +``` + +**当前局限:** 视频理解在部分模型上支持,但效果不稳定;实时音频目前只有 GPT-4o 的 Realtime API 支持,成本较高。 + +--- + +## 七、成本控制 + +用 API 很容易不知不觉花很多钱,几个实用的省钱方法: + +### 模型路由(按任务难度选模型) + +不是所有任务都需要最贵的模型。建立一个简单的路由规则,根据任务复杂度选不同模型: + +| 任务类型 | 推荐模型 | 大约成本比 | +|---------|---------|-----------| +| 简单分类、关键词提取 | Claude Haiku / GPT-4o-mini | 1x | +| 普通问答、代码补全 | Claude Sonnet / GPT-4o | 10x | +| 复杂推理、长文档分析 | Claude Opus / o3 | 50x+ | + +### Prompt 缓存(Cache) + +如果你每次请求都带着相同的系统提示词或大段文档,Anthropic 和 OpenAI 都支持 Prompt Cache——相同内容只计算一次,后续请求的这部分最多打 9 折,最高可省 90% 的输入 token 费用。 + +```python +# Anthropic Cache Control 示例 +messages = [{ + "role": "user", + "content": [ + { + "type": "text", + "text": very_long_system_doc, # 长文档 + "cache_control": {"type": "ephemeral"} # 标记为可缓存 + }, + {"type": "text", "text": user_question} # 每次变化的问题 + ] +}] +``` + +### Batch API + +对于不需要实时响应的任务(比如批量分析日志、批量生成文案),使用 Batch API 可以享受约 50% 的价格折扣,代价是处理时间延迟到几小时内完成。 + +### 控制 Output 长度 + +输出 token 的价格通常是输入的 3~5 倍。明确告诉模型输出长度: + +``` +请用不超过 3 句话回答,不要有多余解释。 +``` + +### Token 计算工具 + +OpenAI 和 Anthropic 都提供 Tokenizer 工具,可以在发送前估算费用,避免意外超支。 + +--- + +## 八、工具生态 + +### LlamaIndex + +专注 RAG 场景的 Python 框架,文档处理、向量检索、多路检索融合都做得很深。上手快,适合快速搭 RAG 原型。 + +```python +# 建索引(离线,跑一次) +documents = SimpleDirectoryReader("./docs").load_data() +index = VectorStoreIndex.from_documents(documents) + +# 查询(在线,每次请求) +query_engine = index.as_query_engine() +response = query_engine.query("这个接口的限流规则是什么?") +``` + +**向量库选型:** + +| 选项 | 适合情况 | +|------|---------| +| PostgreSQL + pgvector | 已有 PG,成本最低,省事 | +| Qdrant | 自部署,高性能,适合大规模 | +| Pinecone | 不想运维,直接用云托管 | + +--- + +### OpenClaw + +2025 年底爆火的开源 AI Agent,60 天内积累 24.7 万 GitHub Star。核心理念是:**以消息平台作为操作界面,让 AI 替你在本机或服务器上自主执行任务**。 + +你不需要打开任何 App,直接在 Telegram、Slack、微信里发一条消息,AI 就能完成文件操作、调接口、发邮件、查数据——完全跑在你自己的机器上,数据不出去。 + +**支持 50+ 消息平台:** WhatsApp、Telegram、Slack、Discord、微信(WeCom)、钉钉、飞书、Teams、Signal、iMessage…… + +**两种能力扩展机制:** + +- **Skills(技能包)**:结构化的"操作手册",明确告诉 AI 在特定场景下按什么顺序调哪些工具。社区已有 100+ 预置 Skills,可以自己写,甚至让 AI 来写新的 Skill +- **MCP**:对外连接标准协议,把公司内部系统接进来。Skills 解决"什么时候怎么调",MCP 解决"能不能调" + +**典型使用场景:** +- 在 Telegram 里发"帮我拉今天的错误日志,整理成表格" +- 定时任务:每天早上自动查数据库、生成日报、发给指定群 +- 接入公司内部系统,变成团队共用的 AI 助手机器人 + +**部署:** 支持 Windows / macOS / Linux 本地部署,也支持阿里云、腾讯云一键部署,国内中文社区资料丰富。底层默认接 Claude,也支持 GPT、DeepSeek、Qwen。 + +2026 年 2 月原作者加入 OpenAI,项目移交开源基金会,仍在活跃维护。 + +**MiMo Claw(小米)** 是同类产品,深度接入小米生态,一键部署,适合已在用小米设备的用户。 + +--- + +## 九、企业级 AI 应用场景 + +| 场景 | 推荐方案 | 备注 | +|------|---------|------| +| 智能客服 | 直接调 API + RAG | AI 先回答,答不了查知识库,再不行转人工 | +| 活动文案生成 | 直接调 API | 给模板和关键词,批量生成 | +| 内部知识库问答 | RAG | 开发文档、运营手册、配置说明 | +| 代码 Review | 直接调 API | 提交 PR 时触发,自动给出评审意见 | +| 日志分析 / 排障 | 直接调 API + Structured Output | 从非结构化日志提取关键信息 | +| 数据分析 | 直接调 API + SQL | 自然语言转 SQL,结果解释成人话 | +| 合同 / 文档审查 | RAG | 检索相关条款 + AI 比对分析 | +| 跨系统自动化任务 | 多步骤编排 + MCP | 自动拉数据、生成报告、发通知 | +| 图片内容审核 | 多模态 API | 截图、UGC 图片内容检测 | +| 游戏内容生成 | 直接调 API | NPC 对话、任务描述、世界观文本 | + +--- + +## 十、AI 编码工具 + +> 模型能力溢出之后,竞争从"谁的模型更聪明"转移到"怎么把模型能力接进工作流"。AI 编码工具是开发者目前最直接感受到生产力变化的地方。 + +### ⚠️ 使用前必看:翻墙说明 + +**Claude 系(claude.ai、Claude Code):必须虚拟网卡模式** + +普通代理(SSR、V2Ray 仅配系统代理)大多数情况下无法使用,Claude 会检测 IP 质量。必须使用 **TUN 模式**(虚拟网卡),让所有流量走网卡层,比如 Clash Verge 开启 TUN 模式,或者使用 Warp。 + +**其余工具:普通代理即可** + +Cursor、GitHub Copilot、Antigravity、Codex 对代理要求没那么严,配置好系统代理即可。 + +--- + +### IDE 派 + +#### GitHub Copilot + +最老牌的 AI 编码助手,GitHub 出品,深度集成进 VS Code、JetBrains 全家桶、Visual Studio,不需要换编辑器。 + +- **行内补全**:预测下一行或下一段,Tab 接受 +- **Copilot Chat**:侧边栏对话,解释代码、找 Bug、生成测试 +- **Copilot Edits**:跨多文件批量修改 +- **Copilot Agent**:自主完成较复杂任务,可以发 PR + +底层以 GPT 系列为主,近期加入 Claude 和 Gemini 可选。 + +**价格:** 免费版(2000 次补全 + 50 次 Chat)/ Pro $10/月 / Pro+ $39/月 / 学生免费 +**翻墙:** 普通代理即可 + +--- + +#### Cursor + +最早把 AI 深度集成进编辑器的产品,2024 年爆火,目前是这个赛道标杆。基于 VS Code fork,迁移成本接近零。 + +- **Tab 补全**:预测整段要改的内容,改了函数签名,调用处参数一并改好 +- **Cmd+K**:选中代码 + 描述,直接内联修改 +- **Chat 侧边栏**:带完整代码库索引,跨文件理解逻辑 + +底层模型可选:Claude、GPT-4o、DeepSeek 都支持。 + +**价格:** 免费版 / Pro $20/月 / Pro+ $60/月(积分制,月积分 = 套餐价美元数) +**翻墙:** 普通代理即可 + +--- + +#### Google Antigravity + +Google 2025 年 11 月随 Gemini 3 发布,VS Code fork,理念比 Cursor 更激进。 + +- **Editor 模式**:类似 Cursor,Tab 补全 + 内联改 + 侧边 Agent +- **Manager 模式**:同时派发多个 Agent 并行处理不同任务,统一监控 + +AI 拥有直接操作文件系统、终端、内置浏览器的权限,同时支持 Claude 和 GPT。 + +**价格:** 免费版(重度使用 2-3 小时触达限额,7 天刷新)/ Pro $20/月 / Ultra $250/月 +**翻墙:** 普通代理即可 + +--- + +### CLI Agent 派 + +> 你说清楚要做什么,AI 自己去读代码、改文件、跑命令,完事汇报。 + +#### Claude Code + +Anthropic 出品,目前公认 Agent 能力最强的 CLI 工具。 + +```bash +claude "找出所有数据库查询超过 500ms 的接口,加上耗时日志并写单元测试" +``` + +- 完整的文件读写和终端执行权限 +- 擅长跨文件理解和大范围改动 +- 支持 MCP,可接入自定义工具 +- SSH 进服务器也能用 + +**价格:** Claude Pro $20/月 起(无免费版),重度用 Max $100/$200/月;也可 API Key 按 token 计费 +**翻墙:** ⚠️ 必须 TUN 模式虚拟网卡 + +--- + +#### Codex(OpenAI) + +OpenAI 2025 年 4 月发布,沙箱隔离运行,多任务并行,token 效率约为 Claude Code 的 4 倍。 + +**价格:** 工具开源免费,走 ChatGPT Plus($20/月)或 OpenAI API 额度 +**翻墙:** 普通代理即可 + +--- + +### 综合对比 + +| 工具 | 类型 | 价格 | 翻墙要求 | 亮点 | +|------|------|------|---------|------| +| GitHub Copilot | IDE 插件 | 免费 / $10 / $39 | 普通代理 | 不换编辑器,企业管控友好 | +| Cursor | IDE(VS Code fork)| 免费 / $20 / $60 | 普通代理 | Tab 补全体验最好,主流首选 | +| Antigravity | IDE(VS Code fork)| 免费 / $20 | 普通代理 | 多 Agent 并行,最激进 | +| Claude Code | CLI Agent | $20~$200/月 | ⚠️ 必须虚拟网卡 | Agent 能力最强,支持 MCP | +| Codex | CLI Agent | API 按量 / $20+ | 普通代理 | token 效率高,沙箱隔离 | + +两个流派不互斥:日常用 Cursor,复杂重构或批量任务丢给 Claude Code。 + +--- + +## 十一、关键判断:什么时候用什么 + +**直接调 API 就够了,当:** +业务场景固定、输入输出明确、公司只用一个模型、团队规模小不需要统一抽象。 + +**需要引入 Semantic Kernel,当:** +需要多步骤编排、做 RAG、在多模型间切换、有多个团队共用 AI 能力。 + +**需要 MCP,当:** +想让 AI 主动操作你的系统、想让 Cursor / Claude Desktop 直接访问内部数据、在构建 Agent 类产品。 + +**需要 RAG,当:** +AI 需要访问内部文档或私有知识库、不想训练模型、回答结果需要能溯源到具体文档。 + +**用本地部署(Ollama),当:** +数据不能出公司网络、高频调用成本敏感、对推理质量要求不是极高。 + +**用多模态,当:** +需要处理图片内容、截图分析、UI 稿转代码、图表数据提取。 + +--- + +## 十二、现状与趋势 + +**已经发生的:** +- MCP 在 16 个月内成为 AI 工具调用的事实标准,速度远超以往任何协议 +- AI 编码工具从"补全代码"进化到"自主完成任务",Cursor 的 Tab 到 Claude Code 的 Agent 只用了不到两年 +- 多模态从实验功能变成了主流模型的标配能力 +- 模型各家差距在收窄,工具层和工程实践的差异越来越重要 + +**正在发生的:** +- 多 Agent 并行协作(一个任务拆给多个 AI 同时跑)从实验室走向产品 +- "Vibe Coding"——用自然语言描述,让 AI 生成整个功能模块——正在成为部分开发者的主力工作方式 +- 本地部署模型质量快速追赶商业 API,轻量任务本地跑已经够用 +- 各大云厂商开始把 AI Agent 能力直接内置进开发平台 + +**还没解决的:** +- 真正落地的企业级 AI 产品依然不多,大部分还在 POC 阶段 +- 生产环境的效果稳定性、成本控制、幻觉处理依然是难点 +- AI 有了文件和终端权限之后,安全和误操作风险如何防控 +- 长上下文场景下的效果一致性:窗口大了不代表记忆力变好 diff --git a/content/search/_index.md b/content/search/_index.md new file mode 100644 index 0000000..aa0803f --- /dev/null +++ b/content/search/_index.md @@ -0,0 +1,6 @@ +--- +title: 搜索 +description: 站内搜索 +layout: search +slug: search +--- diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml new file mode 100644 index 0000000..18c4d5d --- /dev/null +++ b/deploy/docker-compose.yml @@ -0,0 +1,28 @@ +# 拉到 NAS:/volume1/docker/compose/blog/docker-compose.yml +# 仅供参考 / 重建用 —— 5-02 容器已在跑,正常情况下不需要重建 +# +# 部署: +# ssh nas +# cd /volume1/docker/compose/blog +# docker compose up -d +# +# 真正"自动更新"靠 Gitea Actions(.gitea/workflows/build.yml): +# push → runner build hugo → rsync 到 /volume1/docker/blog/public/ +# nginx 直接 serve 新文件(只读挂载,文件系统层同步,无需重启容器) + +services: + blog: + image: nginx:alpine + container_name: blog + restart: unless-stopped + ports: + - "8082:80" + volumes: + - /volume1/docker/blog/public:/usr/share/nginx/html:ro + environment: + - TZ=Asia/Shanghai + healthcheck: + test: ["CMD", "wget", "-q", "-O-", "http://localhost/"] + interval: 30s + timeout: 5s + retries: 3 diff --git a/hugo.yaml b/hugo.yaml new file mode 100644 index 0000000..626179a --- /dev/null +++ b/hugo.yaml @@ -0,0 +1,150 @@ +baseURL: "https://blog.zhengchentao.win/" +title: "陶政辰的笔记本" +theme: stack + +defaultContentLanguage: zh-cn +hasCJKLanguage: true +languageCode: zh-cn + +pagination: + pagerSize: 5 + +permalinks: + posts: "/posts/:slug/" + page: "/:slug/" + +services: + disqus: + shortname: "" + +params: + mainSections: ["posts"] + rssFullContent: true + favicon: "" + + footer: + since: 2026 + customText: "" + + dateFormat: + published: "2006-01-02" + lastUpdated: "2006-01-02 15:04" + + sidebar: + compact: false + emoji: "" + subtitle: "C# / .NET / React / Vue 工程师 · 上海 · 写写技术和不太成熟的思考" + avatar: "img/avatar.jpg" + + article: + math: true + toc: true + readingTime: true + license: + enabled: true + default: "本文按 [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh) 协议发布" + + comments: + enabled: false + provider: disqus + + widgets: + homepage: + - type: search + - type: archives + params: + limit: 5 + - type: categories + params: + limit: 10 + - type: tag-cloud + params: + limit: 10 + page: + - type: toc + + opengraph: + twitter: + site: "" + card: summary_large_image + + defaultImage: + opengraph: + enabled: false + local: false + src: "" + + colorScheme: + toggle: true + default: auto + + imageProcessing: + cover: + enabled: true + content: + enabled: true + +menu: + main: + - identifier: home + name: 首页 + url: / + weight: -100 + params: + icon: home + - identifier: archives + name: 归档 + url: /archives/ + weight: -90 + params: + icon: archives + - identifier: about + name: 关于 + url: /about/ + weight: -80 + params: + icon: user + social: + - identifier: github + name: GitHub + url: "https://github.com/zhengchentao" + params: + icon: brand-github + - identifier: rss + name: RSS + url: "/index.xml" + params: + icon: rss + +related: + includeNewer: true + threshold: 60 + toLower: false + indices: + - name: tags + weight: 100 + - name: categories + weight: 200 + +markup: + goldmark: + extensions: + passthrough: + enable: true + delimiters: + block: [["\\[", "\\]"], ["$$", "$$"]] + inline: [["\\(", "\\)"]] + renderer: + unsafe: true + tableOfContents: + endLevel: 4 + ordered: true + startLevel: 2 + highlight: + noClasses: false + codeFences: true + guessSyntax: true + lineNoStart: 1 + lineNos: false + lineNumbersInTable: true + tabWidth: 4 diff --git a/themes/stack b/themes/stack new file mode 160000 index 0000000..c0f57ba --- /dev/null +++ b/themes/stack @@ -0,0 +1 @@ +Subproject commit c0f57bab7a0879ad204266bb0ca11c361d322682