# 协议是程序之间对话的方式

速成课,讲现代应用赖以运转的数据交换协议。21 个概念——TCP、UDP、HTTP、TLS、HTTP/2 与 HTTP/3、REST、GraphQL、gRPC、SOAP、Polling、SSE、WebSocket、Webhook、Message Broker、消息队列(AMQP)、Kafka、MQTT、JSON、Protobuf、XML——每一个都配有英文名称、一张图、一张卡片(是什么 → 怎么运作 → 例子 → 代价),以及指向其他概念的链接。结尾附一页速查表。

- **Author:** Fedor Chishchin
- **Level:** intro
- **Reading time:** ~30 分钟阅读
- **Published:** 2026-06-02
- **Source:** https://fedorthinks.com/zh/learn/data-protocols-express

---

## 1. 什么是协议

协议是两个程序用来对话的、约定好的一套规则——一条消息长什么样,以及谁可以在什么时候说什么。把规则定对,千差万别的系统就能彼此听懂。

### Protocol——一门语言加一套礼仪

> 两位没有共同母语的外交官仍能谈判——因为他们事先就一门语言、以及发言的顺序达成了一致。任意一边一破,剩下的就只是噪声。

**协议**定下两样东西:消息的**格式**(好让两边都能读懂它),以及**编排**(谁先开口,回复何时到来,怎么结束)。HTTP、gRPC、WebSocket——每一个不过是关于这两件事的一份不同约定。同样的活儿,不同的举止。

#### Protocol (约定)

- **是什么:** 两个程序之间的合同:消息的形状 + 对话的顺序。
- **例子:** HTTP:客户端发送 `GET /users/42`,服务器用 `200 OK` 加 JSON 作答。没有人抢话。
- **坑:** 如果双方对合同的理解不同,你得到的是噪声,而不是一个错误。
- **关联:** 这门课里的其他每个概念,都是一种具体的协议或格式。

### Protocol stack——协议是分层的

> 一封信:邮政系统负责搬运信封,根本不在乎里头是什么;纸上的语言则是你和读信人之间的事。两份独立的约定,叠在一起。

底下是一个**传输**协议——TCP 或 UDP——它只负责在机器之间搬运字节。上面是一个**应用**协议——HTTP、gRPC、MQTT——它赋予那些字节以含义。两者之间通常还有 **TLS**,把信封封起来。你通常工作在最上层——但当事情变慢、变得不稳定时,底层就会渗上来。

_Diagram: 协议栈:底下是字节,上面是含义。每一层都依赖它下面的那一层。_

#### Protocol stack (分层)

- **是什么:** 协议一层叠一层:传输层搬运字节,应用层赋予它们含义。
- **例子:** HTTP 跑在 TLS 上,TLS 跑在 TCP 上,TCP 跑在 IP 上。
- **坑:** 变慢和掉线往往来自下层——盯着 HTTP 看是修不好的。
- **关联:** 底下是 **TCP / UDP**,上面是 **HTTP、gRPC、MQTT**,中间是 **TLS**。

### 每个协议都要回答三个问题

> 一场对话需要先定下三件事:谁先开口,用什么语言,是轮流说还是双方同时说。

**谁发起**——是客户端来问,还是服务器也能主动推送?**数据长什么样**——JSON 文本、紧凑的二进制,还是连续的流?**时机**——一锤子买卖,还是一条长期保持的线路、双方随时都能开口?协议之间几乎所有的差别,都是对这三个问题的不同回答。

### 地图:五组共 21 个概念

> 一张地铁图:先看清整条线路,再一站一站地坐下去。

这门课里的每个概念都属于五组之一:**地基**(一切所依托的东西)、**请求–响应**(问一句,得一个答案)、**实时**(服务器先开口)、**消息**(双方之间有个中间人)和**格式**(里面数据的形状)。

_Diagram: 五组概念——第 2–6 章依次讲它们。_

### 怎么读每个概念

> 一本护照:每个人的栏目都一样,所以很容易比较。

下面的每个协议都按同一套方式讲:它的**英文名称**(你查文档时要搜的词)、一幅现实生活里的画面、一张**图**,以及一张带字段的**卡片**:_是什么_、_怎么运作_、_例子_、_代价_(你要付出什么)、*何时使用*和*关联*。最重要的一条规则:**没有最好的协议,只有合适的协议。**你不会用对讲机发婚礼请柬,也不会靠寄信来协调一场救援。

> **选协议之前:** “谁需要说话、多久说一次、要多快——对面又是谁?”

> 协议是一份约定。按对话的形状去选它,而不是按什么流行。

## 2. 地基

你构建的几乎每样东西底下,都坐着几个你很少直接碰、却始终依赖的协议。了解它们——它们决定了上层到底能做什么。

### TCP——保证送达的可靠线路

> 一通电话,对方每说完一句都回你一声“收到”——慢一点,但什么都不会丢、不会乱。

**TCP(Transmission Control Protocol)**先建立一条连接——也就是**握手**(handshake)。然后它保证每个字节都按顺序到达:接收方确认自己收到了什么,丢掉的就重发。它是网页、电子邮件和大多数 API 底下干活的主力。

_Diagram: TCP:SYN → SYN-ACK → ACK 握手,然后是带确认的数据。丢了?再发一遍。_

#### TCP (传输控制协议 (Transmission Control Protocol))

- **是什么:** 一种带保证的传输:全部送达,按顺序,不丢。
- **怎么运作:** 握手、确认(ACK)、重发丢失的部分。
- **例子:** HTTP/1.1 和 HTTP/2、电子邮件、数据库、大多数 API 的底层。
- **代价:** 建立连接和等待重发会带来一点延迟。**通常是笔很划算的交易。**
- **何时使用:** 每个字节都重要的时候——几乎总是如此。
- **关联:** 反面是 **UDP**。上面跑着 **HTTP**、**TLS**、消息中间件。

### UDP——发出去就不管,又快又会丢

> 隔着一间嘈杂的屋子喊话——快,但漏听了一个词你也不会停下来重复,继续往下说就是了。

**UDP(User Datagram Protocol)**发送数据包,没有握手,也不保证送达和顺序。这听起来很糟,直到你想要的是速度而不是完美:**视频通话、实时游戏、DNS 查询**。在这些场景里,丢一帧画面,好过为了等重发而卡顿。

_Diagram: UDP:第 3 个包丢了——没人会重发。作为交换,什么都不用等。_

#### UDP (用户数据报协议 (User Datagram Protocol))

- **是什么:** 一种没有任何保证的传输:发出去就不管。
- **怎么运作:** 没有连接、没有确认、没有重发、没有顺序。
- **例子:** 视频通话:旧的一帧没有用——不如直接显示下一帧。
- **代价:** 丢包和乱序要你自己处理(或者容忍)。
- **何时使用:** 新鲜比完整更重要的时候:语音、视频、游戏、DNS。
- **关联:** **QUIC** 建立在它之上,**HTTP/3** 又建立在 QUIC 之上。

### HTTP——网络世界的请求–响应语言

> 商店柜台:你要一样东西,得到一个回答,你一转身店员就忘了你。

**HTTP(HyperText Transfer Protocol)**跑在 TCP 之上,定义了大家熟悉的请求/响应:**方法** `GET`、`POST`、`PUT`、`DELETE` 和**状态码** `200`、`404`、`500`。它是**无状态**的——每个请求都独立成立——而这恰恰让网络得以扩展到数十亿次调用。

_Diagram: HTTP:一个请求,一个响应。方法说明要做什么;状态码说明结果如何。_

#### HTTP (超文本传输协议 (HyperText Transfer Protocol))

- **是什么:** “问一句,得一个答案”的应用层协议。
- **怎么运作:** 请求 = 方法 + 路径 + 请求头 + 正文。响应 = 状态码 + 响应头 + 正文。
- **例子:** `GET /users/42` → `200 OK`,正文是 JSON 格式的用户。
- **代价:** 服务器不能先开口——只能回答。
- **何时使用:** 你会碰到的几乎每个 API 都说它。
- **关联:** **REST**、**GraphQL**、**SSE**、**Webhook** 的基础;**WebSocket** 从它起步。

### TLS 与 HTTPS——同样的东西,但封好了

> 同一封信,这回装进了防拆封的信封,外面还有寄信人经过验证的签名。

**TLS(Transport Layer Security)**给 HTTP(以及其他协议)包上**加密**和**身份验证**:中间的任何人都读不到、也伪造不了流量,而**证书**证明服务器确实是它所声称的那一个。它就是 **HTTPS** 里的那个“S”——而且早已不再是可选项:浏览器、应用商店和 API 都默认它存在。

_Diagram: HTTPS = 装在 TLS 里的 HTTP。同一个 Wi-Fi 下的攻击者只能看到加密后的字节。_

#### TLS / HTTPS (传输层安全 (Transport Layer Security))

- **是什么:** 叠在传输层之上的加密与身份验证层。
- **怎么运作:** 由证书为服务器担保;之后所有流量都被加密。
- **例子:** 浏览器地址栏里的小挂锁就是 TLS。
- **代价:** 证书需要签发和续期(如今几乎免费)。
- **何时使用:** **只要数据离开了你的机器,就该放在 TLS 里。**
- **关联:** 包裹着 **HTTP**、**gRPC**、**WebSocket**、消息中间件。

### HTTP/2 与 HTTP/3——同一根管道,变宽了

> 一条单车道的路变成了多车道的高速公路——然后又被重建,让一辆抛锚的车不再堵住所有车道。

**HTTP/1.1** 在每条连接上一次只处理一个请求。**HTTP/2** 在一条连接上同时跑很多个——这就是**多路复用**(multiplexing)。但它建立在 TCP 之上,所以丢一个包就会卡住所有的流——这就是**队头阻塞**(head-of-line blocking)。**HTTP/3** 改用 **QUIC**(跑在 UDP 上):丢包只会卡住它自己那条流,建立连接大约只要一次往返,手机还能在 Wi-Fi 和蜂窝网络之间切换而不断线。

_Diagram: HTTP/1.1——一次一个。HTTP/2——并行,但一次丢包会卡住全部。HTTP/3——丢包只影响它自己那条流。_

#### HTTP/2 · HTTP/3 (多路复用 (multiplexing)、QUIC)

- **是什么:** HTTP 的新版本:同样的请求,底下是一条更快的路。
- **怎么运作:** HTTP/2——一条 TCP 连接里有多条流。HTTP/3——跑在 UDP 之上的 QUIC 里,各条流彼此独立。
- **例子:** 一个有上百张图片的页面,通过一条连接并行加载它们。
- **代价:** 对你几乎没有——在服务器或 CDN 那里打开开关就行。
- **何时使用:** 默认就用,只要环境支持。你的应用代码不用改。
- **关联:** **gRPC** 跑在 HTTP/2 上;HTTP/3 跑在 **UDP** 上。

> **地基的问题:** “我需要每个字节都有保证(TCP)——还是要速度,偶尔丢一点也无所谓(UDP)?”

> 你构建在栈的顶层,但决定能做什么的是底层。而加密不是以后再补上的一层。

## 3. 请求与响应

应用与服务器之间、服务与服务之间的大部分对话,都是“问一个问题,得一个答案”。三种风格占主导,它们以不同的方式用简单换取能力。外加一位你仍会碰到的老前辈。

### REST——建立在朴素 HTTP 动词之上的资源

> 一座井井有条的图书馆,每本书都有固定的位置,而你在哪儿都用同样的四个动作:看、加、改、删。

**REST(Representational State Transfer)**把一切建模成位于 `/users/42` 这类 URL 上的**资源**,用 HTTP 动词来操作,通常返回 JSON。它简单、可缓存、能在浏览器里调试,到处都有人懂——**是公开 API 的默认选择。**

_Diagram: REST:一个资源,一个地址,四个动词包打天下。弱点——一个页面要调用很多次。_

#### REST (表述性状态转移 (Representational State Transfer))

- **是什么:** 一种 API 风格:URL 上的资源 + HTTP 动词 + 通常是 JSON。
- **例子:** `GET /users/42` 读取它,`DELETE /users/42` 删除它。
- **代价:** 你常常拿到的数据比需要的多(**over-fetching**,过度获取),或者为填满一个页面要调用好几次(**under-fetching**,获取不足)。
- **何时使用:** 面向很多客户端的公开 API。几乎总是起点。
- **关联:** 跑在 **HTTP** 上,说 **JSON**。**GraphQL** 补上了它的弱点。

### GraphQL——只要你想要的那些字段

> 一家自助餐厅:不是固定套餐,而是你递给后厨一份精确的清单,端回来的恰好就是这些——不多也不少。

**GraphQL** 提供一个端点,由**客户端写一条查询**,一次往返就取回它所需的确切字段——终结了 REST 的过度获取和获取不足。当很多不同的客户端(网页、移动端、合作伙伴)需要一张复杂数据图谱的不同切片时,它非常出色。

_Diagram: GraphQL:客户端列出字段,一个端点从多个来源把它们汇集起来,返回的恰好就是这些。_

#### GraphQL (API 的查询语言)

- **是什么:** 一个端点;由客户端描述答案的形状。
- **例子:** 移动端的一个页面需要名字和订单总额——它就只要这两样。
- **代价:** 缓存更难做,而且这份灵活性把真正的复杂度压到了你的服务器上。
- **何时使用:** 客户端五花八门,数据图谱又复杂、关系紧密。
- **关联:** 对 **REST** 弱点的一个回答;通常跑在 **HTTP** 上,配 **JSON**。

### gRPC——像调用本地函数一样调用远程函数

> 同一栋楼里两个房间之间的对讲机——简短、快速、私密,因为双方早已共用同一套简语。

**gRPC** 是一个 **RPC(Remote Procedure Call,远程过程调用)**框架——通过网络调用一个函数。它在 HTTP/2 上使用 **Protocol Buffers**(紧凑的二进制),让一个服务能直接调用另一个服务的方法,自带**强类型合同**和流式传输。又快又严格——**非常适合内部的服务间流量。**

_Diagram: gRPC:共享的 .proto 合同生成代码。服务 A 调用 B 的方法,就像调用本地方法一样。_

#### gRPC (Google 的远程过程调用 (Remote Procedure Call))

- **是什么:** 按一份严格的合同,通过网络调用另一个服务的函数。
- **例子:** 订单服务对仓库服务调用 `inventory.Reserve(item, 2)`。
- **代价:** 原生不适合浏览器;二进制负载人眼读不懂,所以调试要靠工具,而不是靠眼睛。
- **何时使用:** 你自己的服务之间频繁、快速地互相通信。
- **关联:** 跑在 **HTTP/2** 上,说 **Protobuf**。

### SOAP——老前辈

> 一份一式三份、盖了章、经过公证的正式合同——笨重,但毫不含糊。

在 REST 之前,**SOAP(Simple Object Access Protocol)**把调用包在严格的 XML **信封**里,并配有正式的 **WSDL** 合同。它啰嗦、老派,但它的严谨至今仍活在银行、支付和企业集成之中。

_Diagram: SOAP:每次调用都是一个带头部和正文的 XML 信封,而 WSDL 描述了你能调用什么。_

#### SOAP (简单对象访问协议 (Simple Object Access Protocol))

- **是什么:** 一种带正式合同的严格 XML 调用协议。
- **例子:** 银行集成:在 `soap:Envelope` 里装一个 `GetBalance`。
- **代价:** 啰嗦、笨重,现代工具很少。
- **何时使用:** 你大概不会新建一个——但很可能得和一个打交道。
- **关联:** 说 **XML**;在新系统里它的位置被 **REST** 和 **gRPC** 取代了。

> **API 的问题:** “这是很多客户端都要用的公开 API(REST),是一个‘贪心’的客户端自己挑字段(GraphQL),还是我自己的两个服务之间快速通信(gRPC)?”

> REST,为了让所有人都看得懂。GraphQL,为了让客户端自己选。gRPC,为了在你自己的服务之间跑得快。

## 4. 实时与推送

朴素的请求/响应有个缺口:服务器不能先开口。当客户端需要在事情发生的当下就听到消息时,你就会用到下面这些。

### Polling——不停地问“有新消息吗?”

> 一个孩子在长途自驾的路上,每隔两分钟就问一句“到了没?”。简单——而且多半是白费口舌。

**Polling(轮询)**是最粗糙的办法:客户端按定时器反复发请求。**Long-polling(长轮询)**对它做了改进:服务器把每个请求一直挂着,直到真有话要说,然后客户端立刻再问一次。它到处都能用,不需要任何特殊协议。

_Diagram: 上图,轮询:为了一个“有”,先得三次空空的“没有”。下图,长轮询:服务器一直沉默,直到有消息。_

#### Polling / Long-polling (按定时器去问)

- **是什么:** 客户端一遍又一遍地向服务器询问有没有新消息。
- **例子:** 订单状态页每隔 10 秒问一次服务器。
- **代价:** 和真正的流相比,既浪费又滞后。
- **何时使用:** **做备用方案不错,做默认方案很差。**
- **关联:** 真正的替代品是 **SSE** 和 **WebSocket**。

### SSE——来自服务器的单向流

> 一档电台广播——电台持续向你播送;你只管听,不会在同一个频道上回话。

**SSE(Server-Sent Events)**让一个 HTTP 响应一直开着,由**服务器向客户端推送一串更新**,自带自动重连,开销几乎为零。非常适合单向的数据流:实时比分、通知、跳动的仪表盘——**LLM 把 token 流式推送到聊天界面**用的也是它。

_Diagram: SSE:服务器沿着一个打开的 HTTP 响应不断发送事件。客户端不能在这条线路上回话。_

#### SSE (服务器发送事件 (Server-Sent Events))

- **是什么:** 在普通 HTTP 上,从服务器到浏览器的单向事件流。
- **例子:** 聊天机器人的回答在模型写作的同时一个词一个词地冒出来。
- **代价:** 只有一个方向;客户端的回复要作为单独的请求发出。
- **何时使用:** 如果客户端不需要在同一条线路上回复,**SSE 是最简单的实时工具**。
- **关联:** 跑在 **HTTP** 上;它的双向兄弟是 **WebSocket**。

### WebSocket——双向都开着的线路

> 一条没挂断的电话线——任何一方一有话就能立刻说,不用重拨。

**WebSocket** 把一条 HTTP 连接**升级**(upgrade)成一条**持久的全双工通道**,客户端和服务器可以自由地互发消息,延迟最低,每条消息的开销极小。当双方都在不停说话时,它就是对的工具:**聊天、多人游戏、协同编辑、实时交易。**

_Diagram: WebSocket:一个带 Upgrade 的 HTTP 请求,一个 101 响应——从此双方想写就写。_

#### WebSocket (全双工通道 (full-duplex))

- **是什么:** 一条持久的通道,双方随时都能开口。
- **例子:** 聊天:“正在输入……”、新消息和队友的光标,双向飞来飞去。
- **代价:** 运维起来比 SSE 麻烦:长连接、重连、负载均衡。
- **何时使用:** 当你**确实**需要双向的时候。
- **关联:** 从 **HTTP** 起步;单向的替代品是 **SSE**。

### Webhook——别打给我们,我们打给你

> 把你的电话号码留给店家,订单到了他们打给你——而不是你每个钟头就跑回去看一趟。

**Webhook** 把方向反了过来:**你登记一个 URL,事件发生时,对方服务就向它发一个 HTTP 请求。**这是服务之间互相通知的标准做法,没有谁需要坐在轮询循环里干等。

_Diagram: Webhook:你只需交出一次 URL——此后每当支付成功,Stripe 就会向你 POST。_

#### Webhook (HTTP 回调 (HTTP callback))

- **是什么:** 对方服务那边一有事情发生,就给你发一个 HTTP 请求。
- **例子:** 支付一成功,Stripe 就向你的端点 `POST`;每次 push,GitHub 都会通知你的服务器。
- **代价:** 你的端点必须能从外部访问,并且要扛得住重试和失败。
- **何时使用:** 一个服务告诉另一个服务:“有事件发生了。”
- **关联:** 就是普通的 **HTTP**,只是方向反过来;取代了 **Polling**。

> **实时的问题:** “服务器只需要推给客户端(SSE),还是双方都要说话(WebSocket),还是一个服务通知另一个服务(webhook)?”

> 如果服务器必须先开口,就别再轮询了。单向流用 SSE,双向用 WebSocket。

## 5. 消息与异步

有时候 A 根本不该直接调用 B。在两者之间放一个 broker,发送方就可以发完即走,而接收方按自己的节奏干活。

### Message broker——替你保管消息的中间人

> 夹在寄信人和收信人之间的邮局——你把信放下就走;对方准备好了再来取。双方不必同时在场。

不再是直接调用,而是由**生产者**把消息交给一个 **message broker**,由它投递给一个或多个**消费者**。这让两者在**时间上**解耦(接收方可以慢,也可以短暂宕机),也在**认知上**解耦(发送方不必知道谁在听)。它是那些有韧性、负载起伏大的**异步**系统的脊梁。

_Diagram: 中间有个 broker:发送方交出去就走,接收方按自己的节奏来取。_

#### Message broker (中间人)

- **是什么:** 一个接收、保存并分发消息的中间人。
- **例子:** Web 服务器放下一条“订单已创建”;仓库和邮件服务各自来取。
- **代价:** 多一个要运维的系统;不再有“马上回答我”这回事了。
- **何时使用:** 慢活儿、流量尖峰、很多接收方。
- **关联:** 它的几种形态是**队列(AMQP)**、**日志(Kafka)**、**发布/订阅(MQTT)**。

### Message queue(AMQP / RabbitMQ)——一份活,一个 worker

> 饭馆里订单暴增——小票在挂架上堆起来,哪位厨师有空就取下一张。

**Message queue(消息队列)**保存任务,直到某个 **worker** 取走一个、处理它,并**确认(ack)**。像 **AMQP**(**RabbitMQ** 说的就是它)这样的协议又增加了智能路由、重试和投递保证。

_Diagram: 队列:Web 应用立刻回复用户,哪个 worker 有空,就接手下一份活。_

#### Message queue (AMQP、RabbitMQ)

- **是什么:** 任务排着队等;每个任务恰好被一个 worker 取走。
- **例子:** 把“发送欢迎邮件”或“生成发票”丢进队列,这样 Web 请求立刻返回,而慢的那部分由 worker 去处理。
- **代价:** 处理完的任务就没了——你没法回头重读历史。
- **何时使用:** 不该拖慢用户的后台工作。
- **关联:** **broker** 的一种形态;如果有很多读者、还需要历史——用 **Kafka**。

### Kafka——很多人都能回放的持久日志

> 一座报纸档案馆——每一期都按顺序保存着,任意数量的读者都可以从任何位置开始,按自己的速度往后读。

**Kafka** 与其说是队列,不如说是一份**持久、有序的事件日志**:许多消费者各自独立地读它,读完之后它依然留在那里。每个读者都记着自己的位置——它的 **offset**。它为极高的吞吐量而生。

_Diagram: Kafka:事件被追加到日志的末尾,每个消费者从自己的位置开始读。_

#### Kafka (事件日志、事件流 (event streaming))

- **是什么:** 一本事件日志,被保留下来,由许多人各自独立地读取。
- **例子:** 同一条“订单已下”的事件流,同时喂给数据分析、搜索索引和邮件。
- **代价:** 系统很重;每天只有几百条消息的话就是杀鸡用牛刀。
- **何时使用:** 事件量巨大,而且有很多读者需要历史记录。
- **关联:** **队列:消息一经处理就没了。Kafka:历史会留下来。**

### MQTT——为不可靠网络而生的迷你发布/订阅

> 一支送货车队用的对讲机频道——短促的通话、低功耗,有一辆暂时开进隧道也没关系。

**MQTT** 是一种超轻量的**发布/订阅(pub/sub)**协议,为 **IoT** 而造:设备把消息发布到**主题**(topic)上,由 broker 把它们交给订阅者。消息极小,电量和带宽的消耗极低,还能容忍不稳定的链路。

_Diagram: MQTT:传感器向主题发布,broker 把每条消息交给所有订阅者。_

#### MQTT (面向 IoT 的发布/订阅 (pub/sub))

- **是什么:** 一种轻量的发布/订阅协议,给弱设备、弱网络用。
- **例子:** 成千上万的传感器、智能家居设备或车辆,通过蜂窝网络上报遥测数据。
- **代价:** 需要一个 broker;用在普通的 Web 上是错误的工具。
- **何时使用:** 当“客户端”是弱网络里的一颗廉价芯片时,**MQTT 适用于 HTTP 不适用的地方**。
- **关联:** **broker** 的一种形态;它的传输层是 **TCP**。

> **消息的问题:** “接收方可以晚点、按自己的节奏处理吗——而这个事件需要一个处理者(队列),还是随着时间推移需要很多读者(日志)?”

> 别让调用方为慢活儿干等。把它交给 broker,然后继续往前走。

## 6. 线路上的数据

协议是信封;里面的内容仍然需要一个双方都认可的形状。这个选择是在可读性与速度之间悄悄做的一笔权衡。

### JSON——可读、通用、默认之选

> 一张谁都能一眼看懂的手写便条——稍微长了点,但不需要解码器。

**JSON(JavaScript Object Notation)**是文本,人能读,到处都支持——是 Web 和 REST API 默认的正文格式。你可以在浏览器里直接扫一眼,靠阅读来调试。下面是同一条记录的三种格式:大小的差别一目了然。

_Diagram: 同一条记录 {id: 42, name: Ann}:JSON 是 22 字节,XML 是 40,Protobuf 是 7。_

#### JSON (JavaScript 对象表示法 (JavaScript Object Notation))

- **是什么:** 一种键–值的文本格式,人和机器都能读。
- **例子:** `{"id": 42, "name": "Ann"}`
- **代价:** **啰嗦,解析也不是最快的**——通常没问题,偶尔在大规模下会成为瓶颈。
- **何时使用:** Web 和公开 API 默认就用它。
- **关联:** **REST** 和 **GraphQL** 的语言;二进制的替代品是 **Protobuf**。

### Protocol Buffers 及其同类——小、快、二进制

> 一枚条形码——你看不懂,但一扫就识别,而且排得很紧凑。要解码它,你得有那份模式(schema)。

像 **Protocol Buffers(Protobuf)**——gRPC 用的就是它——以及 **MessagePack** 和 **Avro** 这样的二进制格式,依据一份共享的**模式**(schema)把数据**序列化**成紧凑、快速的负载。好处是体积和速度;代价是**肉眼读不了**,而且双方必须共享这份模式。

_Diagram: Protobuf:模式规定字段 1 是 id,字段 2 是 name。没有它,这 7 个字节就只是一串数字。_

#### Protobuf (Protocol Buffers、MessagePack、Avro)

- **是什么:** 一种紧凑的二进制格式,事先由一份模式来定义。
- **例子:** `message User { int32 id = 1; string name = 2; }` → 线路上只有 7 个字节。
- **代价:** 肉眼读不了;双方必须保持模式同步。
- **何时使用:** 大流量、内部的、对性能要求苛刻的通信。
- **关联:** **gRPC** 的语言;可读的替代品是 **JSON**。

### XML——啰嗦、正式、仍然健在

> 一份法律文件——标签和结构堆得满满当当,精确无比,但写张便条时谁都不会第一个想到它。

**XML(eXtensible Markup Language)**比 JSON 更早出现,带有丰富的**模式与校验**,代价是体积臃肿——每个值都夹在一个起始标签和一个结束标签之间。你会在 SOAP 服务、文档格式和配置文件里碰到它。

#### XML (可扩展标记语言 (eXtensible Markup Language))

- **是什么:** 一种基于标签、带严格模式的文本格式。
- **例子:** ` <user> <id>42</id> <name>Ann</name> </user> `
- **代价:** 臃肿:同一条记录几乎是 JSON 的两倍长。
- **何时使用:** 新东西很少用——但它**仍然撑着不少老系统**。
- **关联:** **SOAP** 的语言;在 Web 上被 **JSON** 取代了。

> **格式的问题:** “我更看重用肉眼读它、调试它(文本)——还是原始的体积和速度(二进制)?”

> 文本是为了让人看懂,二进制是为了快。大多数 API 默认用可读的格式,是对的。

## 7. 选得好

你不是给一个系统选一个协议——而是给每一场对话选对的那个。真实的应用同时会跑好几种。

### 让协议匹配这场对话

> 一个工具箱,里面有锤子、螺丝刀、扳手。你不会争论哪个最好——你只看眼前的活儿。

按对话的形状来决定。问题从来不是“选哪个协议”,而是“这一条线路,选哪个”。

_Diagram: 左边是对话的形状;右边是为它调校好的协议。_

### 从简单开始;疼了再去碰那些花哨的

> 你不会为了把一袋面粉运过院子,专门去铺一条铁路。

大多数产品靠 **HTTPS 上的 REST + JSON,再加一个跑后台任务的队列**,就能走得很远。gRPC、GraphQL、Kafka 和 WebSocket 是针对具体痛点的答案——服务间延迟、“贪心”的客户端、巨大的事件量、实时交互。等你真切感到那种痛再采用它们,而不是因为听起来时髦。**最简单、又能匹配这场对话的协议,通常会赢。**

### 这一切如何连在一起

> 一块千层蛋糕:每一层都压在下面那层上,而从上往下你只看得到糖霜。

格式装在 API 风格里面,风格跑在 HTTP 或消息中间件之上,这一切都包在 TLS 里,最底下是 TCP 或 UDP。一个真实的应用,轻易就会同时用到这幅图的一半。

_Diagram: 如何连在一起:格式 → API 风格 → 协议 → TLS → 传输层。gRPC 依托 HTTP/2 和 Protobuf,HTTP/3 依托 UDP。_

#### 选协议之前

- **谁来发起对话**——客户端,还是服务器?
- 消息**多久一条、有多大**?
- **一对一,还是随时间推移一对多**?
- **需要可读吗**,还是越小越快越好?
- **客户端是谁**——浏览器、移动应用、我自己的另一个服务,还是一个小设备?
- 在去碰更花哨的东西之前,**普通的 REST + JSON 够用吗**?

#### 用力过猛的征兆

- 你为一个必须让浏览器来调用的**公开 API 选了 gRPC**。
- 明明一个刷新按钮就够了,却用了 **WebSocket**。
- 用 **Kafka** 来搬每天几百条消息。
- 为一个每次都只取那三个字段的客户端用了 **GraphQL**。
- 对你需要经常用手去读、去调试的数据,用了**二进制格式**。

#### 选对了的迹象

- **客户端和服务器彼此一致**,不需要胶水代码在两者之间翻译。
- 协议**匹配流量**——流用流式,问题用请求/响应。
- 你可以用协议所期望的工具来**调试**它——浏览器、日志,或 proto 工具。
- **加密默认开着**,处处如此。
- 每场对话都用**最简单、能匹配它的协议**,不多不少。

## 速查表:全部 21 个概念

### 基础

- **Protocol** (约定): 两个程序之间的合同:消息的形状 + 对话的顺序。
- **Protocol stack** (分层): 分层:传输层搬运字节,应用协议赋予它们含义。

### 地基

- **TCP** (传输控制协议): 可靠、按顺序的送达:握手、确认、重发。
- **UDP** (用户数据报协议): 发出去就不管:快但会丢。视频、游戏、DNS。
- **HTTP** (超文本传输协议): 无状态的请求–响应:方法和状态码。
- **TLS / HTTPS** (传输层安全): 加密 + 服务器身份。HTTPS = 装在 TLS 里的 HTTP。
- **HTTP/2 · HTTP/3** (多路复用、QUIC): 一条连接上跑很多请求;HTTP/3 跑在 UDP 之上的 QUIC 上。

### 请求与响应

- **REST** (表述性状态转移): URL 上的资源 + HTTP 动词 + JSON。公开 API 的默认之选。
- **GraphQL** (API 的查询语言): 一个端点;客户端只要它需要的那些字段。
- **gRPC** (远程过程调用): 调用另一个服务的函数:HTTP/2 + Protobuf,严格的合同。
- **SOAP** (简单对象访问协议): 严格的 XML 信封和 WSDL。在银行和企业里继续活着。

### 实时与推送

- **Polling** (长轮询): 客户端不停地问“有新消息吗?”。一个备用方案。
- **SSE** (服务器发送事件): 通过一个 HTTP 响应,从服务器到客户端的流。LLM 的 token 流式输出。
- **WebSocket** (全双工通道): 一条打开的线路,双方随时开口。聊天、游戏。
- **Webhook** (HTTP 回调): 事件发生时,一个服务向你的 URL 发 POST。

### 消息

- **Message broker** (中间人): 让发送方和接收方在时间上、在认知上解耦。
- **Message queue** (AMQP、RabbitMQ): 任务排队等候;恰好有一个 worker 取走它。
- **Kafka** (事件日志): 持久的日志:很多读者,各有各的位置,历史会保留。
- **MQTT** (面向 IoT 的发布/订阅): 通过主题传递轻量消息,给弱设备和弱网络用。

### 格式

- **JSON** (JavaScript 对象表示法): 可读的文本,Web 的默认之选。略显啰嗦。
- **Protobuf** (Protocol Buffers): 按模式生成的紧凑二进制。快,但肉眼读不了。
- **XML** (可扩展标记语言): 标签加严格的模式。臃肿;活在老系统和 SOAP 里。

> 现代应用不是建立在某一个协议之上的。它们建立在为每场对话选对的协议之上——而且处处裹着 TLS。

速成课结束 · 7 章 · 21 个概念

接下来是深入:HTTP 和 TLS 的规范,gRPC 和 GraphQL 的文档,Ilya Grigorik 的《_High Performance Browser Networking_》。但在细节之前——先想清楚那场对话。谁说话、多久一次、多快、对谁说。协议只不过是让那场对话成为可能的约定。
