一、网络应用体系结构

写一个网络应用,你只需要为端系统写代码。你不需要为路由器写任何东西——这是分层带给应用开发者最大的礼物。

1.1 客户机–服务器(Client-Server)

                    ┌──────────┐
   客户机 ─────────→│  服务器   │  永久 IP、永远在线
   客户机 ─────────→│ (数据中心) │
   客户机 ─────────→└──────────┘
   客户机之间不直接通信

特征:

  • 服务器:永久在线、固定 IP、通常是数据中心里的集群
  • 客户机:间歇连接、可能是动态 IP、彼此不直接通信

⚠️ 可扩展性问题:服务能力受服务器资源限制。N 个客户机同时下载 F 大小的文件,服务器至少要发 N·F 比特。服务时间随 N 线性增长。

1.2 对等(P2P)

    Peer ←——→ Peer
      ↑ ╲    ╱ ↑
      │   ╳    │       没有永远在线的服务器
      ↓ ╱    ╲ ↓       任意端系统直接通信
    Peer ←——→ Peer

特征:自扩展(self-scalability)——每加入一个 peer,既增加了需求,也增加了服务能力。

代价:管理复杂(peer 随时上线下线、IP 会变)、安全难保证、激励机制难设计。

📌 量化对比在第 8 讲:我们会推导出 P2P 分发时间的下界,并看到它在大 N 时几乎不随 N 增长,而 C/S 是线性增长。

1.3 混合形态

现实中的大型系统几乎都是混合的:BitTorrent 用中心化的 tracker + P2P 的数据传输;区块链网络用 P2P 广播 + 中心化的 RPC 服务节点。


二、进程通信

2.1 进程、Socket 与消息

  • 进程:运行在端系统上的程序
  • 同一主机内的进程用进程间通信(由 OS 管,不归网络课)
  • 不同主机上的进程通过交换报文通信

Socket 是进程与网络之间的门:

    发送进程                          接收进程
       │                                 ↑
   ┌───▼────┐  socket                ┌───┴────┐
   │  应用层  │  ←── 应用开发者控制 ──→   │  应用层  │
   ├────────┤                        ├────────┤
   │ 传输层  │  ←── OS 控制         →   │ 传输层  │
   │ 网络层  │                        │ 网络层  │
   │ 链路层  │                        │ 链路层  │
   └────────┘                        └────────┘
        └──────── 物理网络 ─────────────┘

Socket = 应用层与传输层之间的 API 边界。 应用开发者能控制 socket 之上的一切,以及传输层协议的选择(TCP 还是 UDP)和少量参数;socket 之下的行为由操作系统决定。

第 9 讲会亲手写 socket 代码。

2.2 进程寻址:为什么需要端口号

要把报文送到某个进程,需要两级标识:

  IP 地址(32 位 / 128 位)  →  找到哪台主机
  端口号(16 位,0–65535)    →  找到主机上的哪个进程

只有 IP 是不够的:一台服务器上同时跑着 Web、SSH、数据库,报文到了主机之后必须还能分辨给谁。

知名端口(well-known ports,0–1023)

端口 服务 端口 服务
20/21 FTP 143 IMAP
22 SSH 443 HTTPS
25 SMTP 853 DNS-over-TLS
53 DNS 3306 MySQL
80 HTTP 8080 HTTP 备用

(在类 Unix 系统上,绑定 1024 以下端口需要 root 权限——这是一条安全设计。)


三、应用需要什么样的传输服务

设计一个应用时,先问四个问题:

3.1 四类需求

1️⃣ 数据完整性(可靠性)

  • 文件传输、网页、金融交易:必须 100% 可靠,丢一个字节就错
  • 音频、视频:可容忍部分丢失,丢一帧画面比卡顿好

2️⃣ 吞吐量

  • 带宽敏感型(bandwidth-sensitive):多媒体应用需要最低带宽保证,低于阈值就不可用
  • 弹性型(elastic):邮件、文件传输、Web,有多少用多少

3️⃣ 时延

  • 交互式游戏、电话会议:要求 < 100 ms
  • 邮件:几分钟都没关系

4️⃣ 安全性

  • 加密、数据完整性、端点认证

3.2 常见应用的需求表

应用 数据丢失 吞吐量 时延敏感
文件传输 / 网页 不可容忍 弹性
电子邮件 不可容忍 弹性
实时音视频 可容忍 音频 5 kbps–1 Mbps · 视频 100 kbps–10 Mbps 是,百毫秒级
存储音视频 可容忍 同上 是,秒级
交互式游戏 可容忍 > 几 kbps 是,百毫秒级
即时通讯 不可容忍 弹性 是与否

3.3 互联网提供了什么

TCP UDP
可靠数据传输
流量控制
拥塞控制
面向连接 ✅ 需要握手
有序交付
时延保证
带宽保证
头部开销 20 字节 8 字节

⚠️ 注意最后两行:**TCP 和 UDP 都不提供时延和带宽保证。**互联网是 best-effort 的,没有任何传输层协议能凭空创造出确定性。

那么为什么还有人用 UDP?三个理由:

  1. 不想被拥塞控制限速(实时音视频宁可丢包也不降速)
  2. 不想为可靠性付出重传时延(游戏里迟到的位置信息不如丢掉)
  3. 需要自己控制一切(QUIC 在 UDP 上重新实现了可靠传输和拥塞控制,因为在内核 TCP 上无法快速迭代——第 6 讲)

3.4 应用选择表

应用 应用层协议 传输层
Web HTTP/1.1, HTTP/2 TCP
Web HTTP/3 UDP(QUIC)
邮件 SMTP, IMAP TCP
域名解析 DNS UDP(大响应回落 TCP)
流媒体 DASH(基于 HTTP) TCP
实时会议 RTP / SIP / WebRTC UDP
网络时间 NTP UDP

四、HTTP:Web 的应用层协议

4.1 基本概念

  • Web 页面由若干对象(object)组成:一个 HTML 基本文件 + 若干图片、CSS、JS
  • 每个对象由一个 URL 寻址
http://www.example.edu/dept/pic.gif
└─┬─┘  └──────┬──────┘└─────┬─────┘
 协议      主机名          路径
  • HTTP 使用 TCP(HTTP/1.1、HTTP/2),默认端口 80(HTTPS 为 443)
  • HTTP 是无状态的(stateless):服务器不保存关于客户机过去请求的任何信息

💡 **为什么设计成无状态?**因为保存状态很复杂:状态在崩溃后要恢复,客户端和服务器的状态可能不一致,并且状态占内存、限制可扩展性。无状态让服务器可以随意水平扩展——任何一台机器都能处理任何一个请求。 代价是:需要状态的时候(购物车、登录),必须用别的机制补上,这就是 Cookie(第 4.6 节)。

4.2 非持续连接 vs 持续连接

非持续连接(HTTP/1.0):每个对象用一条新的 TCP 连接。

一次对象获取的时间线:

客户机                             服务器
   │—— SYN ————————————————————→ │
   │ ←———————————— SYN/ACK ————— │   ← 1 个 RTT(TCP 握手)
   │—— ACK + HTTP GET —————————→ │
   │ ←—————————————— 文件 ——————— │   ← 1 个 RTT + 传输时间
   │
   总计:2 × RTT + 文件传输时间

这个 2 RTT 是必须记住的数字。

📌 推导一个网页的加载时间:一个 HTML 加 10 张图片,RTT = 100 ms,忽略传输时间。

方式 RTT 数 时间
非持续、串行 2 + 10×2 = 22 2200 ms
非持续、6 条并行连接 2 + ⌈10/6⌉×2 = 2+4 = 6 600 ms
持续连接、流水线 2 + 1 = 3 300 ms

持续连接(HTTP/1.1,默认):一条 TCP 连接上串行发送多个请求/响应,服务器发送完不立即关闭。

两个额外好处(常被忽略但很重要):

  1. 省下 TCP 握手的 RTT
  2. 保住了 TCP 的拥塞窗口。新连接要从慢启动重新开始(第 16 讲),窗口很小、速度很慢。复用连接意味着继承已经涨起来的窗口。

4.3 HTTP 请求报文

HTTP 报文是 ASCII 文本,人类可读——这是它最初能流行的重要原因(也是 Lab 1 里你能直接读懂抓包内容的原因)。

GET /index.html HTTP/1.1\r\n
Host: www.example.edu\r\n
User-Agent: Mozilla/5.0\r\n
Connection: keep-alive\r\n
Accept-Language: zh-CN,en\r\n
\r\n

结构:

请求行:  方法  URL  版本
首部行:  字段名: 值      (可以有很多行)
空行:    \r\n            ← 标志首部结束
实体体:  (POST/PUT 时携带的数据)

⚠️ Host: 首部是必需的(HTTP/1.1 起)。原因:一个 IP 可以承载成千上万个网站(虚拟主机),服务器靠 Host 判断你要访问哪一个。这也是 CDN 得以工作的前提之一。

4.4 HTTP 方法

方法 语义 幂等? 安全?
GET 获取资源 ✅ 不改变服务器状态
POST 提交数据、创建资源
PUT 用请求体整体替换目标资源
DELETE 删除资源
HEAD 同 GET 但只要首部不要实体
OPTIONS 查询支持的方法(CORS 预检用)
  • 安全(safe):不修改服务器状态
  • 幂等(idempotent):执行一次和执行 N 次效果相同

📌 为什么这个区分重要:网络会丢包和重传。幂等的请求可以安全地自动重试,非幂等的不能——这就是浏览器刷新 POST 页面时会弹「确认重新提交表单?」的原因。

4.5 HTTP 状态码

含义 常见码
1xx 信息 101 Switching Protocols(WebSocket 升级)
2xx 成功 200 OK、201 Created、204 No Content
3xx 重定向 301 永久移动、302 临时、304 Not Modified
4xx 客户端错误 400 Bad Request、401 未认证、403 Forbidden404 Not Found、429 Too Many Requests
5xx 服务器错误 500 Internal Error、502 Bad Gateway、503 Service Unavailable、504 Gateway Timeout

⚠️ 考试易错点

  • 401 vs 403:401 = 「我不知道你是谁,请认证」;403 = 「我知道你是谁,但你没权限」
  • 301 vs 302:301 会被浏览器和搜索引擎永久缓存,改错了很难撤回
  • 502 vs 504:502 = 上游返回了无效响应;504 = 上游超时没回

4.6 Cookie:给无状态协议加上状态

四个组成部分:

  1. HTTP 响应报文中的 Set-Cookie: 首部
  2. HTTP 请求报文中的 Cookie: 首部
  3. 用户端浏览器里保存的 cookie 文件
  4. 网站后端的数据库
客户机                                  服务器
   │—— 首次 GET ————————————————————→ │
   │                                   │ 创建 ID 1678
   │ ←—— 200 OK                        │ 写入数据库
   │     Set-Cookie: 1678 ————————————│
   │  [浏览器存下 1678]                 │
   │                                   │
   │—— GET  Cookie: 1678 ————————————→ │ 查库 → 「这是上次那个人」
   │ ←—— 个性化响应 ——————————————————│

用途:会话管理(登录态)、购物车、个性化推荐、追踪用户

⚠️ 隐私问题:第三方 cookie 让广告商能跨站点拼接用户的浏览轨迹。这是 GDPR、cookie 同意横幅、以及浏览器逐步淘汰第三方 cookie 的直接原因。

安全属性(面试常问):

  • HttpOnly:禁止 JavaScript 读取,缓解 XSS 窃取
  • Secure:只在 HTTPS 上发送
  • SameSite=Strict/Lax:限制跨站携带,缓解 CSRF

4.7 条件 GET:不重复传输没变的东西

目标:如果缓存里的副本还是最新的,就不要再传一遍。

客户机(有缓存副本,上次带回 Last-Modified: Wed, 9 Sep 2026 09:23:24)

   │—— GET /pic.gif
   │   If-Modified-Since: Wed, 9 Sep 2026 09:23:24 ——→ │
   │                                                    │ 对比修改时间
   │ ←—— HTTP/1.1 304 Not Modified                     │
   │     (空实体体!)——————————————————————————————│

304 响应不包含对象本身,只有几百字节的头部。对于没变的大图片,节省可以是 99.9%。

另一组更现代的机制是 ETag / If-None-Match:用内容哈希代替时间戳,避免了时钟精度和「内容没变但时间戳变了」的问题。

📌 第 6 讲讲 Web 缓存与 CDN 时,这个机制是基础。


五、例题(Worked Example)

题目:某网页包含 1 个 HTML 基本文件(100 KB)和 8 个小对象(各 10 KB)。RTT = 80 ms,链路速率 10 Mbps,忽略 DNS 查询与排队时延,TCP 慢启动的影响也忽略。

(a) 用 HTTP/1.0 非持续连接、串行获取,总时间? (b) 改为 6 条并行的非持续连接,总时间? (c) 用 HTTP/1.1 持续连接 + 流水线,总时间?

解答

先算传输时间:

HTML:   100 KB × 8 = 800,000 bit ÷ 10 Mbps = 80 ms
每个小对象: 10 KB × 8 = 80,000 bit ÷ 10 Mbps = 8 ms

(a) 非持续串行:

HTML:     2×RTT + 80 ms  = 160 + 80 = 240 ms
每个对象:  2×RTT + 8 ms   = 160 + 8  = 168 ms
8 个对象:  8 × 168 = 1344 ms
总计:     240 + 1344 = 1584 ms

(b) 6 条并行(两批:6 个 + 2 个):

HTML:  240 ms
第一批 6 个并行: 168 ms(并行不叠加 RTT,但带宽被 6 条分摊,
                        此处对象很小,取近似 168 ms)
第二批 2 个并行: 168 ms
总计: 240 + 168 + 168 = 576 ms

(c) 持续 + 流水线:

TCP 握手:      1 × RTT = 80 ms
请求 HTML:     1 × RTT + 80 ms 传输 = 160 ms
8 个请求一起发出,响应连续到达:
               1 × RTT + 8×8 ms = 80 + 64 = 144 ms
总计: 80 + 160 + 144 = 384 ms

结论:1584 ms → 576 ms → 384 ms。持续连接的收益主要来自消除了重复握手的 RTT,而这个收益在 RTT 越大(移动网络、跨洲访问)时越显著。


六、随堂自测

  1. HTTP 为什么设计成无状态的?无状态带来了什么代价,用什么补上?
  2. Host: 首部为什么在 HTTP/1.1 中变成必需的?
  3. 一个网页有 20 个对象,RTT = 200 ms。持续连接(流水线)相比非持续串行,能省多少时间(忽略传输时间)?
  4. 401 和 403 的区别是什么?502 和 504 呢?
  5. 为什么浏览器刷新 POST 结果页会弹出确认框,而刷新 GET 页面不会?
  6. 条件 GET 的 304 响应节省了什么?没节省什么?

七、本讲要点回顾

  • 应用体系结构:C/S 服务能力受限于服务器P2P 自扩展但管理复杂
  • Socket 是应用层与传输层的 API 边界;寻址需要 IP + 端口两级。
  • 应用需求四维:可靠性、吞吐量、时延、安全
  • **TCP 和 UDP 都不提供时延或带宽保证。**用 UDP 是为了绕开拥塞控制、避免重传时延、或自己掌控一切。
  • HTTP 无状态;持续连接省的是 RTT 和已长大的拥塞窗口
  • 非持续连接每个对象 2 RTT
  • 幂等性决定了请求能否被安全重试。
  • Cookie 是给无状态协议外挂状态的机制,也是追踪的技术基础。
  • 条件 GET / 304 只省实体体,不省 RTT。

八、自测答案

1. 无状态让服务器可以任意水平扩展——任何一台服务器都能处理任何请求,无需在集群间同步会话状态,也无需在崩溃后恢复状态。代价是需要状态的应用(登录、购物车)必须自己实现,补救机制是 Cookie(客户端存标识,服务端存数据)以及后来的 token / session store。

2. 因为一个 IP 地址上可以托管成千上万个虚拟主机(尤其是共享主机和 CDN 边缘节点)。HTTP/1.0 的请求行只有路径,服务器无法判断客户想访问哪个站点。Host: 提供了这一信息。(HTTPS 场景下对应的机制是 TLS 的 SNI 扩展,第 28 讲。)

3. 非持续串行:(1 + 20) × 2 RTT = 42 RTT = 8400 ms。持续流水线:1 RTT(握手)+ 1 RTT(HTML)+ 1 RTT(20 个对象一批)= 3 RTT = 600 ms。节省 7800 ms,约 93%。

4. 401 Unauthorized:缺少或无效的认证凭据,响应应带 WWW-Authenticate,客户端补上凭据后可能成功。403 Forbidden:已认证但无权限,重试凭据没用。502 Bad Gateway:代理/网关从上游收到了无效响应。504 Gateway Timeout:代理/网关等待上游超时未收到响应

5. 因为 GET 是幂等且安全的——重复执行不改变服务器状态,浏览器可以放心重发。POST 不是幂等的——重发可能造成重复下单、重复扣款,所以浏览器必须让用户确认。(正确的工程做法是 POST/Redirect/GET 模式:POST 后返回 303 重定向到一个 GET 页面。)

6. 节省了实体体的传输(可能是几百 KB 到几 MB)。没有节省的是一个完整的 RTT——你仍然必须发出请求、等待响应才知道「没变」。这正是 HTTP 缓存要引入 Cache-Control: max-age 的原因:在有效期内连请求都不发,把那个 RTT 也省掉。第 6 讲展开。