§ 1.2 · Section

协议 · Protocol

Rules of the Conversation

两台机器能"连上"只是第一步——能"听懂对方说什么"才是关键。协议就是两台机器"说话之前先约好的规矩":先说什么、再说什么、用什么格式、出错怎么办。

生活场景
🤝 第一次跟陌生人谈生意

你跟一位外国朋友打电话——上来第一句你肯定说"Hello, do you speak Chinese?" 而不是直接开始谈生意。
对方答"Yes, I do."——好,双方达成一致:用中文。然后才进入正题:
"我这批货 100 件,每件 80 元……"
这"先打招呼、确认语言、再谈正事"的过程,就是一次"握手"(Handshake)。
协议做的事,就是把这些"寒暄、约定、确认、确认收到的细节"全部标准化,让两台机器也能这么有条不紊地聊天。

为什么"能连上"不等于"能沟通"

把两台电脑用网线插在一起,电信号确实能过去——但那只是"一串高低电平"。接收方怎么知道这串电平从哪里开始、到哪里结束、哪几位是"收件人"、哪几位才是正文?没有事先的约定,收到的一切都只是噪声。

所以网络世界里有一句话:物理连通解决"到得了",协议解决"听得懂"。前者是硬件的事,后者是约定的事——而后者才是这一节的主角。

换成大白话就是:网线通了,只等于你俩坐进了同一间会议室;能不能谈成事,还得看你们说不说同一种语言。打个比方,你去菜市场买菜,摊主张口一句方言,你一个字没听懂——你们中间可没有任何"信号障碍",声音清清楚楚地传到了耳朵里,问题纯粹在于没有一套双方都认的说话规矩。协议就是那套规矩。

Insight · 一个小实验

如果你把一段中文用 UTF-8 编码存成文件,再用 GBK 打开,屏幕上会出现"锟斤拷"这种乱码。数据一个字节都没丢,链路完全正常,唯一的问题是写的人和读的人用了不同的约定
这就是"没有共同协议"最直观的样子——网络里的协议不匹配,后果和这个乱码一模一样。

协议的三要素

不管是几十年前的电报规程,还是今天的 HTTP/3,任何一个网络协议都可以被拆成同样的三件事:格式怎么写、字段啥意思、谁先谁后。记住这三个词,以后你翻任何一份协议文档都不会迷路:

三个词听着学术,翻译成人话就是寄快递填单子那三件事:语法——单子上哪一格填收件人、哪一格填电话,格式写死了,填错格快递就派不出去;语义——"易碎品"这三个字盖上去意味着什么,全网快递员都得是同一个理解;时序——必须先揽收、再运输、最后派送,你不能让快递员先按门铃再去仓库取货。任何协议都逃不出这三件事。

01

语法 · Syntax

"格式怎么写"——比如 HTTP 请求第一行必须是"GET /index.html HTTP/1.1",顺序、空格、换行都写死了。
类比:信封必须"省-市-区-街道-门牌号"按顺序写。

02

语义 · Semantics

"每个字段是啥意思"——比如 HTTP 状态码 200 表示成功,404 表示没找到,500 表示服务器挂了。
类比:信封左上角贴"加急"贴纸——谁都知道意味着啥。

03

时序 · Timing

"谁先说话、什么时候说、出错怎么办"——比如 TCP 三次握手的顺序:你问"在吗" → 我答"在" → 你说"那我开始了"。
类比:打电话先响铃,对方接起才能说话,不能说挂就挂。

光看定义还有点飘,我们拿一段真实的 HTTP 请求报文把三要素一次全指出来。下面这几行就是你每次打开网页时,浏览器真正发出去的纯文本:

GET /index.html HTTP/1.1          <- 语法:动词 空格 路径 空格 版本,一格都不能错
Host: example.com                 <- 语法:字段名 冒号 空格 值
User-Agent: Mozilla/5.0
Accept: text/html
Accept-Encoding: gzip, br
Connection: keep-alive
                                  <- 语法:一个空行,表示"头部到此结束"
(这里之后才是正文,GET 请求通常没有正文)

语法体现在哪?——"必须是三段、用空格隔开"、"字段名和值之间必须是冒号加空格"、"头部结束必须是一个空行(准确说是 \r\n\r\n)"。这些规则死板到近乎苛刻:你少写一个空格,服务器就直接回 400 Bad Request语义体现在哪?——GET 的含义是"我只要读,别改数据",Host 的含义是"我要访问的是这台服务器上的哪个网站"(这一行让一个 IP 能托管几千个网站),Accept-Encoding: gzip 的含义是"你可以压缩了再发给我,我解得开"。时序体现在哪?——必须客户端先发请求、服务器后回响应,且在 HTTP/1.1 里一个连接上的请求必须按顺序应答,不能乱序。

服务器的回答同样严格:

HTTP/1.1 200 OK                   <- 语义:200 = 成功
Content-Type: text/html; charset=utf-8
Content-Length: 3241              <- 语义:正文正好 3241 字节,读够就停
Content-Encoding: gzip
Cache-Control: max-age=3600       <- 语义:这份内容你可以自己存 1 小时

<!DOCTYPE html><html>…(3241 字节的压缩正文)

注意那个 Content-Length——它是"语义"的绝佳例子。接收方靠这个数字知道什么时候该停止读取。如果这个数字写错了,会发生两种灾难:写大了,接收方一直傻等"还没收完"直到超时;写小了,多出来的字节被当成"下一个请求的开头"来解析,整个连接彻底错乱。说白了 Content-Length 就是快递面单上写的"内含 3 件"——收件人靠这个数字知道拆到第几件可以签收。写成"5 件",他会在门口一直等剩下两件等到快递站关门;写成"1 件",多出来的两件就被他当成隔壁邻居的包裹给拆了。协议里的每个字段都不是装饰,它们都是接收方赖以生存的判断依据。

三要素还有一个不对称的地方值得注意:语法错误容易发现,语义错误极难发现。语法写错,对方立刻报错;但如果你把"用户余额"字段的单位理解成"元"而对方发的是"分",两边的解析都成功、没有任何报错,只是所有金额都错了 100 倍。真实世界里的接口事故,绝大多数是语义误解,不是语法错误。这也是为什么好的协议文档花在解释字段含义上的篇幅,永远远超解释格式。

"语法错误容易发现、语义错误极难发现"这条,打个比方就特别扎心:语法错误好比你把快递单的收件电话填成了 10 位数——快递小哥当场就退单给你;语义错误好比你在"重量"那一栏填了个"3",你以为单位是斤,快递公司按公斤算,钱多收了一倍还没人报错。单子填得漂漂亮亮,流程一路绿灯,钱就是不对。真实的线上事故,八成是后者。

为什么一定要分层:软件工程的一次伟大胜利

在讲"有哪几层"之前,先回答一个更根本的问题:为什么不能写一个协议把所有事一次办完?假设真有这么一个"万能协议",它得同时负责:光纤里的激光怎么闪、网线上怎么避免两台机器同时说话、全球几十万个网络之间怎么选路、丢了包怎么重传、拥塞了怎么减速、HTML 用什么编码、密码怎么加密……

想象一下这个万能协议长什么样:一家餐厅里只招一种员工,这个员工既要种菜、又要杀鱼、还要炒菜、端盘子、收钱、擦桌子、修水管、跟税务局打交道。一个人干得完吗?勉强能。但你想给菜单加一道新菜,就得把这个全能员工从种菜开始重新培训一遍——这就是不分层的真实代价。

后果会是灾难性的,而且是四重灾难:

分层的核心机制其实只有一句话:每一层只跟"上一层"和"下一层"打交道,且只暴露服务、不暴露实现。TCP 只对上层承诺"你给我的字节,我保证按顺序、不重不漏地送到";它绝口不提这些字节是走光纤还是走 5G——它根本不知道,也不需要知道。这种"故意的无知"在软件工程里叫封装,是所有大型系统能被建造出来的唯一办法。

"故意的无知"这个说法很妙,打个比方就是餐厅前厅和后厨的分工:服务员只承诺"您点的菜我保证一道不漏、按顺序端上来",他绝不会告诉你这菜是煤气灶炒的还是电磁炉炒的——他自己也不知道,而且他不需要知道。哪天厨房把煤气灶全换成电磁炉,服务员一句话都不用改。TCP 对上层的承诺,跟这位服务员一字不差。

当然,分层也有代价,值得诚实地说出来。第一是开销:每层都要加自己的头部,一个 1500 字节的以太网帧里,14(以太网)+ 20(IP)+ 20(TCP)= 54 字节是纯头部开销,约 3.6%;如果传的是几十字节的小包(比如游戏操作、语音帧),头部可能比数据还长。第二是性能损失:数据在层间传递要多次内存拷贝,这也是为什么高性能场景会出现"跨层优化"这种打破分层原则的做法。第三是重复劳动:TCP 有校验和,以太网也有 CRC,IP 头也有校验和,同一件事做了三遍。

那个 3.6% 的头部开销换算一下就有感觉了:54 字节头部配 1446 字节数据,相当于寄一箱 30 斤的东西,纸箱和填充泡沫占了 1 斤多——完全划得来。但要是你只寄一支口红(比如游戏里一个 20 字节的操作指令),纸箱还是那么大,泡沫还是那么多,包装比货重两倍还多。这就是为什么实时游戏和语音特别在意小包开销,而下载大文件的人压根不在乎。

协议栈:五层协议在一次点击里同时工作

你可能听过"OSI 7 层"、"TCP/IP 4 层"——这其实就是把"一次完整对话"拆成多个不同层次的协议,每层只管一件事:

"分层"这件事,最贴切的类比是寄国际快递时的层层套箱:你把礼物装进小盒(应用层的内容),小盒放进带缓冲的中盒并贴上"易碎、需签收"(传输层),中盒装进大纸箱写上"寄往德国柏林"(网络层),大纸箱扔上开往机场的货车(链路层),最后飞机把它运过大洋(物理层)。每一层只认自己那层的箱子和自己那层的标签,压根不关心里面还套了几层。

层次负责类比代表协议
应用层这次对话的"业务"是什么写信还是寄包裹HTTP · SMTP · DNS
传输层这次对话"靠不靠谱"挂号信 vs 平信TCP · UDP
网络层这封信"送到哪个城市"分拣中心IP · ICMP
链路层"送到下一个路口"邮递员最后一公里Ethernet · WiFi
物理层"怎么把信号发出去"用光缆还是无线电光纤 · 5G · 双绞线
Analogy · 写信的每一步

你写一封信给朋友——你决定内容(应用层);你决定要不要挂号(传输层);你写上收信城市(网络层);邮递员负责送到对方楼下(链路层);最后邮车 走哪条路(物理层)。
每层都有专门的"工人"和专门的"规则",互不干扰——这就是分层的智慧。

这一整摞协议叠在一起,就叫协议栈(Protocol Stack)。"栈"这个字用得很准:数据发出去时从上往下一层层加壳,收进来时从下往上一层层剥壳,严格的后进先出。你打开一个网页时,这一摞协议是这样同时开工的:

【发送方 · 你的电脑】                     【接收方 · 服务器】

应用层   HTTP: "GET /index.html"          HTTP: 解析请求,返回页面
           ↓ 加 TCP 头(20B)                     ↑ 剥掉 TCP 头
传输层   TCP: 端口 54321 → 443              TCP: 检查序号、校验和、排序
           ↓ 加 IP 头(20B)                      ↑ 剥掉 IP 头
网络层   IP: 192.168.1.20 → 93.184.216.34   IP: 确认目标是我
           ↓ 加以太网头(14B)                    ↑ 剥掉以太网头
链路层   Ethernet: 我的MAC → 网关MAC          Ethernet: CRC 校验通过
           ↓                                      ↑
物理层   ━━━━━━ 电磁波 / 光脉冲 / 电压 ━━━━━━━━━━━━━━━━━━

一句"GET /index.html"(24 字节)出门时已变成 78 字节的以太网帧

这张图里藏着一个精妙的对称性:每一层只跟对面的同一层"对话"。你的 TCP 层是在跟服务器的 TCP 层商量窗口大小,中间经过的十几个路由器完全插不上话——它们只看得懂网络层。这种"同层对话、跨层封装"的结构叫对等层通信(peer-to-peer communication),是分层模型最核心的设计。用邮政来类比:你和朋友在"信件内容层"对话,两地邮局在"分拣层"对话,两个邮递员在"投递层"对话——三组人各说各话,互不干扰,却共同完成了一次通信。

这里的关键点值得单独说透:你的 TCP 只跟对方的 TCP 说话,中间的路由器一句也插不上嘴。好比你寄一封信,信里写"记得带伞"——这句话是你和收信人之间的对话,中间经过的邮局、分拣中心、邮递员,一个都没资格参与讨论今天下不下雨,他们只看信封。同层对话、跨层封装说白了就是这个意思。

顺手记住每一层数据单元的正式名字,读文档时会省很多力气:应用层叫报文(Message),传输层 TCP 叫段(Segment)、UDP 叫数据报(Datagram),网络层叫分组/包(Packet),链路层叫帧(Frame),物理层就是比特流(Bit)。日常聊天大家一律叫"包",但看规范时这些词是严格区分的。

这几个名字乱得像绕口令,但记法很简单:想象同一批货在不同环节的叫法——在你手上叫"礼物",装了盒叫"包装件",进了快递系统叫"包裹",装上车叫"一车货"。东西压根没变,只是每个环节的人按自己的习惯换了个称呼。报文、段、包、帧,就是同一批数据在五个环节里的四个昵称。

还有一件事必须点明:OSI 七层是理论模型,TCP/IP 四层是现实实现。OSI 由国际标准化组织设计得极其工整(会话层、表示层各司其职),但它太复杂、落地太晚,商业上彻底败给了先跑起来的 TCP/IP。今天的现实是:我们用 OSI 的术语("这是二层问题"、"七层负载均衡"),跑 TCP/IP 的代码。这个"名义标准输给事实标准"的故事,在技术史上一遍遍重演。

这段关系换成大白话:OSI 好比学校发的那本《课程标准》,写得极其周正;TCP/IP 好比老师实际用的那本教案,只有四章,但真能把课讲完。今天的现实是——大家嘴上引用《课程标准》的章节号("这是二层问题"),手里翻的却是那本教案。名义上的标准和实际跑着的东西,从来不是一回事。

你每天用到的 8 个协议

协议它管什么你什么时候用到它
HTTP / HTTPS浏览网页每天打开任何网页
DNS域名翻译成 IP你输入 baidu.com 的瞬间
TCP可靠传输聊天消息、邮件、文件下载
UDP快速传输(不保证到达)视频会议、直播、游戏
IP把数据包送到目标城市每一次网络通信
ICMP网络诊断你 ping 一台服务器时
DHCP自动分配 IP你每次连 WiFi
SMTP发邮件你点"发送"按钮时

这八个用一句生活话就能串起来:你输网址那一刻,DNS 帮你查通讯录问到号码;TCP 好比寄挂号信、非要对方签收;UDP 好比往楼下喊一嗓子、听没听见不管;IP 负责把包裹送到正确的城市;ICMP 是你打过去问"喂喂听得见吗"的那声试探;DHCP 是你进小区时物业给你分的门牌号;HTTP 是你跟服务员说的那句"来一份宫保鸡丁";SMTP 是你把信投进邮筒的那个动作。八个协议,八个日常动作,一个不多。

请求-响应模型:绝大多数协议的骨架

把上面那 8 个协议摊开看,你会发现它们的交互形状高度雷同:一方问,一方答。这个"一问一答"的结构叫请求-响应模型(Request-Response),它是协议世界里最主流的骨架,也是理解一切网络行为的默认框架。

请求-响应说白了就是餐厅点菜:你(客户端)不开口,厨房(服务器)绝不会主动给你端菜;你说一句"来份炒饭",厨房就出一份炒饭。一问一答,一次一对,你不问它就一直等着。这也解释了为什么厨房再空闲也不会主动给你送吃的——不是它不想,是这套规矩里压根没有"厨房先说话"这一条。

但"一问一答"其实有好几种变体,各解决不同的问题。这是初学者最容易混淆的一块,一张表就能理清:

交互模式形状谁先说话典型协议 / 场景
请求-响应问 → 答,一次一对客户端HTTP、DNS 查询、数据库查询
轮询 Polling反复问"有新消息吗"客户端老式网页每 5 秒刷一次,浪费带宽
长轮询 Long-Poll问了以后服务器先不答,有消息才答客户端早期网页聊天室的标准做法
服务器推送服务器主动说话服务器SSE、WebSocket、微信新消息通知
发布-订阅订阅一个"主题",有内容就自动来双向MQTT(物联网)、Kafka
流式 Streaming建立一次连接,持续单向灌数据服务器视频直播、gRPC Streaming

这里有一个反直觉但极其重要的事实:HTTP 协议本身不允许服务器主动说话。它的设计是纯粹的"客户端问、服务器答",服务器没有任何办法在没被问的时候插一句嘴。那微信、钉钉、网页版邮箱是怎么"主动"弹出新消息的?答案是它们全都在耍花招——要么让客户端偷偷不停地问(轮询),要么让客户端问了以后服务器故意不回、憋着直到有消息才回(长轮询),要么干脆升级到 WebSocket 这个允许双向说话的协议。

那三种"花招"用点菜来理解,一秒就懂:轮询是你每隔十秒钟就喊一次服务员"我的菜好了吗"(问一百次,九十九次白问,服务员烦你也烦);长轮询是你喊一次"好了叫我",服务员就站在你桌边不走,菜好了才开口(省事,但占着一个服务员);WebSocket是你和厨房之间拉了根对讲机,谁想说话随时按一下——这才是真正的双向。

WebSocket 的升级过程本身就是一个协议协商的漂亮案例。它不另开端口,而是借 HTTP 的壳"变身"

# 客户端先发一个看起来很正常的 HTTP 请求,但夹带了升级请求
GET /chat HTTP/1.1
Host: chat.example.com
Upgrade: websocket              <- "我想换个协议聊"
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13

# 服务器同意了
HTTP/1.1 101 Switching Protocols   <- 101 这个状态码专门用于"换协议"
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

# 从这一行之后,这个 TCP 连接上跑的就不再是 HTTP 了,
# 而是 WebSocket 帧格式,双方都能随时主动发消息。

为什么要这么绕,而不直接开一个新端口跑 WebSocket?因为现实世界的防火墙只信 80 和 443。公司网络、酒店 WiFi、运营商网关,大量只放行这两个端口。借 HTTP 的壳完成升级,等于是"穿着 HTTP 的制服过安检,进去以后再换衣服"。说白了,这就跟坐地铁一样:安检口只认那几种能过机的包,你想带的东西再正当,也得先装进一个能过安检的袋子里。101 Switching Protocols 这个状态码干的活儿,就是"过完安检以后当场把袋子换掉"。协议设计从来不是纯技术选择,它得跟已经存在的几十亿台设备妥协。

有状态 vs 无状态:协议记不记得你

这是协议设计里最重要的一条分水岭,也是面试和实际架构里出现频率最高的概念。定义极简:服务器处理这一次请求时,需不需要依赖"上一次请求留下的记忆"?需要的叫有状态,不需要的叫无状态。

打个比方,这就是小区门口那家便利店的老板火车站窗口的售票员的区别:便利店老板记得你上次赊了五块钱、爱喝哪个牌子的水(有状态);火车站售票员每次都当你是生面孔,你必须把出发地、到达地、日期、身份证从头说一遍(无状态)。前者省你的话,后者省他的记忆——而且换一个窗口办也完全一样。

有状态 · Stateful无状态 · Stateless
服务器记忆为每个客户端维护一份上下文处理完就忘,每次请求都是初次见面
典型协议FTP、SMTP、TCP、SSH、数据库连接HTTP、DNS、UDP、大多数 REST API
好处交互简洁——之前说过的不用重复可任意扩容:请求打到哪台服务器都一样
坏处服务器内存吃紧、挂了状态就丢、难做负载均衡每次请求都要携带全部信息,报文更大
崩溃后果整个会话失效,得重新登录/重连几乎无感——换台机器接着处理

用两个真实协议对比一下就很清楚。FTP 是有状态的:你 cd /docs 进了一个目录,之后 get a.txt 服务器知道你要的是 /docs/a.txt——它记住了你"当前在哪"。HTTP 是无状态的:你请求 /docs/,再请求 /docs/a.txt,服务器对这两次请求毫无关联认知,第二次请求必须自己把完整路径写全。

那问题来了:既然 HTTP 无状态,为什么你登录淘宝一次,之后浏览几十个页面都不用重新登录?因为工程师在无状态的协议上,用了一个巧妙的补丁:把"记忆"存在客户端,每次请求自己带过来

Cookie 干的活儿,说白了就是医院给你的那张就诊卡:医院不可能记住每天几千个病人分别是谁,所以它给你一张卡,每次挂号你自己刷一下——卡一刷,医院立刻"想起"你是谁、上次开了什么药。记忆压根不在医院脑子里,在你手里那张卡上。这就是无状态协议装出"记得你"的全部秘密。

# 第一次:你登录成功,服务器发一张"通行证"给你
HTTP/1.1 200 OK
Set-Cookie: session=a8f3e91c...; Max-Age=604800; HttpOnly; Secure

# 之后每一次请求,浏览器自动把通行证附上
GET /cart HTTP/1.1
Cookie: session=a8f3e91c...    <- 服务器一看就知道"哦是小明"

# 服务器端逻辑:拿这个 session ID 去 Redis 里查是谁
# 协议依然无状态(每次请求都自带全部信息),
# 但用户体验上有了"记忆"。

无状态的价值在云时代被放大到了极致。正因为 HTTP 无状态,淘宝才能在双十一临时加开一万台服务器——你的第一个请求可能打到杭州的机器,第二个打到南京的,第三个又回杭州,全都能正确处理,因为每台机器都不需要"认识"你,只需要能读懂你带来的那张通行证。如果 HTTP 是有状态的,你的所有请求就必须固定打到同一台机器(这叫"会话粘滞"),那台机器一挂你就得重新登录,扩容也会变成噩梦。

反过来说,有状态并非落后。SSH、数据库连接、视频通话都必须有状态——它们需要维护加密密钥、事务上下文、编解码器状态,这些东西每次重建的代价远大于维护它们的代价。判断标准很简单:如果"上下文"很小或很容易由客户端携带,就设计成无状态;如果上下文很重、重建很贵,就得有状态。

这个判断标准换成大白话:如果你能把"上次说到哪儿"写在一张小纸条上随身带着,就无状态;如果那是一整柜子的档案、搬都搬不动,就只能有状态。比如银行柜台给你办一笔长业务,中间要核身份、验密码、打印凭条,这些东西不可能每一步都让你重新排一次队——所以它得有状态。而查一下余额这种事,刷卡就行,谁给你办都一样。

协议为什么"必须双方都遵守"

这是新手最容易困惑的一点——协议不是"厂家规定",它是双方都必须遵守的"约定"。如果两台机器用了不同的协议,它们就互相听不懂——就像你打电话说的是中文,对方只懂西班牙语,根本聊不下去。

再补一个更狠的例子:协议不对,比压根连不上更麻烦。连不上你至少知道"断了,去修";协议不对是电话通了、双方都在说话、都觉得自己说得很清楚,结果聊了十分钟发现完全是两回事。这好比两个人在会议室里各说一门方言,都以为对方在点头同意,散会才发现签的合同不是一份东西。

生活场景
📻 对讲机为什么有"频道"

保安对讲机上有"1-16 频道"。你说 "1 频道",对方也必须调到 1 频道——频道就是"频率协议"。
换成 WiFi 也是同样:2.4G / 5G / 6G 是不同"频道",WiFi 4 / 5 / 6 / 7 是不同"协议版本"——路由器和手机必须支持同一版本才能聊。
你买过"WiFi 6 路由器但手机只支持 WiFi 5"的情况吗?它能工作,但只会按双方都支持的最老协议跑——这就是协议协商。

明文协议 vs 加密协议:那把小锁到底锁了什么

互联网早期的协议全是明文的——因为发明它们的是一群互相信任的科研人员,压根没想过会有人偷看。后果是:只要能碰到数据流经的任何一台设备,你就能完整读到所有内容,包括密码。

明文协议说白了就是用明信片寄信:不用信封,字全写在外面,从邮局到分拣中心到邮递员,谁顺手一瞥都能读完。当年这么设计不是疏忽,是因为那时候整个网络上只有几百个互相认识的研究员——好比一个只有二十户人的小村子,家家都不锁门,因为压根没有外人会来。今天的互联网是个几十亿人的超级都市,还不锁门就是找事了。

这不是理论风险,而是可以现场演示的事实。用 telnet 手动敲一个 HTTP 请求,你会发现整个过程就是纯文本聊天:

$ telnet example.com 80
Connected to example.com.
GET / HTTP/1.1                    <- 你手打的,服务器完全接受
Host: example.com
                                  <- 敲一个空行

HTTP/1.1 200 OK                   <- 服务器回你了
Content-Type: text/html
...

# 换成 443 端口(HTTPS)再试,你会看到一堆乱码
$ telnet example.com 443
GET / HTTP/1.1
<- 服务器直接掐断连接:你没做 TLS 握手,我不认识你

下面这张表列出常见协议的"明文版"和"加密版"对应关系。规律非常清楚:几乎每个老协议都在后来长出了一个带 S 的加密孪生兄弟

用途明文协议(端口)加密协议(端口)明文时代的风险
网页浏览HTTP(80)HTTPS(443)密码、Cookie、浏览内容全暴露
远程登录Telnet(23)SSH(22)root 密码直接被抓走
文件传输FTP(21)SFTP / FTPS(22 / 990)账号与文件内容全裸奔
收邮件POP3(110)/ IMAP(143)POP3S(995)/ IMAPS(993)邮箱密码与全部邮件
发邮件SMTP(25)SMTPS(465 / 587)邮件内容可被篡改
域名解析DNS(53)DoH(443)/ DoT(853)你访问过哪些网站全被记录

要理解那把小锁到底提供了什么,得把它拆成三件独立的事——这三件事经常被笼统地叫做"加密",其实是三种不同的保障:

这三件事用寄挂号信来分,一秒就清楚:机密性=信装在不透光的信封里,路上没人读得到内容;完整性=信封上有火漆封印,被人拆开过你一眼就能看出;身份认证=收信人得出示身份证签收,证明他真是收件人不是隔壁冒名的。三件事缺一件,这封信都算不上安全——尤其是第三件:信封再厚、封印再牢,送错了人就全白搭。

常见误解值得澄清两个。第一:HTTPS 不等于"这个网站安全可信"。任何人花几分钟就能给自己的钓鱼站申请一张免费证书,浏览器照样显示小锁。小锁只保证"你连的确实是地址栏里那个域名,且传输没被偷看",它对网站本身是好人还是坏人一无所知。第二:HTTPS 隐藏不了你访问的域名。TLS 握手时客户端要在 SNI 字段里明文写出目标域名(不然一个 IP 上的几千个网站不知道该拿哪张证书给你),所以运营商依然能记录你访问过哪些站,只是看不到具体页面内容。ECH(Encrypted Client Hello)就是为了补上这个洞而生的新标准。

那个"藏不住域名"的漏洞,通俗地说就是:你的信封是不透光的,但信封外面必须写着收件人的地址,不然邮局根本不知道往哪送。所以运营商永远知道你"给谁写了信、写了多厚的一封、几点写的",只是不知道信里写了什么。很多人以为开了 HTTPS 就完全隐身了,这是最普遍的误会之一。

协议的版本演进与兼容性

网络用了 50 年,怎么没崩?因为新协议几乎都向后兼容——你 WiFi 6 路由器也接受 WiFi 5 手机;HTTP/2 浏览器也能跟 HTTP/1.1 服务器对话。这就是为什么 1980 年的老标准到今天还能用。好的协议不是取代旧的,而是兼容旧的。

向后兼容打个比方,就像小区换了新的门禁系统,但老住户手里那张旧卡照样能刷——新系统悄悄多认了一种旧格式。要是物业哪天宣布"旧卡全部作废,全体重新办卡",那一天小区门口能堵到晚上。互联网跑了五十年没崩,靠的就是从来没敢干这件事。

以 HTTP 这条演进线为例,你能看到一个协议如何在三十年里被逼着做了三次大手术,每次都在解决上一代留下的病根:

版本年份关键改进它解决的痛点
HTTP/0.91991只有一个 GET,返回纯 HTML,连头部都没有能用就行
HTTP/1.01996加入头部、状态码、Content-Type能传图片和非 HTML 内容了
HTTP/1.11997长连接(keep-alive)、Host 头、分块传输不用每张图都重开一次 TCP;一个 IP 能托管多个网站
HTTP/22015二进制分帧、多路复用、头部压缩、服务器推送解决"队头阻塞":一个连接里几十个请求可以并行
HTTP/32022底层从 TCP 换成 QUIC(基于 UDP)解决 TCP 层的队头阻塞;移动网络切换 WiFi/5G 不断连

这条线里最激烈的一步是 HTTP/3。它把底层的 TCP 换成了 UDP——这在十年前是不可想象的,因为 TCP 的可靠传输被认为是天经地义的基础。为什么要换?因为 TCP 有个无法绕开的毛病:它按字节流严格保序。一个网页并行下载 50 个资源,如果其中一个包丢了,TCP 会把后面所有已经到达的数据都扣在缓冲区里等那个丢的包补齐——哪怕后面的数据属于完全无关的另一张图片。这就是"队头阻塞"。HTTP/2 在应用层解决了它,但 TCP 层的阻塞依然存在,只能连 TCP 一起换掉。这告诉你一件事:分层的边界不是神圣的,当某一层成为瓶颈时,人们真的会把它整个换掉。

"队头阻塞"这个词听着玄,其实就是超市排队结账:你后面排了五十个人,前面那位大爷在翻遍全身找一张优惠券——货都在筐里、钱都在手上,全体五十人干等着。TCP 严格保序就是这个规矩:不管后面的包多完整,前面丢了一个,全都得在缓冲区里排着。QUIC 干的活儿相当于超市开了自助结账机——大爷继续找券,别人扫码就走。

兼容性在工程上是怎么实现的?三种手段,都很朴素:

反面教材也很有教育意义:IPv6 恰恰是不向后兼容的——IPv4 的主机无法直接和 IPv6 的主机通信,因为地址长度和头部格式完全变了。结果是什么?IPv6 在 1998 年就定稿了,二十多年后全球普及率才刚过一半,中间催生了 NAT64、6to4、双栈这一堆过渡技术。这是整个网络史上关于"不兼容的代价有多大"最昂贵的一课。

那三种兼容手段,用生活话讲就是:版本号协商=你说"我会中文和英文",对方说"我只会中文",那就说中文(取双方都会的里头最好的那个);忽略未知字段=表格上多出一栏你看不懂的,就空着别管,千万别把整张表退回去;平行部署=新旧两个窗口同时开着,谁愿意去哪个去哪个。IPv6 的悲剧在于它三条都没做到——相当于换门禁的时候连门框一起换了,旧卡插都插不进去。

RFC:协议是怎么被"定下来"的

你可能好奇:既然协议是全世界共同的约定,那到底谁有权定它?答案会让你意外——没有任何政府或公司说得上话,靠的是一群工程师写文档、公开吵架、然后看谁的实现真的跑得起来。

这些文档叫 RFC(Request for Comments,"请求评论")。名字本身就是这个体系的精神写照:它不叫"标准",不叫"规定",而叫"请大家来提意见"。第一份 RFC 1 写于 1969 年 4 月,作者是加州大学洛杉矶分校的一名研究生 Steve Crocker——他当时怕自己的语气显得太权威,才特意起了这么客气的名字。这个客气的名字延续了五十多年,如今编号已经过了 9600 号。

RFC 这套体系,打个比方就像小区里定"垃圾几点扔"这件事:没有哪个部门下红头文件,是几个热心住户在群里发了个提议,然后全楼吵了两个月,最后试着按这个法子扔了一个月发现真行得通,于是就成了规矩。听起来松散,但正因为它是吵出来又试出来的,落地时几乎没人反对。

RFC 编号内容为什么值得知道
RFC 791IPv41981 年定稿,今天全球一半流量还在用它
RFC 793TCP1981 年,三次握手、拥塞控制的源头(2022 年被 RFC 9293 整合替代)
RFC 1035DNS1987 年,整个域名系统的规范
RFC 2616 → 7230~7235 → 9110HTTP/1.1被拆分、重写过多次,说明"标准也在不断返修"
RFC 9000QUIC2021 年,HTTP/3 的底座
RFC 2324超文本咖啡壶控制协议愚人节玩笑文档,却贡献了真实存在的状态码 418 I'm a teapot

一份新协议要走完的路大致是:有人写出草案(Internet-Draft)→ 在 IETF 的邮件列表里被公开批斗几个月甚至几年 → 至少要有两个独立的、能互通的实现(这条是关键)→ 被 IESG 批准 → 分配一个 RFC 编号,成为"提议标准"→ 若被广泛采用,升为"互联网标准"。整个过程遵循 IETF 的一句名言:"We reject kings, presidents and voting. We believe in rough consensus and running code."(我们不要国王、总统和投票,我们信奉大致的共识和跑得起来的代码。)

"至少两个能互通的实现"这条要求,是这个体系最聪明的设计。它意味着一份标准如果写得含糊不清、有二义性,那两组人各自照着实现出来的东西就一定连不上——问题会在成为标准之前就暴露。这道门槛把无数纸上漂亮但实际无法落地的设计挡在了外面。相比之下,OSI 七层模型走的是"先由委员会设计完美标准,再要求厂商实现"的路,结果就是设计得极其工整却迟迟落不了地,最终败给了"先跑起来再补文档"的 TCP/IP。

"必须有两个能互通的实现"这条门槛,说白了就是考试要交两份独立答卷,两份得对得上:如果题目本身出得含糊,两个人各自理解、各自作答,答案一定不一致——问题在成为标准之前就暴露了。这一条把无数"写在纸上很美、真做起来两边接不上"的设计挡在了门外,是整个互联网标准体系里最聪明的一道设计。

最后一个实用建议:RFC 是公开且免费的,地址是 rfc-editor.orgdatatracker.ietf.org。当你对某个协议行为有疑问、网上的答案又互相矛盾时,直接去翻 RFC 原文,那是唯一的最终答案。它们读起来比想象中友好得多——因为写的时候本来就是给同行看的,不是给律师看的。

读懂一次握手

以"TCP 三次握手"为例,让你感受协议有多严谨:

① 客户端:SYN

"我想和你建立连接,我的初始序号是 X。"——只发,不等回话。

② 服务器:SYN + ACK

"我听到了你的 SYN,确认号 X+1;同时我也 SYN,我的初始序号是 Y。"——一次说两件事。

③ 客户端:ACK

"我听到了你的 SYN,确认号 Y+1。"——三次握手完成,双方都知道对方"听到了自己",连接建立。

为什么要三次?两次行不行?答:不行——两次时,服务器不知道客户端有没有收到自己的 SYN。三次之后,双方都明确知道对方收到了自己。协议的设计就是这么严谨。

更深一层的原因跟"旧包还魂"有关。假设只要两次握手,那么一个在网络里迷路了三十秒的旧 SYN 包,终于绕到服务器时,服务器会以为这是一个新连接请求,立刻分配内存、建立连接、等着数据——但客户端早就走了,这个连接会一直空占资源。三次握手的第三个 ACK 相当于让客户端最后确认一句"这个连接我确实还要",把这种幽灵连接挡在门外。协议里那些看起来"多余"的步骤,几乎每一个都是被某个真实事故逼出来的。

三次握手换成大白话就是打电话开头那三句:你"喂?"(SYN)→ 对方"喂,听得到,你能听到我吗?"(SYN+ACK)→ 你"能听到,那我说了"(ACK)。为什么非要第三句?因为到第二句为止,只有你确认了"对方听得见我",对方还不知道"你听不听得见他"。他要是这时候就开始说正事,可能全白说了。三句话之后,两个人都确认了"我说的话对方能听见"——这才叫连接建立。

握手不只有三次这一种。把常见协议的"开场仪式"排在一起,你能明显看到安全和速度之间的拉锯

协议建立连接需要几个往返(RTT)做了什么
UDP0不握手,直接开发。所以它快,也所以它不可靠
TCP1(三次握手实际只占 1.5 个 RTT)确认双向可达、交换初始序号
TCP + TLS 1.21 + 2 = 3再加两轮:交换证书、协商密钥套件、生成会话密钥
TCP + TLS 1.31 + 1 = 2TLS 1.3 把握手精简掉一个往返,这是它最大的卖点
QUIC(HTTP/3)1,重连时 0把传输层握手和加密握手合并成一次,还能复用上次的密钥

这张表解释了一个你能实际感受到的现象:跨国网站第一次打开特别慢。假设北京到美国单程 90ms,一个 RTT 就是 180ms。用 TCP + TLS 1.2,光是"打招呼"就要 3 个 RTT = 540 毫秒,这时候一个字节的网页内容都还没开始传。换成 QUIC,同样的距离只需 1 个 RTT = 180ms,直接省掉 0.36 秒。这就是为什么全球性的大公司拼命推 HTTP/3——在跨洋链路上,减少握手往返次数比增加带宽有效得多。

那 540 毫秒是个什么感觉?差不多是你眨五次眼的时间,也是人耳能明确察觉到"卡了一下"的门槛。更关键的是这半秒里一个字都还没开始传——纯粹花在"喂喂喂你好你好"上。这好比你去银行办事,取号、核身份、验密码折腾了十分钟,真正办的那笔业务只用了三十秒。QUIC 干的活儿就是把前面那十分钟压到三分钟,业务本身一点没变快,但你体感上快了一大截。

Recap · 收束

协议 = 网络里的"共同语言"。它规定了格式、含义、时序,让两台互不相识的机器能听懂对方。
三要素记牢:语法(格式怎么写,错一个空格就 400)、语义(每个字段啥意思,写错了不报错但全盘皆错)、时序(谁先说话、出错怎么办)。
分层是协议世界最重要的组织方式,它把"8×5×100 个组合"变成了"8+5+100 个协议"——把乘法变成加法。每层只跟上下相邻的层打交道,且只对同层的"对端"说话,中间的路由器插不上嘴。代价是每层都要加头部(约 3.6% 开销)和多次内存拷贝。
交互形状上,绝大多数协议是请求-响应;HTTP 天生不许服务器主动说话,所以才有轮询、长轮询、WebSocket 这些花招。状态上,HTTP 的无状态是它能在云上无限扩容的根本原因,Cookie 只是把"记忆"甩给了客户端。
协议还在不断演进,规律是能兼容就兼容——HTTP 从 0.9 走到 3,连底层 TCP 都换成了 QUIC;而 IPv6 因为不兼容,二十多年才普及一半。所有这些约定,都由公开免费的 RFC 文档定义,靠的是"大致的共识和跑得起来的代码"。
你不需要背下所有协议——只要记住一件事:遇到"两台机器连不上"的问题,第一步就是问:它们的协议对得上吗?版本对得上吗?谁该先说话?

☰ 主页
Xue Hai Wu Ya · Network · § 1.2 · Protocol