打开《计算机网络:自顶向下方法》应用层这一章,是个周末下午。这本书书名里的“自顶向下”四个字,在这一章里体现得淋漓尽致——它没有先急着讲物理层的载波怎么调制、链路层的帧怎么封装,而是把浏览器、邮件、聊天软件这些我们每天都在用的东西摆到桌上,顺着“它们到底是怎么通信的”这一问题,一路往下拆。这一篇笔记围绕应用层展开,适合正在上网络课的同学,也适合想系统梳理网络协议栈的开发。读完这一篇,你能搞懂 HTTP、DNS、FTP 这些协议究竟在设计什么、Socket 到底是个什么玩意,以及以后遇到网络问题该从哪里下手。
1. 应用层的整体定位与学习思路
1.1 为什么自顶向下一上来就讲应用层
绝大多数网络教科书喜欢从物理层开始,先讲双绞线、光纤、编码方式,读者往往要熬过几百页才能看到 TCP/IP 和 HTTP,热情已经消磨得差不多了。自顶向下方法反其道而行,第一站就是应用层,理由其实非常务实:应用层是所有网络协议栈里离普通用户和开发者最近的一层,也是我们感知最明显的一层。
想一想你每天在做什么:打开网页是 HTTP/HTTPS、收发邮件是 SMTP/POP3/IMAP、看视频是 HTTP 配合流媒体协议、玩在线游戏往往走 UDP 加速通道。这些全都是应用层协议的应用。学习一门知识,先建立“它解决什么问题”的直觉,再深入到细节,效率会高得多。应用层恰好提供了这个直觉入口——你不用先知道 TCP 的拥塞控制算法怎么实现,就能先理解为什么网页要建立连接、为什么视频会卡顿、为什么 DNS 解析失败会直接导致“网页打不开”。
还有一个容易被忽视的点:应用层是软件开发人员真正会亲手编程接触的一层。绝大多数业务代码都运行在这一层,通过 Socket 调用下面各层的服务。先学应用层,意味着你能尽早把“网络”和“写代码”两件事联系起来,而不是在抽象的协议状态机里面打转。我的感受是,读完这一章再回头看底层章节,很多困惑会自动消失,因为你知道每一个底层机制最终是为了服务哪一类应用需求。
1.2 端到端原则:网络只负责尽力而为,靠谱的事交给应用自己
应用层学习过程中最需要建立的一个底层信念是“端到端原则”。这个原则说起来很简单:网络核心(路由器、交换机组成的中间部分)只做一件事——尽力把数据包转发到目的地,至于数据是否完整、是否有序、是否被篡改,这些“聪明”的功能尽可能放在端系统(也就是你我的电脑、手机、服务器)上实现。
为什么要这样设计?因为网络核心要保持足够的简单和高速。路由器每秒钟要处理海量数据包,如果让它在转发过程中同时做复杂的校验、重传、排序,转发速度会急剧下降,网络设备也会复杂到难以管理。而端系统是智能的,它有充足的计算资源来完成这些精细活。这就好比物流公司只负责把包裹从一个城市送到另一个城市,至于包裹里面是什么、易碎品有没有贴标签、签收的时候要不要开箱验货,那是发件人和收件人之间的事,物流干线不会拆开每个包裹去检查。
这个原则直接解释了应用层为什么可以放心选择不同的传输服务。如果应用需要确保数据不丢失、按顺序到达,就选择 TCP;如果应用更看重实时性、能容忍少量丢包,就选择 UDP。应用层协议本质上就是在这两种传输服务之上制定的“规矩”,比如 HTTP 基于 TCP,因为它要求页面完整可靠;而实时语音通常基于 UDP,因为晚到和错序的数据已经失去意义。理解端到端原则之后,你就不会再问“为什么 TCP 不是应用层协议”这种问题了——传输服务是通用的,不该绑定在某一种具体应用上。
2. 核心协议逐层拆解:HTTP、FTP、SMTP 与 DNS
2.1 HTTP 协议:从持久连接到多路复用
HTTP 是应用层最重要的协议,没有之一。它定义了一个极其简单的模型:客户端发起请求,服务器返回响应。请求报文和响应报文的格式我们在笔记(一)里已经梳理过,这一篇重点聊聊它的演进逻辑,因为理解演进才能理解协议为什么长成今天这样。
最初的 HTTP/1.0 是非持久连接,每个请求都要新建一个 TCP 连接,页面里有十张图片就要建立十次连接。每次连接都要经历 TCP 三次握手和慢启动,网页加载自然会慢。HTTP/1.1 引入了持久连接,默认完成一次请求后连接不关闭,后续请求可以复用已有连接。这个改进极大降低了连接开销,但也暴露出新的问题:持久连接上的请求必须按先进先出的顺序处理,队头阻塞随之而来。如果第一个请求的服务端处理很慢,后面所有请求都得排队等着。
HTTP/2 用多路复用解决了这个瓶颈。它允许同一个 TCP 连接上同时交错传输多个请求和响应,以帧为最小单位打散传输,然后按流 ID 重组。直观来说,HTTP/1.1 像是单车道排队通行,HTTP/2 则把车道分成多个并行的通道,车辆可以在不同通道上互不干扰地并行行驶。HTTP/2 还引入了头部压缩,大量重复的请求头字段被索引替代,减少传输字节数。
HTTP/3 则更进一步,把传输层从 TCP 换成了基于 UDP 的 QUIC 协议,从根上解决了 TCP 层面的队头阻塞。TCP 是可靠的、有序的字节流,一个数据包丢失会导致后续所有已到达的数据包在缓冲区里等待重传,这就是 TCP 队头阻塞;而 QUIC 允许数据包独立到达、独立确认,单个包丢失只影响它自己。加上 0-RTT 的连接建立优化,HTTP/3 在弱网环境下的表现明显优于前代。读这一章时要注意,教材往往以 HTTP/1.1 为基准讲解,但实际开发现状已经是 HTTP/2 普及、HTTP/3 各云厂商在陆续支持,两者都需要了解。
2.2 Cookie、Web缓存与条件GET:无状态协议的补丁逻辑
HTTP 设计时是无状态的,也就是说服务器不记得你上一次请求做过什么。这是为了保持服务器实现的简单和可扩展,但也带来一个现实问题:电商网站需要记住购物车、登录状态、个性化推荐,这些都需要“记住用户”。解决办法就是 Cookie 和 Session。
Cookie 的机制很巧妙:服务器在响应头里通过 Set-Cookie 字段发放一个唯一标识,浏览器存下来,之后每次请求自动在 Cookie 头中带回去。服务器看到这个标识,就能从自己的数据库或缓存里找到对应用户的状态信息。教材里总结了 Cookie 的四类典型用途:身份识别、购物车记录、个性化推荐、网站访问跟踪。注意 Cookie 本身存在安全隐患,比如跨站脚本攻击可以窃取 Cookie,所以现代应用往往会给 Cookie 加上 HttpOnly、Secure 等属性,甚至使用更安全的 Token 机制。
Web 缓存(代理服务器)是另一个有趣的设计。如果所有用户都从源服务器获取数据,服务器压力大、网络流量也大。缓存服务器部署在网络边缘,替源服务器存储热门内容的副本。当用户请求命中缓存时,直接从缓存返回,延迟大幅降低。但缓存内容可能过期,于是就有了条件 GET:缓存服务器用 If-Modified-Since 或 ETag 头去源服务器确认内容有没有变化,源服务器返回 304 Not Modified 表示没变,缓存继续使用旧副本;返回 200 和新的实体内容则代表内容已更新。
这个“补丁逻辑”在计算机网络里特别常见,教材的价值就在于把这些看似零散的设计串起来,让你理解每一次协议改动背后的权衡。读到这里你会有种感觉:互联网协议不是一步到位的完美设计,而是在真实需求驱动下一次次演进出来的,理解了动机自然就能记住规则。
2.3 FTP 与 SMTP 的设计差异对比
FTP(文件传输协议)和 SMTP(简单邮件传输协议)经常被放在一起讨论,因为它们都是较早期的应用层协议。但两者有一个根本性的架构差异:FTP 使用两个并行的连接——控制连接和数据连接。控制连接负责传命令,端口 21;数据连接负责传文件内容,端口 20。这种模式叫“带外控制”,控制信息与数据信息分离,好处是一边传文件一边还能发命令中断传输。
不过,FTP 今天在公网上用得越来越少了,主要因为安全性问题。FTP 默认以明文方式传输账号密码和文件内容,在网络上裸奔,很容易被窃听。这是读这一章时最值得注意的一个细节:协议设计的年代决定了它的安全基因,现代替代方案普遍转向基于 SSH 的 SFTP 或加密的 FTPS。
SMTP 的设计则不同。邮件传输是一个“存储转发”的过程:发送方的邮件服务器把邮件推给接收方的邮件服务器,如果接收方暂时不在线也没关系,邮件会暂存在服务器上,等对方获取。SMTP 本身只负责“推”,命令和响应都是 ASCII 文本,类似对话式交互。这是因为邮件系统诞生于字符终端时代,简单的文本命令是当时最通用的交互方式。
邮件协议组里还需要分清 SMTP 与 POP3、IMAP 的职责。SMTP 负责邮件在服务器之间传输,POP3 和 IMAP 负责用户从服务器取件。POP3 简单粗暴,通常会把邮件下载到本地并删除服务器副本;IMAP 则在服务器上维护邮件目录,支持多设备同步,更适合移动办公场景。用一张表对比更直观:
| 协议 | 用途 | 默认端口 | 传输特性 | 主要问题 |
|---|---|---|---|---|
| FTP | 文件传输 | 21/20 | 控制连接与数据连接分离 | 明文传输,存在安全隐患 |
| SMTP | 邮件推送 | 25/587 | 存储转发,ASCII 命令 | 限制越来越多,加密靠 STARTTLS |
| POP3 | 邮件取回 | 110/995 | 下载后删除服务器副本 | 多设备同步困难 |
| IMAP | 邮件同步 | 143/993 | 服务器维护目录,多端同步 | 服务器存储压力大 |
2.4 DNS:分布式数据库的设计智慧
DNS 是应用层里最像“系统设计”的一个协议。全称是域名系统,核心技术思路是把域名解析成 IP 地址。但难点在于:整个互联网有海量域名,任何单一服务器都扛不住这规模的查询流量,也不可容忍单点故障。
教材给出的答案是层次化的分布式数据库。DNS 服务器划分为根 DNS 服务器、顶级域(TLD)服务器和权威 DNS 服务器。根服务器负责返回顶级域服务器的地址,顶级域服务器负责返回权威服务器的地址,权威服务器负责最终给出具体域名的 IP。这样设计的好处是职责单一、负载分散、故障隔离。你查询某个域名时,不会让某台核心服务器扛下所有流量,而是沿着层级逐级递归或迭代。
这里要理解递归查询和迭代查询的区别。递归查询是“帮我查到底,最后把结果告诉我”,查询负担在 DNS 服务器;迭代查询是“我不知道但告诉你去哪问,你自己去问”,查询负担在客户端。实际解析过程通常是混合的:本机向本地 DNS 服务器发起递归查询,本地 DNS 服务器再向根、TLD、权威服务器发起迭代查询。
DNS 还有一个关键机制是缓存。各级 DNS 服务器都会缓存查询结果,缓存时间由每条记录对应的 TTL(Time to Live)字段控制。通过缓存,绝大多数域名解析请求根本到不了根服务器,本地 DNS 服务器就能直接返回结果,这就是为什么“输入域名基本秒开”。当你在改 DNS 记录后反映“怎么还是老地址”,多半就是在等 TTL 过期。
记录类型是另一个常考点。A 记录把域名指向 IPv4 地址,AAAA 指向 IPv6 地址,CNAME 把域名指向另一个域名,MX 指定邮件服务器,NS 指定权威 DNS 服务器。实际排障中,用 nslookup 或 dig 查询某一类型记录,能快速判断问题出在解析还是连接,这个我们在后面的实操章节展开。
3. 应用架构的选型思考:C/S 与 P2P
3.1 C/S 和 P2P 的本质区别
应用层协议的底层是应用架构,也就是多个通信进程之间怎么组织关系。教材里对比了两种主流架构:客户端/服务器(C/S)和对等网络(P2P)。理解这个对比,对系统设计能力的提升帮助很大,很多人工作多年回想起来,发现当年读书时没意识到这里埋着分布式系统设计的种子。
C/S 架构里,服务器是永远在线、固定 IP 地址的提供服务方,客户端是主动发起请求的使用方。这种架构的中心化特征带来了明显的优缺点。优点:管理简单,服务端可以统一做身份认证、数据存储、权限控制,数据一致性容易保证。缺点:服务器是瓶颈,用户量上来之后必须扩容,同时服务器也是单点故障——服务器挂了,所有客户端都瘫痪。现实中解决瓶颈要靠负载均衡、多机房部署,解决单点需要主备切换,这些都是架构复杂度。
P2P 架构没有固定的服务器,每个参与者既是客户端也是服务器,文件可以从多个节点同时下载。BitTorrent 是典型例子。P2P 最大的优势是扩展性极好:用户越多,节点越多,可供分享的资源也越多,系统反而越健壮。对应的代价是管理困难——缺乏集中管控,节点随时上线和下线,内容版权和安全问题也难处理,所以纯 P2P 在实际商业应用中不多见。
3.2 文件分发模型下的定量对比
教材里有一个经典的文件分发时间对比,值得认真推导一遍。假设要分发一个大小为 F 的文件,C/S 架构下服务器上传速率是 u_s,每个客户端下载速率为 d_i。服务器要把 F 分别发给所有 N 个客户端,在不并发限制的理想情况下,分发时间至少是 N * F / u_s。客户端以 d_min(最慢的那个)下载,时间至少不小于 F / d_min。所以 C/S 模式下分发时间的下界是 max(N * F / u_s, F / d_min)。
P2P 模式下每个 peer 下载完一部分后就能把这部分上传给其他 peer,相当于服务器不再独自承担所有上传流量。分发时间的下界变成 max(F / u_s, F / d_min, N * F / (u_s + Σu_i))。其中第三项分母是服务器和所有 peer 上传速率之和,这体现了 P2P 的“人多力量大”。当 N 很大时,C/S 模式的时间线性增长,而 P2P 模式的时间会被多的上传带宽摊薄。这是我读应用层时印象最深刻的一处定量推演,它把架构差异从感觉层面落到了数学层面。
实际工程里,这两者不是二选一。现在大量视频平台、云盘采用“C/S 管元数据、P2P 传内容”的混合模式。中心服务器负责目录索引、用户认证,内容分发走 P2P 或 CDN 缓存。既有中心化的管理优势,又借助 P2P 降低带宽成本。读到这里可以体会一下,教材里的架构对比并不是孤立的学术分类,它直接对应着真实系统的设计权衡。
3.3 实际系统几乎没有纯粹的架构
学完这一节,还需要建立“架构演进”视角。任何一个成长中的应用系统,都可能因为用户规模、成本结构、合规要求的变化,从一种架构调整为另一种,或者在两者之间取一个平衡。一个小的团队做出来的系统,一开始用简单的 C/S 架构完全没问题,用户量上来之后才逐步引入 CDN 做静态内容加速,再用缓存层降低数据库压力,最终可能部分业务采用 P2P 形式来摊薄带宽成本。
落实到编程层面,应用架构最终会体现在进程间如何通信。这就是 Socket 接口存在的意义。Socket 是应用层与传输层之间的抽象接口,它把 TCP 的三次握手、数据可靠传输、UDP 的数据报收发全部封装成一组 API。对开发来说,写 TCP Socket 服务端时,你会深刻体会到 C/S 模型的每一个点:一条 accept 循环对应一个客户连接,多线程或事件循环是处理并发的常用手段;写 UDP Socket 时又体会到无连接的特性——不用建立连接,直接把数据包发到目的地,灵活但不保证到达。
4. 动手验证:抓包与编程实验
4.1 用 Wireshark 抓一次 HTTP 完整请求
光看书不抓包,等于背菜谱不下厨。应用层这一章最好的验证方式,就是用 Wireshark 抓一次真实的 HTTP 请求,亲眼看协议值班到底长什么样。本地启动 Wireshark,选择正在上网的网卡,设置过滤条件tcp.port == 80或http,然后打开浏览器访问一个普通网站。能看到完整的流程:
- DNS 查询阶段:观察客户端发送的 DNS 查询请求,以及服务器返回的响应,里面包含解析出来的 IP 地址。
- TCP 三次握手:三个标志位为 SYN、SYN+ACK、ACK 的报文依次出现,注意观察序列号和确认号的变化。
- HTTP 请求阶段:客户端发出 GET 请求,请求行第一行形如
GET / HTTP/1.1,后面跟着一系列请求头字段。 - HTTP 响应阶段:服务器返回状态行
HTTP/1.1 200 OK,携带响应头和数据。
第一次抓包时最震撼的往往是:原来一个页面加载要经历这么多轮交互。你可以试着在 Wireshark 里点击 HTTP 数据包,看请求头和响应头中的每一个字段,再回去对照教材里报文格式的图,很多之前没看懂的细节瞬间就明白了。比如Connection: keep-alive是 HTTP/1.1 默认开启持久连接的标志,Content-Length用于标记响应体长度,Cache-Control控制缓存策略。
这里有一个操作技巧:别直接抓所有流量,否则数据包又多又杂,很难找到目标。先在 Wireshark 的显示过滤器里写http.request只看请求,再右键某个请求选择“Follow TCP Stream”,就能看到客户端和服务器之间按时间顺序排列的全部交互内容。这个方法在实际排障中几乎天天用。
4.2 手写一个简易 HTTP 客户端
理解协议最好的方式之一,是抛开现成的库,自己用 Socket 实现一遍 HTTP 客户端。下面这段 Python 代码,用最原始的 Socket 发送 HTTP/1.1 GET 请求:
import socket def http_get(host, path="/"): # 1. 建立 TCP 连接 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect((host, 80)) # 2. 构造并发送 HTTP 请求报文 request = ( f"GET {path} HTTP/1.1\r\n" f"Host: {host}\r\n" f"Connection: close\r\n" f"User-Agent: python-http-client\r\n" f"\r\n" ) sock.send(request.encode()) # 3. 接收并打印响应 response = b"" while True: chunk = sock.recv(4096) if not chunk: break response += chunk sock.close() # 4. 分离响应头和响应体 header, _, body = response.partition(b"\r\n\r\n") print("--- Response Headers ---") print(header.decode(errors="replace")) print("--- Body Preview ---") print(body[:500].decode(errors="replace")) http_get("example.com", "/")这段代码的每一步都在呼应教材内容:socket.socket(AF_INET, SOCK_STREAM)创建的是 TCP 套接字,connect((host, 80))触发三次握手建立连接,send发送字节流形式的 HTTP 请求报文,recv接收服务器返回的响应。注意Connection: close请求头,它告诉服务器响应后关闭连接,简化了接收逻辑——读到数据流的结束就代表响应已经收完。
运行后可以看到服务器返回 200 状态行和完整 HTML 页面。这里的收获不光是“会写代码”,更是理解 HTTP 是基于文本行的协议:无论请求还是响应,头部区由一行行首部字段组成,用空行和正文分隔,一切内容肉眼可读。这也解释了为什么 HTTP 易于调试,排障时用nc或telnet手敲请求就能测试服务器响应。
4.3 用 dig 和 UDP Socket 做一次 DNS 查询
DNS 解析实验同样值得亲手做一遍。在 Linux 或 macOS 终端里执行下面两条命令:
dig example.com dig @8.8.8.8 example.com A第一条使用系统默认配置的本地 DNS 服务器,第二条指定公共 DNS 服务器。观察输出中的关键字段:QUESTION SECTION是查询的问题,ANSWER SECTION是返回的答案,其中包含域名、TTL 值和 A 记录地址。dig会把查询过程显示得很清楚,能直观地看到响应时延,这是排查“为什么域名解析慢”的基本手段。Windows 用户对应使用nslookup,输出虽然不如 dig 详细,但同样能看到解析到的 IP 地址。
再有就是直接用 UDP Socket 发起 DNS 查询。DNS 默认使用 UDP 端口 53,请求格式是一个二进制报文。构造完整报文需要按教材里的报文格式一步步填充标识、标志字段、问题记录,代码会稍长。但亲手构造一次之后,你对“DNS 报文头由 ID、标志、四种计数字段组成”这段描述的记忆会牢固很多。把报文发出去,接收响应后按格式解析,你能亲眼看到响应报文里 Question 和 Answer 部分是怎么组织的。这个实验建议配合 Wireshark 的dns过滤条件一起做,观察请求发出和响应返回的两个数据包。
有一点需要提醒:UDP 是面向数据报的,一次 recvfrom 收到的数据报有可能超过缓冲区大小,所以套接字缓冲区要开得足够大,并且要对解析过程做边界判断。DNS 响应超过 512 字节时可能会触发 TCP 回退,这也是为什么 DNS 服务器通常同时开放 53 的 TCP 端口。这些小细节正是教材里“DNS 大多数时候用 UDP,但在需要时用 TCP”这句话背后的实际原因。
5. 常见问题与排查技巧实录
5.1 分层定位法:应用层的故障先看应用
应用层知识最直接的落地场景就是排障。我自己排查网络问题的顺序几乎固定:先在应用层看现象,再逐层往下排除。举一个真实例子,某次用户报告“网页有时打不开,刷新一下又好”,第一反应不是去查路由器,而是先看应用层能否复现。用curl -v请求目标地址,-v参数会打印连接过程、请求头和响应头。
观察到现象后,判断可能的诱因。如果curl提示Could not resolve host,说明问题出在 DNS 解析,优先检查 DNS 服务器配置、本机 hosts 文件、DNS 缓存。如果解析正常但迟迟没有收到响应,可能是 TCP 连接超时,此时用ping检查网络连通性、用telnet 目标地址 端口检测目标端口是否可达。如果端口通但响应异常,才去抓包分析请求报文内容是否规范,或者怀疑应用服务器自身出了问题。
这个“应用 → 传输 → 网络 → 链路”的排查顺序,正好和自顶向下教材的分层逻辑一致。你在应用层积累的协议知识,在这里全部变成定位问题的工具。常见到的问题整理成一张速查表,方便对照处理:
| 现象 | 优先怀疑 | 排查命令 | 可能原因 |
|---|---|---|---|
| 域名无法解析 | DNS | dig、nslookup | DNS 配置错误、TTL 缓存污染 |
| 连接超时 | 传输层 | telnet、nc | 端口未开放、防火墙拦截 |
| 收到响应但内容错误 | 应用层 | curl -v | 请求头缺失、服务端逻辑异常 |
| 能打开但加载特别慢 | 应用+传输 | 浏览器 F12、Wireshark | DNS 慢、TCP 重传、内容未压缩 |
| 部分用户访问白屏 | CDN/缓存 | curl -H模拟不同区域 | 缓存过期、CDN 拉取失败 |
5.2 应用层开发调试的几个实用细节
章节最后整理几个开发调试中的亲测心得,比较琐碎但很实用。
第一个是连接复用。HTTP 客户端库默认开启连接复用,但如果代码里每次请求都新建连接,页面一多 TCP 连接就会大量堆积,服务端出现大量 TIME_WAIT。高并发下要把连接池的max_connections和keepalive时间调对,避免频繁握手。
第二个是超时设置。应用需要区分连接超时和读超时。连接超时只在connect阶段生效,读超时控制从发送请求到收到首个字节的时间。不设置读超时,一个挂起的后端服务可能让你所有线程卡死,生产事故多半由此引发。
第三个是 DNS 缓存的坑。操作系统和各类客户端库都会缓存 DNS 结果,生产环境里改了域名解析记录,往往要等很久才完全生效。除了确认 TTL,必要时可以通过观察dig的输出与本地缓存的差异来定位问题。对要求实时感知域名变更的服务,可以使用带短 TTL 的解析策略或主动刷新机制。
第四个是关于重试策略。应用层重试要特别小心,因为请求不一定是幂等的。比如支付请求如果超时盲目重发,可能导致重复扣款。实际的规范做法是给请求添加唯一 ID,服务端做幂等控制,同时采用退避重试而非固定间隔重试,避免在服务端故障恢复期间产生请求风暴。
第五个是抓包分析的入门顺序。遇到诡异问题时,先用curl复现,然后在 Wireshark 里定位耗时最长的数据包,用“Follow TCP Stream”查看完整交互。比如网页慢,先看是不是 DNS 等待了很长时间,再看有没有 TCP 重传,最后才看应用响应时间。抓包分析是应用层调试的兜底手段,因为任何协议栈细节都能在数据包层面暴露无遗。
至于协议演进对开发的影响,我个人的体会是:不要太依赖对单条连接的直觉判断,现在很多问题发生在连接池、代理、负载均衡这些中间环节上。理解 HTTP/2 多路复用和 HTTP/3 的特性,能帮助你解释为什么某些调优手段在旧协议下有效、在新协议下失效。读这一章时如果能把视线从单个请求延伸到整个请求链路上,知识的价值会翻倍。
最后分享一个读书建议。读完应用层这一章之后,趁热打铁写一个最简化的小项目:用 Socket 起一个只支持 GET 的静态文件服务器,然后用自己写的客户端去请求它,中间开 Wireshark 全程抓包。这个过程不复杂,但你会完整经历 DNS 解析(访问 localhost 或本机 IP)、TCP 握手、HTTP 请求和响应四大环节,等于把整章核心内容亲手跑了一遍。读一遍和亲手抓一次包,是完全不同的两种理解深度,前者是记住,后者是内化。