fedorthinks

速成课 · No. 05

两个程序只有在就规则达成一致时才能协作——发送什么、以什么形状发送、谁在什么时候说话。协议就是这份约定,而挑对协议,就是在选择这场对话怎么进行。

21 个概念 · 每个都有一张图 · 结尾附速查表

§ 01

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

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

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

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

ConceptProtocol· 约定
是什么
两个程序之间的合同:消息的形状 + 对话的顺序。
例子

HTTP:客户端发送 GET /users/42,服务器用 200 OK 加 JSON 作答。没有人抢话。

坑
如果双方对合同的理解不同,你得到的是噪声,而不是一个错误。
关联
这门课里的其他每个概念,都是一种具体的协议或格式。

Protocol stack——协议是分层的

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

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

ApplicationHTTP · gRPC · WebSocket · MQTTSecurityTLS — encryption + identityTransportTCP · UDP — moves bytesNetwork (IP)the letter textsealed envelopethe postal servicemeaningbytes
协议栈:底下是字节,上面是含义。每一层都依赖它下面的那一层。
ConceptProtocol stack· 分层
是什么
协议一层叠一层:传输层搬运字节,应用层赋予它们含义。
例子
HTTP 跑在 TLS 上,TLS 跑在 TCP 上,TCP 跑在 IP 上。
坑
变慢和掉线往往来自下层——盯着 HTTP 看是修不好的。
关联
底下是 TCP / UDP,上面是 HTTP、gRPC、MQTT,中间是 TLS。

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

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

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

地图:五组共 21 个概念

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

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

FoundationTCPUDPHTTPTLS / HTTPSHTTP/2 · HTTP/3Request–responseRESTGraphQLgRPCSOAPReal-time & pushPollingSSEWebSocketWebhookMessagingMessage brokerQueue · AMQPKafkaMQTTData formatsJSONProtobufXML
五组概念——第 2–6 章依次讲它们。

怎么读每个概念

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

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

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

§ 02

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

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

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

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

ClientServerSYNSYN-ACKACKhandshakedata #1ACK #1✗ data #2 lostdata #2 (resent)retry
TCP:SYN → SYN-ACK → ACK 握手,然后是带确认的数据。丢了?再发一遍。
ProtocolTCP· 传输控制协议 (Transmission Control Protocol)
是什么
一种带保证的传输:全部送达,按顺序,不丢。
怎么运作
握手、确认(ACK)、重发丢失的部分。
例子
HTTP/1.1 和 HTTP/2、电子邮件、数据库、大多数 API 的底层。
代价
建立连接和等待重发会带来一点延迟。通常是笔很划算的交易。
何时使用
每个字节都重要的时候——几乎总是如此。
关联
反面是 UDP。上面跑着 HTTP、TLS、消息中间件。

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

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

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

SenderReceivergot 1, 2, 41234lost — nobody resendsno handshake · no resend · no order guaranteegood for: video calls · live games · DNS
UDP:第 3 个包丢了——没人会重发。作为交换,什么都不用等。
ProtocolUDP· 用户数据报协议 (User Datagram Protocol)
是什么
一种没有任何保证的传输:发出去就不管。
怎么运作
没有连接、没有确认、没有重发、没有顺序。
例子
视频通话:旧的一帧没有用——不如直接显示下一帧。
代价
丢包和乱序要你自己处理(或者容忍)。
何时使用
新鲜比完整更重要的时候:语音、视频、游戏、DNS。
关联
QUIC 建立在它之上,HTTP/3 又建立在 QUIC 之上。

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

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

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

stateless: every request stands aloneClientbrowser / appServerGET /users/42200 OK · { id: 42, name: …}GETreadPOSTcreatePUTupdateDELETEremovestatus codes: 200 ok · 404 not found · 500 server error
HTTP:一个请求,一个响应。方法说明要做什么;状态码说明结果如何。
ProtocolHTTP· 超文本传输协议 (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 都默认它存在。

HTTPS = HTTP inside TLSCertificateproves server identityClientServera8 f3 9c 1e 07 b2 … (encrypted)Attackeron public Wi-Fi✗ can't read or forge it
HTTPS = 装在 TLS 里的 HTTP。同一个 Wi-Fi 下的攻击者只能看到加密后的字节。
ProtocolTLS / 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 和蜂窝网络之间切换而不断线。

HTTP/1.1over TCPreq 1req 2req 3→→one request at a time per connectionHTTP/2over TCP✗many streams, one connection · one lost packet stalls them allHTTP/3QUIC over UDP✗a loss stalls only its own stream · ~1 round trip to connect
HTTP/1.1——一次一个。HTTP/2——并行,但一次丢包会卡住全部。HTTP/3——丢包只影响它自己那条流。
ProtocolHTTP/2 · HTTP/3· 多路复用 (multiplexing)、QUIC
是什么
HTTP 的新版本:同样的请求,底下是一条更快的路。
怎么运作

HTTP/2——一条 TCP 连接里有多条流。HTTP/3——跑在 UDP 之上的 QUIC 里,各条流彼此独立。

例子
一个有上百张图片的页面,通过一条连接并行加载它们。
代价
对你几乎没有——在服务器或 CDN 那里打开开关就行。
何时使用
默认就用,只要环境支持。你的应用代码不用改。
关联
gRPC 跑在 HTTP/2 上;HTTP/3 跑在 UDP 上。

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

§ 03

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

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

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

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

ClientGET /users/42POST /usersPUT /users/42DELETE /users/42users/42a resourceJSON back · cacheable · easy to debug in a browserweak spot: one screen can need several calls
REST:一个资源,一个地址,四个动词包打天下。弱点——一个页面要调用很多次。
API styleREST· 表述性状态转移 (Representational State Transfer)
是什么
一种 API 风格:URL 上的资源 + HTTP 动词 + 通常是 JSON。
例子
GET /users/42 读取它,DELETE /users/42 删除它。
代价

你常常拿到的数据比需要的多(over-fetching,过度获取),或者为填满一个页面要调用好几次(under-fetching,获取不足)。

何时使用
面向很多客户端的公开 API。几乎总是起点。
关联
跑在 HTTP 上,说 JSON。GraphQL 补上了它的弱点。

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

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

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

client queryquery { user(id: 42) { name orders { total } }}/graphqlone endpointUsersOrdersReviewsnot asked → not fetched{ name, orders: [{ total }] }exactly the fields asked — one round trip
GraphQL:客户端列出字段,一个端点从多个来源把它们汇集起来,返回的恰好就是这些。
API styleGraphQL· API 的查询语言
是什么
一个端点;由客户端描述答案的形状。
例子
移动端的一个页面需要名字和订单总额——它就只要这两样。
代价
缓存更难做,而且这份灵活性把真正的复杂度压到了你的服务器上。
何时使用
客户端五花八门,数据图谱又复杂、关系紧密。
关联
对 REST 弱点的一个回答;通常跑在 HTTP 上,配 JSON。

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

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

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

orders.protoshared typed contractcodegencodegenService Acalls orders.Get(42)HTTP/2 + ProtobufService Bimplements Get()typed response (binary)
gRPC:共享的 .proto 合同生成代码。服务 A 调用 B 的方法,就像调用本地方法一样。
ProtocolgRPC· Google 的远程过程调用 (Remote Procedure Call)
是什么
按一份严格的合同,通过网络调用另一个服务的函数。
例子
订单服务对仓库服务调用 inventory.Reserve(item, 2)。
代价
原生不适合浏览器;二进制负载人眼读不懂,所以调试要靠工具,而不是靠眼睛。
何时使用
你自己的服务之间频繁、快速地互相通信。
关联
跑在 HTTP/2 上,说 Protobuf。

SOAP——老前辈

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

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

<soap:Envelope>soap:Headerauth, transaction idsoap:Body<GetBalance>account 123WSDLformal contractstrict XMLeverywhere
SOAP:每次调用都是一个带头部和正文的 XML 信封,而 WSDL 描述了你能调用什么。
ProtocolSOAP· 简单对象访问协议 (Simple Object Access Protocol)
是什么
一种带正式合同的严格 XML 调用协议。
例子
银行集成:在 soap:Envelope 里装一个 GetBalance。
代价
啰嗦、笨重,现代工具很少。
何时使用
你大概不会新建一个——但很可能得和一个打交道。
关联
说 XML;在新系统里它的位置被 REST 和 gRPC 取代了。

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

§ 04

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

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

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

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

Polling — ask on a timerclientservernew?nonew?nonew?nonew?yes!Long-polling — server holds the requestclientservernew?held open until there is newsnews!ask again at once
上图,轮询:为了一个“有”,先得三次空空的“没有”。下图,长轮询:服务器一直沉默,直到有消息。
TechniquePolling / Long-polling· 按定时器去问
是什么
客户端一遍又一遍地向服务器询问有没有新消息。
例子
订单状态页每隔 10 秒问一次服务器。
代价
和真正的流相比,既浪费又滞后。
何时使用
做备用方案不错,做默认方案很差。
关联
真正的替代品是 SSE 和 WebSocket。

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

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

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

Servere.g. an LLM APIBrowserEventSourceone long-lived HTTP responsedata: Heldata: lodata: !✗ the client can't reply on this linedrops? the browser reconnects by itself
SSE:服务器沿着一个打开的 HTTP 响应不断发送事件。客户端不能在这条线路上回话。
ProtocolSSE· 服务器发送事件 (Server-Sent Events)
是什么
在普通 HTTP 上,从服务器到浏览器的单向事件流。
例子
聊天机器人的回答在模型写作的同时一个词一个词地冒出来。
代价
只有一个方向;客户端的回复要作为单独的请求发出。
何时使用
如果客户端不需要在同一条线路上回复,SSE 是最简单的实时工具。
关联
跑在 HTTP 上;它的双向兄弟是 WebSocket。

WebSocket——双向都开着的线路

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

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

ClientServerGET /chat · Upgrade: websocket101 Switching Protocolsone open full-duplex channelchat messagetyping…new messagecursor moved
WebSocket:一个带 Upgrade 的 HTTP 请求,一个 101 响应——从此双方想写就写。
ProtocolWebSocket· 全双工通道 (full-duplex)
是什么
一条持久的通道,双方随时都能开口。
例子
聊天:“正在输入……”、新消息和队友的光标,双向飞来飞去。
代价
运维起来比 SSE 麻烦:长连接、重连、负载均衡。
何时使用
当你确实需要双向的时候。
关联
从 HTTP 起步;单向的替代品是 SSE。

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

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

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

Your serverPOST /webhooks/stripeStripepayment service1. register my URL (once)3. HTTP POST · event JSON4. 200 OKpayment succeeded2.no polling loop
Webhook:你只需交出一次 URL——此后每当支付成功,Stripe 就会向你 POST。
PatternWebhook· HTTP 回调 (HTTP callback)
是什么
对方服务那边一有事情发生,就给你发一个 HTTP 请求。
例子

支付一成功,Stripe 就向你的端点 POST;每次 push,GitHub 都会通知你的服务器。

代价
你的端点必须能从外部访问,并且要扛得住重试和失败。
何时使用
一个服务告诉另一个服务:“有事件发生了。”
关联
就是普通的 HTTP,只是方向反过来;取代了 Polling。

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

§ 05

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

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

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

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

ProducersenderBrokerholds messagesConsumer AConsumer BConsumer Csend & godecoupled in time (receiver may be slow or down) and in knowledge
中间有个 broker:发送方交出去就走,接收方按自己的节奏来取。
ConceptMessage broker· 中间人
是什么
一个接收、保存并分发消息的中间人。
例子
Web 服务器放下一条“订单已创建”;仓库和邮件服务各自来取。
代价
多一个要运维的系统;不再有“马上回答我”这回事了。
何时使用
慢活儿、流量尖峰、很多接收方。
关联
它的几种形态是队列(AMQP)、日志(Kafka)、发布/订阅(MQTT)。

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

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

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

Web appreturns at oncequeue (RabbitMQ · AMQP)jobjobjobjobWorker 1take · do · ackWorker 2take · do · ackWorker 3take · do · ackeach job goes to exactly one free worker, then leaves the queue
队列:Web 应用立刻回复用户,哪个 worker 有空,就接手下一份活。
ProtocolMessage queue· AMQP、RabbitMQ
是什么
任务排着队等;每个任务恰好被一个 worker 取走。
例子

把“发送欢迎邮件”或“生成发票”丢进队列,这样 Web 请求立刻返回,而慢的那部分由 worker 去处理。

代价
处理完的任务就没了——你没法回头重读历史。
何时使用
不该拖慢用户的后台工作。
关联
broker 的一种形态;如果有很多读者、还需要历史——用 Kafka。

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

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

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

Producerorder placedappend to the endtopic “orders” — an ordered log01234567Analyticsat offset 2Searchat offset 5Emailat offset 7messages stay after reading · each reader keeps its own position
Kafka:事件被追加到日志的末尾,每个消费者从自己的位置开始读。
ToolKafka· 事件日志、事件流 (event streaming)
是什么
一本事件日志,被保留下来,由许多人各自独立地读取。
例子
同一条“订单已下”的事件流,同时喂给数据分析、搜索索引和邮件。
代价
系统很重;每天只有几百条消息的话就是杀鸡用牛刀。
何时使用
事件量巨大,而且有很多读者需要历史记录。
关联
队列:消息一经处理就没了。Kafka:历史会留下来。

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

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

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

Temp sensorkitchenDoor sensorfront doorVan trackerGPSpublishMQTT brokertopic: home/kitchen/tempPhone appDashboardsubscribedtiny messages · low battery · fine with flaky links
MQTT:传感器向主题发布,broker 把每条消息交给所有订阅者。
ProtocolMQTT· 面向 IoT 的发布/订阅 (pub/sub)
是什么
一种轻量的发布/订阅协议,给弱设备、弱网络用。
例子
成千上万的传感器、智能家居设备或车辆,通过蜂窝网络上报遥测数据。
代价
需要一个 broker;用在普通的 Web 上是错误的工具。
何时使用

当“客户端”是弱网络里的一颗廉价芯片时,MQTT 适用于 HTTP 不适用的地方。

关联
broker 的一种形态;它的传输层是 TCP。

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

§ 06

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

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

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

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

JSONtext{"id":42,"name":"Ann"}22 bytesXMLtext<user><id>42</id><name>Ann</name></user>40 bytesProtobufbinary08 2A 12 03 41 6E 6E7 bytes
同一条记录 {id: 42, name: Ann}:JSON 是 22 字节,XML 是 40,Protobuf 是 7。
FormatJSON· JavaScript 对象表示法 (JavaScript Object Notation)
是什么
一种键–值的文本格式,人和机器都能读。
例子
{"id": 42, "name": "Ann"}
代价

啰嗦,解析也不是最快的——通常没问题,偶尔在大规模下会成为瓶颈。

何时使用
Web 和公开 API 默认就用它。
关联
REST 和 GraphQL 的语言;二进制的替代品是 Protobuf。

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

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

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

message User { int32 id = 1; string name = 2;}User objectid 42 · name Ann08 2A 12 03 41 6E 6E7 bytes on the wireUser objectdecodedencodedecodeboth sides need the same schema
Protobuf:模式规定字段 1 是 id,字段 2 是 name。没有它,这 7 个字节就只是一串数字。
FormatProtobuf· Protocol Buffers、MessagePack、Avro
是什么
一种紧凑的二进制格式,事先由一份模式来定义。
例子
message User { int32 id = 1; string name = 2; } → 线路上只有 7 个字节。
代价
肉眼读不了;双方必须保持模式同步。
何时使用
大流量、内部的、对性能要求苛刻的通信。
关联
gRPC 的语言;可读的替代品是 JSON。

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

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

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

FormatXML· 可扩展标记语言 (eXtensible Markup Language)
是什么
一种基于标签、带严格模式的文本格式。
例子

`

42Ann

`

代价
臃肿:同一条记录几乎是 JSON 的两倍长。
何时使用
新东西很少用——但它仍然撑着不少老系统。
关联
SOAP 的语言;在 Web 上被 JSON 取代了。

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

§ 07

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

让协议匹配这场对话

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

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

public API, many clientsREST + JSONclient picks exact fieldsGraphQLmy service ↔ my service, fastgRPCserver pushes updatesSSEboth sides talk liveWebSocketservice tells service: event happenedWebhookslow background jobQueue · AMQPmany readers, replay historyKafkatiny devices, weak networkMQTT
左边是对话的形状;右边是为它调校好的协议。

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

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

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

这一切如何连在一起

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

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

FormatsAPI stylesProtocolsSecurityTransportJSONXMLProtobufRESTGraphQLSOAPgRPCSSEWebSocketWebhookHTTP/1.1 · HTTP/2over TCPHTTP/3QUICBrokersAMQP · Kafka · MQTTall ride on HTTPTLS — wraps it allTCPreliable, orderedUDPfast, lossy
如何连在一起:格式 → 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 的默认之选。

GraphQLAPI 的查询语言

一个端点;客户端只要它需要的那些字段。

gRPC远程过程调用

调用另一个服务的函数:HTTP/2 + Protobuf,严格的合同。

SOAP简单对象访问协议

严格的 XML 信封和 WSDL。在银行和企业里继续活着。

实时与推送

Polling长轮询

客户端不停地问“有新消息吗?”。一个备用方案。

SSE服务器发送事件

通过一个 HTTP 响应,从服务器到客户端的流。LLM 的 token 流式输出。

WebSocket全双工通道

一条打开的线路,双方随时开口。聊天、游戏。

WebhookHTTP 回调

事件发生时,一个服务向你的 URL 发 POST。

消息

Message broker中间人

让发送方和接收方在时间上、在认知上解耦。

Message queueAMQP、RabbitMQ

任务排队等候;恰好有一个 worker 取走它。

Kafka事件日志

持久的日志:很多读者,各有各的位置,历史会保留。

MQTT面向 IoT 的发布/订阅

通过主题传递轻量消息,给弱设备和弱网络用。

格式

JSONJavaScript 对象表示法

可读的文本,Web 的默认之选。略显啰嗦。

ProtobufProtocol Buffers

按模式生成的紧凑二进制。快,但肉眼读不了。

XML可扩展标记语言

标签加严格的模式。臃肿;活在老系统和 SOAP 里。

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

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

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