本文对《TCP/IP 指南》第三部分第三章(主机配置协议)进行中文整理,力求通俗易懂。

第一章:为什么需要主机配置协议?

网络配置的痛点

每台设备接入网络前,都需要被告知一些基本信息:

一台主机需要知道的参数:
  - 自己的 IP 地址(最重要,必须唯一)
  - 子网掩码
  - 默认路由器(网关)的地址
  - DNS 服务器地址
  - IP 数据报的 TTL 值
  - 本地网络的 MTU(最大传输单元)
  - ……等几十个参数

手动配置的四大困境


困境 说明
远程配置 管理员不可能跑到每台机器前手动配置
移动设备 笔记本换了网络就要换 IP,手动改很麻烦
无存储设备 有些设备根本没有硬盘,无法保存配置
地址共享 IP 地址不够用,必须动态分配重复利用

主机配置协议的作用:让主机开机后自动获取所有需要的配置信息,无需人工干预。

第二章:BOOTP——第一代自动配置协议

历史背景与设计目标

BOOTP 的前辈是 RARP(逆地址解析协议),但 RARP 有很多缺陷:

RARP 的问题 BOOTP 的改进
只能提供 IP 地址 能提供更多配置参数
依赖硬件层广播,不能跨网络 使用 UDP,支持跨网络(通过中继代理)
每个网络都需要单独的 RARP 服务器 一台 BOOTP 服务器可以服务多个网络

BOOTP 的两阶段启动

阶段一(BOOTP 负责):
  主机开机 → 发送 BOOTP 请求 → 获得 IP 地址和配置参数
阶段二(TFTP 负责):
  使用 IP 地址 → 通过 TFTP 下载操作系统软件 → 完成启动

BOOTP 的工作原理

BOOTP 使用 UDP 传输,端口分配:

  • 服务器监听端口:67
  • 客户端监听端口:68
    为什么需要两个专用端口?因为 BOOTP 服务器可能需要广播回复,如果用客户端的临时端口,其他设备可能会误收到这个广播。

BOOTP 详细操作步骤

步骤1:客户端创建 BOOTREQUEST 消息
  - Op 字段设为 1(请求)
  - 把自己的硬件地址(MAC 地址)填入 CHAddr 字段
  - 生成随机事务 ID 填入 XID 字段
  - CIAddr 字段:如果知道自己的 IP 就填入,否则填 0
步骤2:客户端广播请求
  - 发送到地址 255.255.255.255(广播地址)
步骤3:服务器收到请求
  - 服务器在 UDP 67 端口监听
  - 根据客户端的 CHAddr 查询表格,找到对应的 IP 地址
步骤4:服务器创建 BOOTREPLY 消息
  - Op 字段改为 2(回复)
  - 把查到的 IP 地址填入 YIAddr(你的 IP 地址)字段
  - 填入服务器自己的 IP 和名称
步骤5:服务器发送回复
  方式取决于 B 标志和 CIAddr 字段:
  - B=1:广播回复
  - CIAddr 非零:单播到客户端已知的 IP
  - 否则:可以用 ARP 或广播
步骤6:客户端处理回复
  - 保存获得的 IP 地址和其他参数
步骤7:客户端进入第二阶段
  - 用 TFTP 下载操作系统软件

BOOTP 消息格式


字段名 大小(字节) 说明
Op 1 操作码:1=请求,2=回复
HType 1 硬件类型(1=以太网)
HLen 1 硬件地址长度(以太网=6)
Hops 1 跳数,由中继代理使用
XID 4 事务 ID,用于匹配请求和回复
Secs 2 客户端启动后经过的秒数
Flags 2 标志位,含广播标志 B
CIAddr 4 客户端 IP 地址(客户端自填)
YIAddr 4 服务器分配给客户端的 IP
SIAddr 4 服务器 IP 地址
GIAddr 4 中继代理 IP 地址(网关 IP)
CHAddr 16 客户端硬件地址(MAC 地址)
SName 64 服务器名称
File 128 启动文件名
Vend 64 厂商特定区域(后来用于扩展信息)

丢包重传机制

BOOTP 使用 UDP(不可靠),所以客户端需要自己处理重传。
指数退避算法(防止大量主机同时重传把网络搞崩):
第 n 次重传等待时间 = 2 n − 1 × 4  秒 ± 随机抖动 \text{第}n\text{次重传等待时间} = 2^{n-1} \times 4 \text{ 秒} \pm \text{随机抖动} n次重传等待时间=2n1×4 ±随机抖动
具体:

  • 第1次重传:随机等待 0~4 秒
  • 第2次重传:随机等待 0~8 秒
  • 第3次重传:随机等待 0~16 秒
  • ……以此类推
    就像以太网的碰撞退避:如果大家都同时重发,就再等一会儿,但等的时间是随机的,避免再次碰撞。

BOOTP 厂商扩展信息(Vendor Extensions)

Vend 字段原本供厂商自定义,后来 RFC 1048 规定了一种通用的使用方法:

Vend 字段结构:
  [魔法 Cookie: 99.130.83.99][扩展字段1][扩展字段2]...[结束标记:255]
   ↑ 4字节,标识这是通用扩展模式

每个扩展字段使用 TLV 编码(类型-长度-值):

一个扩展字段:
  [Code: 1字节][Len: 1字节][Data: Len字节]
   ↑类型         ↑后面数据的字节数  ↑实际数据

特殊值:

  • Code=0:填充字节(对齐用)
  • Code=255:结束标记

BOOTP 中继代理

问题:客户端不知道服务器在哪,只能广播。但路由器默认不转发广播,所以客户端和服务器必须在同一个网络。
解决方案:BOOTP 中继代理(Relay Agent)

没有中继代理时(服务器和客户端必须在同一网络):
  客户端 → 广播 → 服务器(只有同网络的服务器能收到)
有中继代理时(服务器可以在任何地方):
  客户端 → 广播 → 中继代理(在本地网络)
                      ↓
              单播转发给远端服务器
                      ↓
              服务器单播回复给中继代理
                      ↓
              中继代理转发回复给客户端

中继代理的工作逻辑:

  1. 检查 Hops 字段:如果超过 16,丢弃(防止无限循环);否则递增
  2. 把自己的 IP 地址填入 GIAddr 字段(第一个中继代理填写)
  3. 把请求转发给服务器
    服务器看到 GIAddr 非零,就知道这是中继过来的,把回复单播发给中继代理的地址。

第三章:DHCP 概述与地址分配

DHCP 是 BOOTP 的升级版

BOOTP 的最大问题:只支持静态地址分配(硬件地址固定对应某个 IP)。
现代网络的需求:

  • 笔记本电脑频繁切换网络
  • IP 地址数量有限,需要重复利用
  • 设备数量可能超过可用 IP 数量
    **DHCP(动态主机配置协议)**解决了这些问题,它建立在 BOOTP 的基础上,添加了最重要的新功能:动态地址分配

三种地址分配方式


方式 说明 适用场景
手动分配 管理员预先指定某设备用某 IP,DHCP 只是传达 服务器、路由器等需要固定 IP 的设备
自动分配 DHCP 从地址池中选一个永久分配给设备 实际很少用(不如直接手动分配)
动态分配 DHCP 从地址池中租一个 IP 给设备,有时间限制 普通客户端,这是 DHCP 最重要的功能

动态分配的好处

  • 自动化:无需管理员干预
  • 集中管理:所有 IP 信息都在服务器上
  • 地址复用:设备不用时,地址自动归还
  • 支持移动设备:任何设备都能获取地址
  • 避免冲突:统一分配不会重复

租约(Lease)——DHCP 最核心的概念

在 DHCP 里,设备不"拥有"IP 地址,而是"租用"IP 地址。
用租房来类比:

传统 IP 分配:
  → 你购买了这套房子(永久拥有这个 IP)
DHCP 动态分配:
  → 你租了这套房子,租期 X 天(临时使用这个 IP)
  → 租期到了需要续租,否则被收回
  → 你可以提前退租(告诉服务器不需要了)

租约时长策略

租约时间的长短是一个权衡:
长租约 ⇒ 地址稳定 短租约 ⇒ 地址利用率高 \text{长租约} \Rightarrow \text{地址稳定} \quad \text{短租约} \Rightarrow \text{地址利用率高} 长租约地址稳定短租约地址利用率高

租约时长 适用场景
1小时以内 地址紧缺,设备频繁上下线
1天 访客设备,每天重新申请
3天 Microsoft DHCP 服务器的默认值
1周 适中的折中方案
3个月 稳定环境,设备偶尔长期离线(如大学暑假)
1年 近似于永久分配

注意:默认情况下,租约过了一半时间,客户端就会开始尝试续租。

地址池与范围(Scope)

管理员配置一段可供分配的 IP 范围:

示例:分配 111.14.56.20 到 111.14.56.254
                           ↑
             为什么不从 .1 开始?
             .1 到 .19 手动分配给服务器和路由器

多服务器的地址范围划分

服务器A 和 服务器B 共用同一段地址空间:
  总范围:111.14.56.1 ~ 111.14.56.254
  保留地址(两个服务器都排除):.1 ~ .19
  服务器A 负责:.20 ~ .199
  服务器B 负责:.200 ~ .254
好处:一台服务器宕机,管理员可以快速调整让另一台接管全部地址

第四章:DHCP 详细操作

客户端与服务器的职责

DHCP 服务器的职责

  • 存储和管理 IP 地址池
  • 存储和管理配置参数
  • 管理租约(记录哪个设备用哪个 IP 用到什么时候)
  • 响应客户端的各种请求
  • 提供管理界面给管理员
    DHCP 客户端的职责
  • 主动发起通信(服务器不会主动联系客户端)
  • 管理自己的配置参数
  • 在合适的时间续租或终止租约
  • 检测消息丢失并重传

DHCP 客户端状态机

DHCP 客户端在不同状态之间切换:

开机/租约过期

发送 DHCPDISCOVER

收到 OFFER,发送 DHCPREQUEST

收到 DHCPACK 且地址未被占用

收到 DHCPNAK 或地址被占用

T1 计时器到期

用户主动释放 DHCPRELEASE

收到 DHCPACK(续租成功)

T2 计时器到期

收到 DHCPNAK

任意服务器 DHCPACK(重绑定成功)

收到 DHCPNAK 或租约到期

发送 DHCPREQUEST(重用旧地址)

收到 DHCPACK 且地址未被占用

收到 DHCPNAK 或地址被占用

INIT

SELECTING

REQUESTING

BOUND

RENEWING

REBINDING

INIT_REBOOT

REBOOTING


状态 说明
INIT 初始状态,没有 IP 地址
SELECTING 等待服务器的 OFFER
REQUESTING 已选定服务器,等待确认
INIT-REBOOT 重启后有旧租约,尝试恢复
REBOOTING 等待旧租约确认
BOUND 正常工作状态,有有效的 IP 地址
RENEWING T1 到期,向原服务器申请续租
REBINDING 续租失败,T2 到期,向任意服务器申请

两个关键计时器

每次获得租约后,客户端启动两个计时器:
T 1 = 50 % × 租约时长 (续租计时器) T1 = 50\% \times \text{租约时长} \quad \text{(续租计时器)} T1=50%×租约时长(续租计时器)
T 2 = 87.5 % × 租约时长 = 7 8 × 租约时长 (重绑定计时器) T2 = 87.5\% \times \text{租约时长} = \frac{7}{8} \times \text{租约时长} \quad \text{(重绑定计时器)} T2=87.5%×租约时长=87×租约时长(重绑定计时器)

租约时间轴示例(假设租约8天):
  |----|----|----|----|----|----|----|----|
  0    1    2    3    4    5    6    7    8天
              ↑T1到期(第4天)     ↑T2到期(第7天)         ↑租约到期(第8天)
              开始续租             续租失败,向任意服务器重绑定

初始租约分配流程(14步详解)

步骤1:客户端创建 DHCPDISCOVER 消息
  - 填入自己的 MAC 地址(CHAddr)
  - 生成随机事务 ID(XID)
  - 可选:请求特定 IP、特定租约时长、特定参数列表
步骤2:客户端广播 DHCPDISCOVER
  - 发到 255.255.255.255
  - 进入 SELECTING 状态
步骤3:各服务器收到并处理
  - 查询数据库,决定是否响应
步骤4:服务器创建 DHCPOFFER 消息
  - 填入准备分配的 IP(YIAddr)
  - 填入租约时长
  - 填入各种配置参数(通过 DHCP Options)
步骤5:服务器探测地址(可选但推荐)
  - 用 ICMP ping 检测该 IP 是否已被使用
  - 预留该地址
步骤6:服务器发送 DHCPOFFER
步骤7:客户端收集所有 OFFER
  - 可以等一会儿收集多个 OFFER
  - 选择最好的(如租约最长的)
  - 如果没收到任何 OFFER,重传 DHCPDISCOVER
步骤8:客户端创建 DHCPREQUEST
  - 包含选定服务器的标识(Server Identifier 选项)
  - 包含请求的 IP 地址(Requested IP Address 选项)
步骤9:客户端广播 DHCPREQUEST
  - 广播是为了告知所有服务器(包括未被选中的)
  - 进入 REQUESTING 状态
步骤10:服务器处理 DHCPREQUEST
  - 未被选中的服务器:等待一段时间再释放预留地址
    (因为客户端可能还会回来选它作为备用)
步骤11:被选中的服务器发送 DHCPACK 或 DHCPNAK
  - DHCPACK:确认租约,包含所有配置参数
  - DHCPNAK:拒绝(如该地址已被其他人用了)
步骤12:客户端处理回复
  - 收到 DHCPACK:继续下一步
  - 收到 DHCPNAK:回到步骤1重新来
步骤13:客户端检查 IP 是否被占用(ARP 探测)
  - 发送 ARP 请求:"有没有人用这个 IP?"
  - 有人回复:发送 DHCPDECLINE 告知服务器,回到步骤1
  - 无人回复:说明地址可用
步骤14:客户端完成分配
  - 设置 IP 地址和所有参数
  - 启动 T1 和 T2 计时器
  - 进入 BOUND 状态

租约续租与重绑定流程

续租(Renewal)

T1 到期 → 客户端单播发送 DHCPREQUEST 给原服务器
  ↓
服务器回复 DHCPACK → 续租成功,重置 T1 和 T2
  ↓
服务器回复 DHCPNAK → 续租失败,回到 INIT 状态
  ↓
服务器无回复 → 保持 RENEWING 状态,继续重试,直到 T2 到期

重绑定(Rebinding)

T2 到期(原服务器无响应)→ 客户端广播 DHCPREQUEST
  ↓
任意服务器回复 DHCPACK → 绑定到新服务器,续租成功
  ↓
服务器回复 DHCPNAK → 回到 INIT 状态
  ↓
租约到期仍无回复 → 失去 IP 地址,回到 INIT 状态

为什么两步?先单播(省流量,通常就够了),再广播(兜底,让其他服务器接管)。

重启后的租约恢复(Reallocation)

客户端重启后发现还有有效租约,不需要完整的分配过程:

完整分配(4步网络交互):  DISCOVER → OFFER → REQUEST → ACK
租约恢复(2步网络交互):              REQUEST → ACK

主动释放租约

客户端发送 DHCPRELEASE 消息给原服务器:

场景:
  - 设备要换到另一个网络
  - 管理员要重置 IP 分配
  - 修复某些 DHCP 问题
客户端单播发送 DHCPRELEASE → 服务器记录该租约已结束
(服务器不需要回复;释放是"礼貌"行为,不是强制的)

已有地址的设备请求参数

有时候设备已经有 IP(手动配置的),但还想从 DHCP 服务器获取其他配置参数:

设备(已有 IP)→ 发送 DHCPINFORM(单播或广播)
                          ↓
                DHCP 服务器 → 回复 DHCPACK(包含请求的参数)

常见场景:DHCP 服务器本身用 DHCP 获取其他参数(服务器对另一个服务器发 DHCPINFORM)。

第五章:DHCP 消息格式与选项

DHCP 消息格式

DHCP 消息格式和 BOOTP 基本一样(为了向后兼容),主要差别是:

  • Vend 字段改名为 Options 字段,且长度变为可变长度(最少 312 字节)
  • 具体的 DHCP 消息类型通过 Options 里的 DHCP Message Type 选项指定

字段名 大小(字节) 与 BOOTP 的差异
Op 1 相同:1=请求,2=回复
HType 1 相同
HLen 1 相同
Hops 1 相同,供中继代理使用
XID 4 相同
Secs 2 DHCP 中定义为:客户端尝试获取/续租以来的秒数
Flags 2 相同,含广播标志 B
CIAddr 4 DHCP 中仅在 BOUND/RENEWING/REBINDING 状态下填入
YIAddr 4 相同,服务器分配给客户端的 IP
SIAddr 4 DHCP 中是下一步引导过程中客户端应使用的服务器 IP
GIAddr 4 相同,中继代理 IP
CHAddr 16 相同
SName 64 可被 Option Overload 征用来承载 Options
File 128 可被 Option Overload 征用来承载 Options
Options 可变 BOOTP 中是固定 64 字节的 Vend 字段

DHCP 的 8 种消息类型


选项53值 消息类型 谁发 用途
1 DHCPDISCOVER 客户端 寻找服务器,请求租约
2 DHCPOFFER 服务器 提供一个 IP 地址和租约条款
3 DHCPREQUEST 客户端 接受某个服务器的 OFFER,或续租/重绑定
4 DHCPDECLINE 客户端 告知服务器提供的地址已被占用
5 DHCPACK 服务器 确认分配/续租,包含最终配置参数
6 DHCPNAK 服务器 拒绝或撤回之前的 OFFER
7 DHCPRELEASE 客户端 主动释放租约
8 DHCPINFORM 客户端 已有 IP,只请求其他配置参数

DHCP Options 结构

Options 字段格式:

[魔法 Cookie: 99.130.83.99(4字节)]
[Option1: Code(1B) + Len(1B) + Data(Len bytes)]
[Option2: Code(1B) + Len(1B) + Data(Len bytes)]
......
[End: 255(1字节,无 Len 和 Data)]

Option Overload(选项超载)
当 Options 字段放不下所有选项时,可以把 SName(64字节)和 File(128字节)也用来存放选项。
通过 Option 52(Option Overload 选项)告知接收方如何解析这两个字段。

重要 DHCP 选项速查

RFC 1497 厂商扩展(最常用的基础选项)

Code 含义
0 填充字节(对齐用)
1 子网掩码
2 时区偏移(相对 UTC 的秒数)
3 路由器(默认网关)地址列表
6 DNS 服务器地址列表
12 主机名
15 DNS 域名
255 结束标记

DHCP 专属扩展选项

Code 含义
50 客户端请求的 IP 地址
51 IP 地址租约时长(秒)
52 Option Overload 标志
53 DHCP 消息类型(最重要!)
54 服务器标识符
55 客户端请求参数列表
57 客户端/服务器能接受的最大消息长度
58 T1 续租计时器值
59 T2 重绑定计时器值
61 客户端标识符(替代默认的 MAC+子网 标识)

第六章:DHCP 实现与特性

APIPA——服务器找不到时的自救机制

问题:DHCP 服务器宕机了,客户端怎么办?
解决方案:APIPA(Automatic Private IP Addressing,自动私有 IP 地址分配)

APIPA 工作流程:
  1. DHCP 获取失败(多次重试无响应)
  2. 在 169.254.0.1 ~ 169.254.255.254 中随机选一个地址
  3. 用 ARP 探测该地址是否被占用
  4. 有冲突 → 重新选地址,回到步骤2
  5. 无冲突 → 用这个地址临时工作
  6. 每隔5分钟继续尝试联系 DHCP 服务器
  7. 找到服务器后,换回 DHCP 分配的正式地址

地址块 169.254.0.0/16 是专门为自动配置保留的:
169.254.0.1 ∼ 169.254.255.254 (共 65534 个可用地址) 169.254.0.1 \sim 169.254.255.254 \quad \text{(共 65534 个可用地址)} 169.254.0.1169.254.255.254(共 65534 个可用地址)
APIPA 的局限性

  • 169.254.x.x 是私有地址,不能访问互联网
  • 只能和同一网段内的其他 APIPA 设备通信
  • 不提供默认网关等其他配置参数

服务器冲突检测

问题:如果两台 DHCP 服务器都从同一地址池分配,可能给两个客户端分配同一个 IP。
解决方案:DHCP 服务器冲突检测

服务器要给客户端分配地址之前:
  1. 先 ping 这个 IP 地址
  2. 有回应 → 说明已被占用,换一个地址
  3. 无回应 → 地址可用,放心分配
多台服务器都这样做 → 可以安全地使用重叠地址范围

DHCP 与 BOOTP 的互操作

DHCP 服务器如何识别对方是 BOOTP 还是 DHCP 客户端?
→ 看消息中是否有 DHCP Message Type(选项53)

  • 有:是 DHCP 客户端
  • 没有:是 BOOTP 客户端

组合 处理方式
DHCP 服务器 + BOOTP 客户端 服务器用手动或自动分配(不能动态分配),不发送 DHCP 专属选项
DHCP 客户端 + BOOTP 服务器 客户端把 BOOTP 回复当作无限期租约处理

DHCP 安全问题

DHCP 协议本身没有任何安全机制,主要威胁:

威胁 说明 应对
流氓 DHCP 服务器 攻击者架设假服务器,分配错误配置(如错误网关) 物理网络访问控制、802.1X 认证
流氓 DHCP 客户端 伪造大量 DHCP 请求耗尽地址池(DHCP 饥饿攻击) 交换机端口安全(DHCP Snooping)

RFC 3118 定义了 DHCP 认证选项,但由于兼容性问题,实际部署很少。

第七章:DHCPv6——为 IPv6 设计的 DHCP

为什么需要 DHCPv6?

IPv6 地址从 32 位变成了 128 位,地址格式、分配方式都完全不同,需要重写 DHCP。

IPv6 的两种自动配置方式

IPv6 主机配置方式:
  ├── 无状态自动配置(Stateless)
  │     - 主机自己计算地址(基于接口 MAC 地址 + 路由器前缀)
  │     - 不需要服务器
  │     - RFC 2462 定义
  │
  └── 有状态自动配置(Stateful)
        - 由 DHCPv6 服务器分配地址和参数
        - 和 DHCPv4 类似,但协议全部重写

DHCPv6 的主要变化


特性 DHCPv4 DHCPv6
基础 基于 BOOTP 完全重写
传输 UDP 67/68 UDP 546/547(新端口)
发现机制 广播 链路本地多播
消息格式 BOOTP 格式扩展 全新格式
兼容性 兼容 BOOTP 不兼容 DHCPv4

DHCPv6 基本消息交换

四步交换(获取新地址)

客户端 → 多播 Solicit(寻找服务器)
        ↓
服务器 → Advertise(我可以服务你)
        ↓
客户端 → Request(我选择你,确认地址)
        ↓
服务器 → Reply(确认,给你地址和参数)

两步交换(只获取参数,已有地址)

客户端 → 多播 Information-Request(只要参数)
        ↓
服务器 → Reply(给你参数)

第八章:协议演进总结

三代主机配置协议的对比:

RARP(1984年)
  - 功能:只能提供 IP 地址
  - 依赖:同网络内必须有 RARP 服务器
  - 问题:不能跨网络,功能太少
     ↓ 升级
BOOTP(1985年)
  - 功能:IP 地址 + 更多参数 + 启动文件名
  - 依赖:UDP(高层协议,可跨网络)
  - 改进:中继代理支持跨网络
  - 问题:只支持静态地址映射
     ↓ 升级
DHCP(1993年)
  - 功能:动态 IP 分配 + 完整配置参数
  - 依赖:基于 BOOTP,向后兼容
  - 改进:租约机制、地址池管理、APIPA
  - 现状:现代网络的标准配置协议

关键数字速记
T 1 = 50 % × 租约时长 T1 = 50\% \times \text{租约时长} T1=50%×租约时长
T 2 = 87.5 % = 7 8 × 租约时长 T2 = 87.5\% = \frac{7}{8} \times \text{租约时长} T2=87.5%=87×租约时长
BOOTP 服务器端口 = 67 , 客户端端口 = 68 \text{BOOTP 服务器端口} = 67, \quad \text{客户端端口} = 68 BOOTP 服务器端口=67,客户端端口=68
APIPA 地址范围 = 169.254.0.1 ∼ 169.254.255.254 \text{APIPA 地址范围} = 169.254.0.1 \sim 169.254.255.254 APIPA 地址范围=169.254.0.1169.254.255.254
DHCP 魔法 Cookie = 99.130.83.99 \text{DHCP 魔法 Cookie} = 99.130.83.99 DHCP 魔法 Cookie=99.130.83.99

TCP/IP 网络管理框架与 SNMP 协议详解

目录

  1. 为什么需要网络管理?
  2. SNMP 是什么?名字的两层含义
  3. SNMP 的整体架构:谁管谁?
  4. SNMP 框架的四大组件
  5. 管理信息的描述:SMI 和 MIB
  6. MIB 对象的命名体系
  7. SNMP 协议操作详解
  8. SNMP 消息格式详解
  9. SNMP 的安全问题
  10. SNMP 版本演化史
  11. RMON:远程网络监控

1. 为什么需要网络管理?

形象比喻

想象一个棒球裁判——他做得越好,你越感觉不到他的存在。网络管理员也一样:网络顺畅运行时,用户忘了管理员的存在;一出问题,所有人都找他

小网络 vs 大网络的管理方式

以前网络很小,管理员可以:

  • 直接走到那台电脑旁边操作
  • 用简单的底层链路协议检查设备
    但现代网络:
  • 设备遍布全球各地
  • 使用不同的底层技术(光纤、WiFi、以太网……)
  • 唯一的共同点:都跑 TCP/IP 协议
    所以,利用 TCP/IP 网络本身来传递"管理信息",是最合理的方案。这就是 SNMP 框架诞生的背景。

SNMP 的历史起源

1980 年代,出现了三个竞争者:

方案 全称 RFC
HEMS/HEMP 高层实体管理系统/协议 RFC 1021~1024
SGMP 简单网关监控协议 RFC 1028
CMIP 公共管理信息协议(OSI 体系)

1988 年,IETF 发布 RFC 1052,决定以 SGMP 为基础,开发新标准——SNMP(简单网络管理协议)。

2. SNMP 是什么?名字的两层含义

"SNMP"这个词有两层意思,经常让人混淆:

SNMP(广义)= 互联网标准管理框架(Internet Standard Management Framework)
               包含:SMI + MIB + SNMP协议 + 安全管理
SNMP(狭义)= 具体的通信协议
               只负责:在设备之间传递管理信息

本文用"SNMP 框架"指广义,用"SNMP 协议"指狭义。

"Simple"为什么说简单?

SNMP 跟更复杂的协议(比如 CMIP)相比才算"简单"。它的设计目标是:

  • 提供一种通用方式描述任意设备的管理信息
  • 将"定义信息"和"传输信息"解耦,互不干扰
  • 协议操作本身只有几个,容易理解
  • 厂商实现起来相对简单

3. SNMP 的整体架构:谁管谁?

两类设备

SNMP 网络中有两种角色:

被管节点 (Managed Node)
  - 就是普通的网络设备(路由器、交换机、服务器……)
  - 安装了 SNMP 代理软件
  - 被动响应管理站的请求
网络管理站 (NMS, Network Management Station)
  - 专门运行管理软件的设备
  - 主动向被管节点发请求、收数据
  - 是整个管理系统的"大脑"

每台设备上的软件组件

被管节点上运行:

SNMP 代理 (Agent)
  - 实现 SNMP 协议
  - 响应 NMS 的请求
  - 主动发送 Trap 通知(出问题时)
MIB(管理信息库)
  - 存储该设备所有可管理的变量
  - 比如:IP 地址、接口状态、错误计数……

NMS 上运行:

SNMP 管理器 (Manager)
  - 实现 SNMP 协议
  - 向各代理发请求,收集信息
SNMP 应用程序
  - 提供图形界面或报告
  - 让人类管理员能看懂数据

架构示意图

                    +-----------------------+
                    |         NMS           |
                    |  +----------------+  |
                    |  | SNMP 管理器    |  |
                    |  +----------------+  |
                    |  | SNMP 应用程序  |  |
                    |  +----------------+  |
                    +-----------+-----------+
                                |
              SNMP 消息(通过 TCP/IP 网络传输)
           ________________________|________________________
           |                       |                       |
+----------+--------+   +----------+--------+   +----------+--------+
|    被管节点 A      |   |    被管节点 B      |   |    被管节点 C      |
|  +-------------+  |   |  +-------------+  |   |  +-------------+  |
|  | SNMP 代理   |  |   |  | SNMP 代理   |  |   |  | SNMP 代理   |  |
|  +-------------+  |   |  +-------------+  |   |  +-------------+  |
|  |    MIB      |  |   |  |    MIB      |  |   |  |    MIB      |  |
|  +-------------+  |   |  +-------------+  |   |  +-------------+  |
+-------------------+   +-------------------+   +-------------------+

4. SNMP 框架的四大组件

互联网标准管理框架
Internet Standard Management Framework

SMI
管理信息结构

MIB
管理信息库

SNMP 协议
实际通信协议

安全与管理
Security & Administration

定义:如何描述对象

存储:各设备的管理变量

传输:在代理与管理站之间移动数据

认证、加密、访问控制

组件关系

把三个核心组件的关系理解成三层抽象:

第三层:SNMP 协议
        负责"怎么传"——收发消息、请求响应
第二层:MIB
        负责"传什么"——定义设备有哪些可管理的变量
第一层:SMI
        负责"怎么定义"——规定 MIB 中的变量用什么格式描述

5. 管理信息的描述:SMI 和 MIB

5.1 信息导向 vs 命令导向

传统管理协议是"命令导向"的,比如:

  • 发命令:读取设备运行小时数
  • 发命令:切换到测试模式
    问题: 每种设备需要不同的命令集,新设备一出来就要改协议,维护成本极高。
    SNMP 改为"信息导向":
不再发命令,而是读写"变量"
读操作:NMS 读取 MIB 变量 "sysUpTime"(已运行时间)
写操作:NMS 修改 MIB 变量 "currentMode" 的值为 "test"

这样,协议本身只需要"读"和"写"两种操作,不关心数据的具体含义——协议和数据完全解耦

5.2 MIB 对象的属性

每个 MIB 对象(变量)都有以下属性:

属性 SMIv1 名称 SMIv2 名称 说明
名称 Object Name Object Name 文本名(对象描述符)+ 数字 OID
语法 Syntax Syntax 数据类型(整数、字符串等)
访问权限 Access Max-Access 只读/读写/只写/不可访问
状态 Status Status mandatory/optional/obsolete 等
描述 Definition Description 文字说明

SMIv2 的访问权限层级(从高到低):

read-create    可读、可写、可创建(最高权限)
read-write     可读可写
read-only      只读
accessible-for-notify  只用于 Trap 通知
not-accessible 不可访问(特殊用途)

5.3 SMI 支持的数据类型


数据类型 说明 SMIv1 SMIv2
Integer/Integer32 32位有符号整数,范围 − 2 31 -2^{31} 231 2 31 − 1 2^{31}-1 2311
Octet String 可变长度字节串(文本或二进制)
Null 空值
Bits 位枚举(一组位标志)
Unsigned32 32位无符号整数,范围 0 0 0 4294967295 4294967295 4294967295
IpAddress IP 地址(4字节)
Counter/Counter32 计数器,从 0 0 0 增到 4294967295 4294967295 4294967295 后归零
Gauge/Gauge32 可升可降的量值,类似仪表盘
TimeTicks 从某时刻起的百分之一秒数(时间戳)
Opaque 不透明数据,原样传输不解析
Counter64 64位计数器,最大值 2 64 − 1 2^{64}-1 2641

5.4 MIB 对象定义示例

下面是 RFC 3418 中的一个真实 MIB 对象定义(SMIv2 格式):

-- 对象名(描述符)
sysLocation OBJECT-TYPE
    -- 数据类型:显示字符串,长度 0 到 255
    SYNTAX DisplayString (SIZE (0..255))
    -- 最大访问权限:可读可写
    MAX-ACCESS read-write
    -- 状态:当前有效
    STATUS current
    -- 描述:该节点的物理位置,不知道则为空串
    DESCRIPTION
        "The physical location of this node
         (e.g., 'telephone closet, 3rd floor').
         If the location is unknown, the value is
         the zero-length string."
    -- 对象标识符:system 组下的第 6 个对象
    ::= { system 6 }

这表示:sysLocationsystem 组下的第 6 个对象,即 OID 为 1.3.6.1.2.1.1.6

6. MIB 对象的命名体系

6.1 两种名称

每个 MIB 对象有两个名字

文本名(对象描述符):sysLocation
  - 人类友好,便于阅读和记忆
数字名(对象标识符 OID):1.3.6.1.2.1.1.6
  - 机器友好,全球唯一,层级结构

6.2 全球对象树

SNMP 使用一棵全球统一的层级树来组织所有对象,由 ISO 和 ITU 共同维护:

根节点 Root

ccitt(0) / itu(0)
ITU 标准

iso(1)
ISO 标准

joint-iso-ccitt(2)
联合标准

org(3)
其他组织

dod(6)
美国国防部

internet(1)
互联网
OID: 1.3.6.1

directory(1)
保留给ISO

mgmt(2)
主要MIB区域
OID: 1.3.6.1.2

experimental(3)
实验性对象

private(4)
厂商私有对象

security(5)
安全保留

snmpV2(6)
SNMPv2专用

mib-2(1)
标准MIB
OID: 1.3.6.1.2.1

enterprise(1)
厂商扩展
OID: 1.3.6.1.4.1

6.3 关键路径记忆

所有标准 MIB 对象都在:  1.3.6.1.2.1
                         iso.org.dod.internet.mgmt.mib-2
所有厂商私有对象都在:  1.3.6.1.4.1
                         iso.org.dod.internet.private.enterprise

举个例子:

  • 思科(Cisco)的所有私有对象都以 1.3.6.1.4.1.9 开头(9 是思科的企业编号)

6.4 标准 MIB 对象组

mib-2(1.3.6.1.2.1)下的标准对象组:

组名 代码 编号 完整 OID 用途
system sys 1 1.3.6.1.2.1.1 通用设备信息
interfaces if 2 1.3.6.1.2.1.2 网络接口信息
at at 3 1.3.6.1.2.1.3 地址转换(已废弃)
ip ip 4 1.3.6.1.2.1.4 IP 层信息
icmp icmp 5 1.3.6.1.2.1.5 ICMP 统计
tcp tcp 6 1.3.6.1.2.1.6 TCP 统计
udp udp 7 1.3.6.1.2.1.7 UDP 统计
egp egp 8 1.3.6.1.2.1.8 EGP(已过时)
snmp snmp 11 1.3.6.1.2.1.11 SNMP 自身管理

OID 递归定义示例:

mib-2        = 1.3.6.1.2.1
system       = { mib-2 1 }      => 1.3.6.1.2.1.1
sysUpTime    = { system 3 }     => 1.3.6.1.2.1.1.3
sysLocation  = { system 6 }     => 1.3.6.1.2.1.1.6

7. SNMP 协议操作详解

7.1 两种通信模式

SNMP 使用两种方式通信:

轮询(Poll-Driven)
  - NMS 主动向 Agent 请求数据
  - 类比:每天去信箱取信
  - 适用于:定期收集统计信息
中断(Interrupt-Driven)
  - Agent 主动向 NMS 发送通知
  - 类比:电话铃响了你才知道有事
  - 适用于:紧急事件(链路断了、认证失败……)

7.2 PDU(协议数据单元)分类

SNMP 消息叫做 PDU(Protocol Data Unit),按功能分为四类:

类别 说明 SNMPv1 PDU SNMPv2/v3 PDU
Read(读) 轮询读取信息 GetRequest, GetNextRequest GetRequest, GetNextRequest, GetBulkRequest
Write(写) 修改设备变量 SetRequest SetRequest
Response(响应) 回复请求 GetResponse Response
Notification(通知) 主动通知 NMS Trap Trapv2, InformRequest

7.3 读操作:GetRequest / Response

流程:

NMS                                    Agent
 |                                       |
 |  1. 创建 GetRequest-PDU              |
 |     (包含要查询的 MIB 对象名称)     |
 |-------------------------------------->|
 |                                       |  2. 收到请求
 |                                       |  3. 查找 MIB 变量值
 |                                       |  4. 创建 Response-PDU
 |<--------------------------------------|
 |  5. 处理返回的 MIB 对象值             |
 |                                       |

7.4 表格遍历:GetNextRequest 和 GetBulkRequest

设备的某些信息是"表格"形式,例如一台路由器有多个 IP 地址,存在 ipAddrTable 中。
SNMPv1 的解决方案:GetNextRequest

NMS 发送:GetNextRequest(ipAddrTable[1])
Agent 返回:ipAddrTable[2] 的值
NMS 发送:GetNextRequest(ipAddrTable[2])
Agent 返回:ipAddrTable[3] 的值
...(直到返回表格外的对象,说明遍历完毕)

缺点: 每次只取一条,效率低,产生大量来回消息。
SNMPv2 的改进:GetBulkRequest
一次请求,批量返回多行:

// GetBulkRequest 结构示意
struct GetBulkRequest {
    int pdu_type;          // 类型 = 5
    int request_id;        // 请求 ID
    int non_repeaters;     // 普通变量的数量(表格前面的那些)
    int max_repetitions;   // 表格要返回几行
    VarBindList var_binds; // 变量列表
};
// 例如:查 4 个普通变量 + 表格的前 3 行
// non_repeaters = 4, max_repetitions = 3

7.5 写操作:SetRequest

NMS 通过 SetRequest 修改 Agent 上的 MIB 变量,从而控制设备行为:

NMS                                    Agent
 |                                       |
 |  SetRequest-PDU                       |
 |  (变量名 + 新值)                     |
 |-------------------------------------->|
 |                                       |  验证:
 |                                       |  - 变量名是否存在?
 |                                       |  - 是否有写权限?
 |                                       |  - 值的类型/大小是否合法?
 |                                       |  - 安全检查通过?
 |                                       |  执行修改
 |<--------------------------------------|
 |  Response-PDU(成功或错误码)          |

7.6 通知操作:Trap / InformRequest

Trap(陷阱):
Agent 检测到重要事件(链路故障、重启、认证失败……)时,主动发给 NMS,不需要确认:

Agent                                  NMS
 |                                       |
 |  Trapv2-PDU(无需等待请求)           |
 |-------------------------------------->|
 |                                       |  (NMS 不需要回复)

InformRequest(通知请求):
用于 NMS 之间互相传递信息,需要确认

NMS-1                                 NMS-2
 |                                       |
 |  InformRequest-PDU                    |
 |  (转发收到的 Trap 信息)              |
 |-------------------------------------->|
 |<--------------------------------------|
 |  Response-PDU(确认收到)             |

8. SNMP 消息格式详解

8.1 消息结构概览

SNMP 使用 ASN.1(抽象语法标记1)定义消息格式——和定义 MIB 对象用的语言一样。
每条 SNMP 消息 = 消息头(Message Header) + 消息体(PDU):

+------------------+---------------------------+
|   消息头          |           PDU             |
|  (版本/安全信息)  |  (控制字段 + 变量绑定列表) |
+------------------+---------------------------+

变量绑定(Variable Binding):
PDU 里的每个变量都是一个"名值对":

+--------------------+--------------------+
| Object Name (OID)  | Object Value (值)  |
| 如:1.3.6.1.2.1.1.6| 如:"机房三楼"    |
+--------------------+--------------------+

8.2 SNMPv1 消息格式

消息头极简,只有三个字段:

+----------+----------------+----------+
| Version  |   Community    |   PDU    |
| (4字节)  | (可变长字符串) | (可变长) |
+----------+----------------+----------+
  值=0(v1)  类似密码,明文   实际协议体

SNMPv1 通用 PDU 格式(Get/GetNext/Set/GetResponse):

+----------+-----------+-------------+-------------+----------------+
| PDU Type | Request ID | Error Status | Error Index | Variable Binds |
| (4字节)  |  (4字节)  |   (4字节)   |   (4字节)   |    (可变长)    |
+----------+-----------+-------------+-------------+----------------+

PDU Type 枚举值:

0 = GetRequest-PDU
1 = GetNextRequest-PDU
2 = GetNextRequest-PDU(注:原文如此,2实为GetResponse)
3 = SetRequest-PDU
4 = Trap-PDU(特殊格式)

Error Status 错误码:

代码 含义
0 noError 无错误
1 tooBig 响应太大无法传输
2 noSuchName 对象名不存在
3 badValue 值的类型或大小不对
4 readOnly 试图写只读变量
5 genErr 其他通用错误

SNMPv1 Trap-PDU(格式特殊):

+----------+------------+-----------+---------+-----------+----------+----------------+
| PDU Type | Enterprise | Agent Addr| Generic | Specific  | TimeStamp| Variable Binds |
| (4字节)  | (可变长OID)| (4字节IP) | Trap码  | Trap码    | (4字节)  |   (可变长)     |
+----------+------------+-----------+---------+-----------+----------+----------------+

8.3 SNMPv2 消息格式

SNMPv2c(社区字符串版)消息头:
与 SNMPv1 基本相同,只是版本号变为 1(没错,是 1 不是 2,这是个历史遗留设计)。
SNMPv2 通用 PDU 格式(与 v1 类似但 PDU Type 更多):

PDU Type 取值:
0 = GetRequest-PDU
1 = GetNextRequest-PDU
2 = Response-PDU
3 = SetRequest-PDU
4 = 废弃(原 Trap)
5 = GetBulkRequest-PDU(独特格式,见下)
6 = InformRequest-PDU
7 = Trapv2-PDU
8 = Report-PDU

GetBulkRequest-PDU 特殊格式:

+----------+-----------+--------------+----------------+----------------+
| PDU Type | Request ID | NonRepeaters | MaxRepetitions | Variable Binds |
| (4字节)  |  (4字节)  |   (4字节)    |    (4字节)     |    (可变长)    |
+----------+-----------+--------------+----------------+----------------+

8.4 SNMPv3 消息格式

SNMPv3 在消息头里增加了完整的安全字段,结构更复杂:

+-------------+---------+------------+----------+----------------+------------+
| MsgVersion  | MsgID   | MsgMaxSize | MsgFlags | MsgSecurityModel| MsgSecurity|
|  (4字节)    | (4字节) |  (4字节)   | (1字节)  |   (4字节)      | Parameters |
+-------------+---------+------------+----------+----------------+------------+
                                                                       |
                                                              +-------------------+
                                                              |    Scoped PDU     |
                                                              | (上下文ID + PDU)  |
                                                              +-------------------+

MsgFlags(1字节)位结构:

  位 7~3      位 2            位 1          位 0
+---------+-----------+-------------+----------+
| 保留(5位)| Reportable | Priv Flag   | Auth Flag|
|  未使用  | 需回报告   | =1 启用加密 | =1 启用认证|
+---------+-----------+-------------+----------+

传输层:
SNMP 跑在 UDP 之上(而非 TCP),使用两个端口:

UDP 端口 161:普通 SNMP 消息(Get/Set/Response)
UDP 端口 162:Trap 消息

选择 UDP 而非 TCP 的理由:

  • SNMP 的请求/响应模式简单,不需要 TCP 的连接管理开销
  • UDP 头部更短,处理更快
    但 UDP 的问题:
  • 消息可能丢失 → NMS 需要设置超时,超时重发
  • 消息大小有限 → GetBulkRequest 的 MaxRepetitions 要谨慎设置,避免响应超出 UDP 上限

9. SNMP 的安全问题

9.1 SNMPv1 的安全有多弱?

SNMPv1 安全机制极其简单,只有一个"社区字符串(Community String)":

消息头里带一个字段,就像密码:
  - 发送方在消息里附上社区字符串(明文!)
  - 接收方比对字符串,不匹配则拒绝

问题:

  • 明文传输——网络抓包就能看到密码
  • 一旦密码泄露,任何人都能控制你的设备
  • 就像把钥匙用透明袋子装着——能防君子,不防小人

9.2 SNMPv2/v3 的安全模型


模型 用于 核心思路
基于 Party 的安全(PSM) SNMPv2p 定义"通信方",指定认证和加密协议
基于用户的安全(USM) SNMPv2u, SNMPv3 基于用户身份,支持多种认证/加密算法
基于视图的访问控制(VACM) SNMPv3 细粒度控制:哪个用户能访问哪些 MIB 对象

VACM 的概念:

视图(View)= 一组可访问的 MIB 对象
用户组(Group)= 一批用户
上下文(Context)= 特定的管理信息范围
访问控制 = 哪个用户组 + 在什么上下文 + 能访问哪个视图

10. SNMP 版本演化史

1987
SGMP
RFC 1028
(前身)

1988
SNMPv1
RFC 1157
(广泛部署)

1992
SNMPsec
RFC 1351-1353
(未普及)

1993
SNMPv2p
RFC 1441-1452
(party-based 安全)

1996
SNMPv2c
RFC 1901-1908
(社区字符串安全)

1996
SNMPv2u
RFC 1909-1910
(用户-based 安全)

SNMPv2*
(厂商私有,未标准化)

1998/2002
SNMPv3
RFC 3411-3418
(统一标准)

版本对比


特性 SNMPv1 SNMPv2c SNMPv3
安全机制 社区字符串(明文) 社区字符串(明文) USM(加密+认证)
访问控制 VACM
GetBulk
InformRequest
当前状态 仍广泛使用 部分使用 推荐使用

SNMPv2 分裂的教训:
在争论安全方案时,各方无法达成共识,导致出现 v2p、v2c、v2u、v2* 等多个互不兼容的版本。大多数用户选择"等等看",继续用 SNMPv1。这正说明了:标准的统一性比完美性更重要

11. RMON:远程网络监控

11.1 RMON 是什么?

RMON 常被称为"协议",但准确地说:

RMON 不是独立协议,它是 SNMP 的一个 MIB 模块,专门描述网络监控探针的管理对象。
它的 OID 是 1.3.6.1.2.1.16(mib-2 下的第 16 组)。

11.2 为什么需要 RMON?

普通设备(路由器、主机)的 SNMP Agent 收集的信息有限——它们的主业是路由/主机工作,不能全力搞监控。
RMON 针对专用网络探针(网络分析仪)定义了一套更丰富的 MIB 对象。

11.3 RMON 的 9 个子组

rmon (1.3.6.1.2.1.16)
├── statistics  (16.1)  - 网络流量统计(包大小分布、广播数、错误计数……)
├── history     (16.2)  - 历史采样数据(控制采样频率)
├── alarm       (16.3)  - 告警配置(超过阈值时触发事件)
├── hosts       (16.4)  - 各主机的流量统计
├── hostTopN    (16.5)  - 主机排行榜(如发包最多的前10台)
├── matrix      (16.6)  - 任意两主机之间的流量矩阵
├── filter      (16.7)  - 数据包过滤(捕获特定类型的包)
├── packetCapture(16.8) - 数据包捕获缓冲区
└── event       (16.9)  - 事件记录与 Trap 触发

11.4 RMON 的告警机制

管理员配置:
  监控哪个变量?(如 etherStats 的碰撞计数)
  多久采样一次?(如每 30 秒)
  阈值是多少?(如超过 100 次/秒则告警)
触发后:
  记录一条日志
  发送 Trap 到 NMS
  NMS 管理员收到告警,决定下一步处理

总结

整个 SNMP 体系可以用一句话概括:
用统一的方式(SMI)定义设备变量(MIB),
用标准协议(SNMP)远程读写这些变量,
让管理员通过 NMS 监控和控制网络中的所有设备。

核心设计哲学:信息导向而非命令导向
这使得 SNMP 协议本身保持极简(只有几种 PDU),
而通过 MIB 模块的扩展来适应无限多种设备类型,
真正做到了协议与数据的分离

TCP/IP 应用层寻址与文件传输详解

URI、URL、URN 及文件/消息传输概述

目录

  1. 为什么需要应用层寻址?
  2. URI 总览:三个容易混淆的概念
  3. URL 详解:怎么找到资源
  4. URL 的完整语法结构
  5. 常见 URL 方案(Scheme)
  6. 相对 URL 与绝对 URL
  7. URL 的长度与复杂性问题
  8. URL 混淆与欺骗手段
  9. URN 详解:用名字而非位置标识资源
  10. 文件与消息传输概述

1. 为什么需要应用层寻址?

已有地址,为何还需要更多?

学过网络的人可能会问:我们已经有了这么多地址,还需要更多吗?

第 2 层(数据链路层):MAC 地址       例如:AA:BB:CC:DD:EE:FF
第 3 层(网络层):  IP 地址          例如:192.168.1.100
第 4 层(传输层):  端口号 + 套接字   例如::80, :443

有了 IP 地址和端口号,理论上可以访问网络上任何资源——但问题是找不到

形象类比

假设你知道某本书在某栋楼里(IP 地址),也知道去哪个房间(端口号),但楼里有 100 万本书,你怎么知道要哪一本、在哪个书架、第几排、第几本?
应用层寻址就是解决这个"最后一公里"定位问题的。它服务的是人类,不是计算机——就像 DNS 让你用 www.google.com 代替 142.250.80.46 一样。

URI 的诞生

TCP/IP 定义了一套 统一资源标识符(Uniform Resource Identifier,URI) 系统,让每个文件、对象、程序都可以被唯一标记和访问。
URI 最初随万维网(WWW)诞生,现在也用于其他很多协议。

2. URI 总览:三个容易混淆的概念

URI
统一资源标识符
(总称)

URL
统一资源定位符
(最常用)

URN
统一资源名称
(较少使用)

告诉你:怎么访问 + 在哪里
例:
http://www.example.com
/file.html

告诉你:这是什么(名字)
例:URN:isbn:
0-679-73669-7

三者的区别


概念 全称 作用 类比
URI Uniform Resource Identifier 统称,包含 URL 和 URN 身份标识的总概念
URL Uniform Resource Locator 指明访问方法 + 位置 给你详细行车路线
URN Uniform Resource Name 只给名字,不管位置 书的 ISBN 编号

生动类比

URL 的方式:

“坐火车到上海,转地铁 2 号线,到人民广场站 B 出口,右转走 100 米,进红色大楼 3 楼 302 室,书架最下层右数第 3 本”
URN 的方式:
“ISBN: 978-7-115-54558-0”(这本书永远是这个号,不管它在哪)
URL 实用,URN 稳定——这就是为什么 URL 几乎统治了互联网,而 URN 虽然概念更优雅,却难以推广。

3. URL 详解:怎么找到资源

URL 的两大核心信息

URL 必须包含两样东西:

  1. 访问方法(Scheme):用什么协议去拿?(HTTP?FTP?)
  2. 资源位置(Location):在哪台服务器的哪个路径下?

URL 为什么强大?

用一个字符串就能替代完整的操作流程。比如访问 FTP 文件,传统方式需要:

1. 打开 FTP 客户端
2. 连接服务器 ftp.example.com
3. 登录(用户名/密码)
4. 切换目录:cd /drivers/
5. 下载文件:get widgetdriver.zip

用 URL 一步搞定:

ftp://ftp.example.com/drivers/widgetdriver.zip

URI 相关标准


RFC 编号 发布年份 内容
RFC 1630 1994 URI 在 WWW 中的使用(综述)
RFC 1737 1994 URN 的功能需求
RFC 1738 1994 URL 详细规范
RFC 1808 相对 URL 的定义与使用
RFC 2141 URN 语法
RFC 2396 1998 URI 通用语法(权威标准)

4. URL 的完整语法结构

最通用的 URL 格式

<scheme>://<user>:<password>@<host>:<port>/<url-path>;<params>?<query>#<fragment>

各部分说明

http://  john:secret123  @  www.mysite.org  :  8080  /cgi-bin/photos.php  ;mode=full  ?page=wedding  #Reception07
  |          |                  |               |           |                  |              |              |
scheme   用户名:密码           主机名           端口         路径               参数           查询字符串      片段锚点

元素 是否必须 说明 示例
scheme 必须 协议/访问方法 http, ftp, mailto
user:password 可选 登录认证,很少用 admin:123
host 通常必须 DNS 名称或 IP 地址 www.example.com
port 可选 省略则用默认端口 :8080(HTTP 默认 80)
url-path 通常需要 目录 + 文件名路径 /docs/readme.txt
params 可选 方案特定参数 ;type=i
query 可选 传给服务器的查询 ?name=Alice&age=30
fragment 可选 页面内锚点,不发给服务器 #chapter2

注意:fragment 的特殊性

#fragment 严格来说不是 URL 的一部分——它不会发送给服务器,只由客户端(浏览器)自己使用,用来定位页面内某个位置(比如滚动到某个书签)。

URL 中的特殊字符编码

URL 只允许使用安全字符(字母、数字和少数符号)。其他字符必须用 %XX 的十六进制编码表示:

字符 编码 字符 编码 字符 编码
空格 %20 # %23 % %25
/ %2F ? %3F @ %40
: %3A & %26 = %3D
< %3C > %3E { %7B
} %7D [ %5B ] %5D
\ %5C ^ %5E ~ %7E

示例:
文件名 are you there? 编码后变成:

are%20you%20there%3F

文件名 {ABC Corp} budget; draft #3 编码后:

%7BABC%20Corp%7D%20budget%3B%20draft%20%233

建议: 文件命名时用下划线代替空格,避免编码:

原来:my file name.txt   →  URL: my%20file%20name.txt
改为:my_file_name.txt   →  URL: my_file_name.txt  (干净多了)

5. 常见 URL 方案(Scheme)

HTTP(万维网)

http://<user>:<password>@<host>:<port>/<url-path>?<query>#<bookmark>
  • 默认端口:80(HTTPS 为 443)
  • 最常见的 URL 类型
  • ?query 常用于向服务器传参数
    示例:
http://www.example.com/search?keyword=TCP&page=2#results

FTP(文件传输)

ftp://<user>:<password>@<host>:<port>/<url-path>;type=<typecode>
  • 默认端口:21
  • 省略用户名/密码 → 匿名访问
  • type=a 表示 ASCII 文件,type=i 表示二进制文件,type=d 请求目录列表
    示例:
ftp://ftp.example.com/drivers/widgetdriver.zip

等价于:登录 FTP → 进入 drivers 目录 → 下载 widgetdriver.zip

mailto(发送电子邮件)

mailto:<username>@<domainname>

示例:

mailto:support@example.com

这是特殊 URL——它指向的不是"文件",而是一个"动作"(发邮件给某人)。

news / nntp(新闻组)

news://<newsgroup-name>
news://<message-id>
nntp://<host>:<port>/<newsgroup-name>/<article-number>
  • news:// 使用本地默认新闻服务器,端口默认 119
  • nntp:// 显式指定服务器

telnet(远程登录)

telnet://<user>:<password>@<host>:<port>
  • 默认端口:23
  • 省略密码 → 服务器提示输入
  • 指向的是"服务"而非"文件"

file(本地文件)

file://<host>/<url-path>     # 局域网文件
file:///<url-path>           # 本机文件(三个斜杠)

Windows 路径的特殊处理:

Windows 路径:C:\WINDOWS\SYSTEM32\DRIVERS\ETC\HOSTS
URL 形式:    file:///C|/WINDOWS/SYSTEM32/DRIVERS/ETC/HOSTS
              注意:反斜杠→正斜杠,冒号→竖线符号|

6. 相对 URL 与绝对 URL

什么是绝对 URL?

包含访问资源所需全部信息的 URL,不依赖任何上下文:

http://www.longdomainnamesareirritating.com/photos/wedding/reception.jpg

什么是相对 URL?

省略了部分信息,需要依赖基准 URL(Base URL) 来补全:

photos/wedding/reception.jpg    # 省略了 scheme 和 host

相对 URL 的三种基准来源(按优先级)

优先级 1:文档内显式声明的基准 URL(如 HTML 的 <base> 标签)
优先级 2:包含该文档的父实体的 URL(如 MIME 消息)
优先级 3:获取该文档时使用的 URL(最常见的默认方式)

实际解析规则

以下面这个基准 URL 为例:

http://site.net/dir1/subdir1/file1?query1#bookmark1

相对 URL 解析结果 说明
#bookmark2 http://site.net/dir1/subdir1/file1?query1#bookmark2 只换书签(页内跳转)
?query2 http://site.net/dir1/subdir1/file1?query2 换查询参数,书签被丢弃
file2 http://site.net/dir1/subdir1/file2 换文件名,查询和书签都丢弃
/file2 http://site.net/file2 单斜杠 = 从根目录开始
.. http://site.net/dir1/ 两点 = 上一级目录
../file2 http://site.net/dir1/file2 上一级目录中的 file2
../subdir2/file2 http://site.net/dir1/subdir2/file2 上一级再进 subdir2
../../dir2/subdir2/file2 http://site.net/dir2/subdir2/file2 上两级再往下
//file2 http://file2 双斜杠替换主机名,慎用!
ftp://othersite.net/x ftp://othersite.net/x 有新 scheme → 视为绝对 URL

相对 URL 的优势:可移植性

使用相对 URL,整个网站迁移域名时,不需要修改任何内部链接

原域名:www.longdomainnamesareirritating.com
新域名:www.shortname.com
使用绝对URL:每个页面里的链接都要改 → 工程浩大
使用相对URL:什么都不用改 → 自动适配新域名

7. URL 的长度与复杂性问题

导致 URL 变长的常见原因

1. DNS 名称过长

不好:www.superautobodyshopandpizzaparlor.com
好:  www.superauto.com

2. 文件名使用不安全字符

文件名:{ABC Corp} budget; draft #3; 3Q2024.htm
URL:   %7BABC%20Corp%7D%20budget%3B%20draft%20%233%3B%203Q2024.htm
改进后文件名:ABC_budget_draft3_3Q2024.htm
改进后URL:   ABC_budget_draft3_3Q2024.htm  (清晰多了)

3. 查询参数字符串

http://groups.google.com/groups?q=%22potato+salad%22&hl=en&lr=&ie=
UTF-8&safe=off&selm=B826FB57.89C0%25sbrooks%40ev1.net&rnum=2

URL 换行与分隔问题

大多数程序每行只显示 78~80 个字符。URL 如果超过这个长度,就会换行,容易导致:

  • 用户复制时遗漏末尾部分
  • 程序不能正确识别 URL 的结束位置
    解决方案:用尖括号包裹 URL
原始写法:
  请访问 http://www.networkingistoodarnedcomplicated.com 获取更多信息。
推荐写法(用尖括号明确边界):
  请访问 <http://www.networkingistoodarnedcomplicated.com> 获取更多信息。
也可加 URL: 标签:
  <URL:http://www.networkingistoodarnedcomplicated.com>

另一个方案:使用短链接服务(如 TinyURL),将长 URL 转为短 URL。

8. URL 混淆与欺骗手段

URL 语法的灵活性本是优点,却也被人利用来制造欺骗性链接。以下是常见手法(用于了解和防范,不用于作恶):

手法 1:添加大量无意义查询字符串

http://malicious.com/q=asdkfjhaksjdhfkajsdhfkjasdhfkjsdhfkjashdfkjasdhfkjh...

让人眼花缭乱,看不清楚真实目标。

手法 2:直接用 IP 地址代替域名

http://209.68.14.80     ← 和 http://www.example.com 是同一个网站

大多数人不知道可以直接用 IP 地址,所以看不出是什么网站。

手法 3:伪装成 IP 地址的数字域名

http://42.12.205.114.com    ← 看起来像 IP 地址,其实是域名

手法 4:伪造认证信息

HTTP URL 支持 用户名:密码@主机 格式,可以用来伪装:

http://www.cnn.com@www.malicioussite.com
                   ↑
           这才是真正要访问的地址
           前面的 www.cnn.com 只是"用户名",会被忽略

手法 5:用 % 编码混淆域名

http://%57%57%57.%50%43%47%55%49%44%45.%43%4F%4D
        W  W  W .  P  C  G  U  I  D  E .  C  O  M

解码后就是 http://www.PCGUIDE.COM,但一般人根本认不出来。

手法 6:IP 地址的各种数值表示

一个 IP 地址 209.68.14.80 可以用多种等价形式表示:

表示形式 示例 说明
标准点分十进制 http://209.68.14.80/ 正常形式
点分八进制 http://0321.0104.016.0120/ 每段前加 0
点分十六进制 http://0xD1.0x44.0x0E.0x50/ 每段前加 0x
单一十进制整数 http://3510898256/ 32 位数字
单一十六进制 http://0xd1440e50/ 32 位十六进制
单一八进制 http://032121007120/ 32 位八进制

IP 地址转为 32 位整数的计算方式:
N = A × 2 24 + B × 2 16 + C × 2 8 + D N = A \times 2^{24} + B \times 2^{16} + C \times 2^{8} + D N=A×224+B×216+C×28+D
对于 209.68.14.80
N = 209 × 16777216 + 68 × 65536 + 14 × 256 + 80 = 3510898256 N = 209 \times 16777216 + 68 \times 65536 + 14 \times 256 + 80 = 3510898256 N=209×16777216+68×65536+14×256+80=3510898256

组合攻击示例

原始:http://www.example.com
↓ 转为 IP
http://209.68.14.80
↓ 加伪装认证信息
http://www.cnn.com@209.68.14.80
↓ IP 转单一十进制数
http://www.cnn.com@3510898256
↓ IP 转带大量前导零的八进制
http://www.cnn.com@0000000000000321.00000000104.00000000000016.00000120

看起来像 CNN,实际上访问的是完全不同的网站。

9. URN 详解:用名字而非位置标识资源

URL 的根本缺陷

URL 把"资源"和"位置"绑定在了一起,一旦资源移动:

原来:http://old-server.com/papers/myresearch.pdf
移动后:http://new-server.com/archive/2024/myresearch.pdf

所有指向原来 URL 的链接全部失效,出现经典的:

HTTP 404 - NOT FOUND

类比:如果用"44 号格伦代尔新月路,悉尼"来标识一个人,那这个人一搬家,描述就失效了。

URN 的设计思路

URN 用永久名字而非位置来标识资源,就像:

  • 书有 ISBN,全球唯一,无论这本书在哪个书架
  • 人有身份证号,无论住在哪里

URN 的语法

URN:<namespace-ID>:<resource-identifier>

示例:

URN:isbn:0-679-73669-7       ← 标识某本书(用 ISBN)
URN:ietf:rfc:2396            ← 标识某个 RFC 文档

URN 的命名空间(Namespace)

因为不同类型的资源用不同编号系统,URN 用命名空间来区分:

URN:isbn:0-679-73669-7     ← isbn 命名空间(书籍)
URN:tel:+1-555-1234567     ← tel 命名空间(电话号码)
注意:4167819249 这串数字既可能是 ISBN 也可能是电话号码
      命名空间 ID 确保不会混淆

URN 为什么没有普及?

URN 面临的核心问题:知道名字,但不知道在哪里

给你一个 URN:isbn:0-679-73669-7
→ 你知道这是某本书
→ 但你在哪里下载/购买/阅读它?
→ URN 不告诉你!

要让 URN 实用,需要一套 URN 解析系统——类似 DNS 把域名转为 IP,把 URN 转为可访问的 URL。这套系统的设计极其复杂,至今仍在开发中(RFC 3401~3406 定义了 DDDS 动态委托发现系统)。

URL vs URN 对比

你想访问一本书

用什么方式?

URL 方式
去三楼书架C区右数第5本
(位置导向)

URN 方式
ISBN: 978-7-115-54558-0
(名字导向)

优点:知道怎么去拿
缺点:书移走了就404

优点:永远唯一不变
缺点:需要额外解析系统才知道书在哪

10. 文件与消息传输概述

什么是文件?

文件是计算机系统中最基本的信息单元:

  • 一个独立的数据集合
  • 存储在文件系统的目录/文件夹中
  • 由字节序列组成
  • 有文件属性(名称、大小、修改时间等)
    有趣的是:文件传输不是互联网的应用,而是互联网被发明出来的原因之一——早期的 FTP 协议比 IPv4、TCP、UDP 都出现得早。

两大应用类别

TCP/IP 文件与消息传输应用

通用文件传输
General File Transfer

消息传输
Message Transfer

FTP
文件传输协议

TFTP
简单文件传输协议

Email
电子邮件

Usenet
网络新闻组

WWW
万维网/超文本

通用文件传输

把文件当"黑盒"处理,不关心内容是什么,只管从 A 搬到 B:

FTP(File Transfer Protocol):功能完整,交互式,支持登录
TFTP(Trivial FTP):极简版,通常用于网络设备启动配置

消息传输应用

处理有特定格式的文件,这些文件本身就是为通信而生的:

应用 特点 类比
电子邮件(Email) 一对一或一对少数,有明确收件人 普通信件
网络新闻(Usenet) 一对多,所有人可见可回复 公告板/论坛
万维网(WWW) 超文本,可嵌入图片/多媒体/链接,一文档链接另一文档 百科全书+图书馆

边界正在模糊

现代 TCP/IP 应用让文件传输和消息传输的界限越来越模糊:

Email     → 可以附件传文件,可以包含 HTML 超文本
Web浏览器 → 可以访问 FTP 服务器下载文件
Web应用   → 可以实时收发消息(即时通讯、邮件客户端)

总结

应用层寻址体系:
  URI(总称)
    ├── URL(最常用)= 访问方法 + 位置
    │     格式:scheme://user:pass@host:port/path;params?query#fragment
    │     特点:实用、直接,但资源移动后链接失效
    │
    └── URN(较新)= 永久名字
          格式:URN:命名空间:资源标识符
          特点:稳定不变,但需要解析系统才能找到资源
文件传输体系:
  通用文件传输(FTP/TFTP)→ 不关心内容,只管搬运
  消息传输(Email/Usenet/WWW)→ 处理有格式的通信文件

URL 是互联网上最成功的"应用层地址"方案,其简单、灵活、人类可读的特性使其无处不在——从网页链接到电视广告,从代码配置到命令行工具。理解 URL 的完整语法,不仅能帮助你更好地使用网络,也能让你识破那些试图利用 URL 复杂性进行欺骗的恶意手段。

TCP/IP 通用文件传输协议详解

FTP 与 TFTP 深度解析

目录

  1. 概述:为什么需要文件传输协议
  2. FTP 的历史与标准
  3. FTP 的整体架构模型
  4. FTP 连接建立与认证
  5. FTP 数据连接:主动模式与被动模式
  6. FTP 传输模式
  7. FTP 数据表示:类型、格式与结构
  8. FTP 命令详解
  9. FTP 回复码系统
  10. FTP 用户界面与用户命令
  11. FTP 会话示例分析
  12. TFTP:精简版文件传输协议
  13. TFTP 操作流程详解
  14. TFTP 选项协商
  15. TFTP 消息格式
  16. FTP 与 TFTP 综合对比

1. 概述:为什么需要文件传输协议

文件传输是网络的起点

有个让人惊讶的历史事实:文件传输协议的历史比 TCP 和 IP 还要早。网络的发明,很大程度上就是为了传输文件,而不是文件传输作为网络的应用出现的。
早期开发者把网络用途分为两类:

直接使用(Direct):远程登录到另一台计算机,就像在用本地机器
    → 演化为 Telnet 协议
间接使用(Indirect):从远程主机取回资源,在本地使用
    → 演化为 FTP 协议

FTP 与 TFTP 的关系

就像 TCP 和 UDP 的关系一样:

FTP  ↔  TCP    功能全面、可靠、复杂、面向流
TFTP ↔  UDP    极度精简、快速、简单、面向数据报

2. FTP 的历史与标准

1971年 RFC 114   FTP 诞生(此时 TCP/IP 还不存在,用的是 NCP)
1972年 RFC 354   首次描述现代通信模型
1973年 RFC 542   协议形态已与现代非常接近
1980年 RFC 765   第一个基于 TCP/IP 的 FTP 标准
1985年 RFC 959   当前基础标准(沿用至今,超过 40 年!)

FTP 是 TCP/IP 应用层协议中寿命最长的之一,核心设计在 1985 年之后几乎没有变化。

3. FTP 的整体架构模型

3.1 两个核心概念:控制连接 vs 数据连接

FTP 最重要、最独特的设计是使用两条独立的 TCP 连接

控制连接(Control Connection)
  ├── 端口:服务器端口 21
  ├── 生命周期:整个 FTP 会话期间持续存在
  ├── 用途:传输命令和回复
  └── 不传输文件数据
数据连接(Data Connection)
  ├── 端口:动态分配
  ├── 生命周期:每次传输数据时临时建立,完成后关闭
  ├── 用途:传输文件或目录列表等实际数据
  └── 每次传输都创建新连接

类比:控制连接像"电话里的谈判",数据连接像"快递公司派来的货车"——每次需要发货时派一辆,送完就走。

3.2 FTP 软件组件

服务器_服务器FTP进程

客户端_用户FTP进程

控制连接(TCP 端口21)
命令和回复

数据连接(动态端口)
文件数据

用户界面
User Interface
(人机交互)

用户协议解释器
User-PI
(管理控制连接)

用户数据传输进程
User-DTP
(管理数据连接)

服务器协议解释器
Server-PI
(管理控制连接)

服务器数据传输进程
Server-DTP
(管理数据连接)


组件 位置 职责
用户界面(User Interface) 客户端 翻译用户友好命令为 FTP 协议命令
用户-PI(User-PI) 客户端 管理控制连接,发送命令,接收回复
用户-DTP(User-DTP) 客户端 管理数据连接,发送/接收文件数据
服务器-PI(Server-PI) 服务器 监听端口 21,处理命令,发送回复
服务器-DTP(Server-DTP) 服务器 管理数据连接,与本地文件系统交互

注意:客户端有 3 个组件(多一个用户界面),服务器只有 2 个组件。

3.3 代理 FTP(第三方传输)

FTP 标准还定义了一种"代理模式":用户在自己机器上操控,让两台服务器之间直接传文件。由于安全隐患较大,现实中几乎不用。

4. FTP 连接建立与认证

4.1 建立控制连接的过程

客户端(用户-PI)                    服务器(服务器-PI)
      |                                    |
      |  TCP 连接请求(随机端口→端口21)   |
      |------------------------------------>|
      |                                    |  服务器在端口 21 持续监听
      |  220 Service ready for new user    |
      |<------------------------------------|
      |  USER ixl(发送用户名)            |
      |------------------------------------>|
      |  331 User ixl okay, need password  |
      |<------------------------------------|
      |  PASS xxxxxxxx(发送密码)         |
      |------------------------------------>|
      |  230 Logged in.(登录成功)        |
      |<------------------------------------|
      |         控制连接已建立              |

4.2 安全问题

FTP 的认证机制极其简单,用户名和密码以明文传输,任何中间设备都能截获。RFC 2228(FTP 安全扩展)提供了加密选项,但基础 FTP 本身不安全。

4.3 匿名 FTP(Anonymous FTP)

许多组织想向公众提供文件下载(如驱动程序),但不可能给每个访客创建账号。解决方案:匿名 FTP(RFC 1635):

用户名:anonymous 或 ftp
密码:  任意邮件地址(仅用于日志记录,不做验证)
权限限制:
  - 只能访问特定公开目录
  - 通常只读,不能删除/重命名
  - 普通已认证用户权限更高

现在大多数组织改用网站提供下载,但早年(1980~1990 年代)匿名 FTP 是发布软件的主要方式。

5. FTP 数据连接:主动模式与被动模式

这是 FTP 中最容易让人困惑的部分,也是最重要的实践概念。

5.1 主动模式(Active / Normal)

谁发起数据连接:服务器

客户端(端口 1678→21,控制连接已建立)
      |
      |  PORT 命令(告诉服务器:请连接我的 1742 端口)
      |------------------------------------>|
      |                                    |
      |    服务器主动从端口 20 连接客户端 1742
      |<================================== |  数据连接
      |                                    |
      |  数据传输(双向均可)               |

关键点:

  • 服务器使用固定端口 20 作为数据连接的源端口
  • 客户端用 PORT 命令告诉服务器应连哪个端口
  • 控制连接(1678)和数据连接(1742)通常用不同端口,避免 TCP 复用冲突
    为什么建议用不同端口: TCP 规定连接关闭后,同一端口号需等待一段时间才能复用(避免延迟包混入新会话)。连续传多个文件时,若端口相同会产生延迟。

5.2 被动模式(Passive)

谁发起数据连接:客户端

客户端(端口 1678→21,控制连接已建立)
      |
      |  PASV 命令(告诉服务器:你被动等,我来连你)
      |------------------------------------>|
      |  227 Entering Passive Mode (...,p1,p2)
      |  服务器告知客户端:请连接我的 2223 端口
      |<------------------------------------|
      |                                    |  服务器在 2223 监听
      |  客户端从 1742 连接服务器 2223     |
      |==================================> |  数据连接
      |                                    |
      |  数据传输(双向均可)               |

5.3 为什么被动模式更常用?

这与防火墙有关:

主动模式的问题:
  服务器主动连接客户端 → 从客户端视角看,这是"入站连接"
  现代防火墙默认拦截未主动请求的入站连接
  → 文件传输失败!
被动模式的解决:
  客户端主动发起所有连接 → 从客户端视角看,都是"出站连接"
  防火墙通常允许出站连接
  → 文件传输成功!

代价:把问题转移到服务器——服务器需要为被动模式开放一段端口范围(如 5000~6000),并在防火墙上允许这些端口的入站连接。这对管理员来说更可控,因为服务器数量远少于客户端数量。

5.4 违反分层原则的问题

PORT 和 PASV 命令在应用层消息中直接传递 IP 地址和端口号,这违反了 TCP/IP 的分层原则。这在使用 NAT(网络地址转换) 时会造成麻烦,因为 NAT 修改了 IP 地址,但 FTP 消息体里的 IP 地址不会自动更新,导致连接失败。需要专门的 NAT 穿越机制来处理。

6. FTP 传输模式

数据连接建立后,实际数据的发送方式有三种:

模式 机制 特点 使用率
流模式(Stream) 直接推送字节流,靠关闭连接标志结束 最简单、最高效、无额外开销 最常用
块模式(Block) 数据打包成块,每块有 3 字节头部 支持断点续传,可检测中断 较少用
压缩模式(Compressed) 游程编码(Run-Length Encoding)压缩 减少数据量 几乎不用

为什么压缩模式没人用:

  • 调制解调器已在硬件层做了压缩
  • 大文件通常已经是 ZIP/RAR 等压缩格式
  • 二次压缩效果微乎其微,甚至可能反而变大
    游程编码(Run-Length Encoding)简单示意:
    原始数据:AAAAABBBBCCC (12字节)
    编码后:5A4B3C (6字节)——用"重复次数+字符"表示连续重复的字节,节省空间。

7. FTP 数据表示:类型、格式与结构

7.1 为什么不能把所有文件都当"字节流"?

不同操作系统对文本文件的换行符约定不同:

UNIX / Linux:  LF      (0x0A,\n)
旧版 Mac OS:   CR      (0x0D,\r)
Windows:       CR+LF   (0x0D 0x0A,\r\n)

如果把 Windows 文本文件直接复制到 UNIX,每行末尾会多出一个 \r,程序读取时会出现乱码。

7.2 FTP 的四种数据类型


类型 用途 传输方式
ASCII ASCII 文本文件 自动转换换行符到 CR+LF(通用格式),接收方再转回本地格式
EBCDIC IBM 大型机文本文件 类似 ASCII,但字符集不同
Image(二进制) 图片、压缩包、可执行文件等 原样传输,一字节不改
Local 特殊位宽的本地格式 按指定比特数解释

实际使用: 90% 的场合只用 ASCII 和 Image(Binary)两种。

7.3 ASCII 模式的转换流程

UNIX 系统 → 发送文本文件 → Windows 系统
发送方(UNIX):
  每个 LF → 转为 CR+LF(NETASCII 通用格式)
传输中:
  CR+LF(NETASCII 标准换行)
接收方(Windows):
  CR+LF → 保持 CR+LF(Windows 本地格式)
结果:Windows 可以正常打开该文件

反向传输(Windows → UNIX):

发送方(Windows):CR+LF → CR+LF(不变,本来就是标准)
接收方(UNIX):CR+LF → LF(去掉 CR)

重要警告: 二进制文件(ZIP、JPG、EXE)必须用 Image 模式,绝对不能用 ASCII 模式!否则 FTP 会把文件里碰巧值为 0x0D 或 0x0A 的字节当成换行符处理,导致文件损坏。这是 FTP 传输中最常见的错误之一。

7.4 数据结构


结构 说明 使用情况
文件结构(File) 连续字节流,无内部结构 默认,绝大多数场合
记录结构(Record) 有序记录,每条有结束标记 某些文本文件场景
页结构(Page) 索引数据页 历史遗留,几乎不用

8. FTP 命令详解

FTP 命令用 3~4 个字母的代码表示,分三组:

8.1 访问控制命令


命令 全称 说明
USER User name 发送用户名
PASS Password 发送密码
ACCT Account 指定账户(多账户系统)
CWD Change Working Directory 切换工作目录
CDUP Change Directory UP 切到上级目录
SMNT Structure Mount 挂载文件系统
REIN Reinitialize 重置会话(不断开连接)
QUIT Logout 登出并关闭控制连接

8.2 传输参数命令


命令 全称 说明
PORT Data Port 告诉服务器客户端监听哪个端口(主动模式)
PASV Passive 请求被动模式,服务器等待客户端连接
TYPE Representation Type 设置数据类型(ASCII/EBCDIC/Image/Local)
STRU File Structure 设置数据结构(File/Record/Page)
MODE Transfer Mode 设置传输模式(Stream/Block/Compressed)

8.3 服务命令(最重要的一组)


命令 全称 说明
RETR Retrieve 从服务器下载文件
STOR Store 上传文件到服务器
STOU Store Unique 上传并确保文件名唯一(防止覆盖)
APPE Append 上传并追加到已有文件
ALLO Allocate 预留存储空间
REST Restart 断点续传(仅 Block/Compressed 模式)
RNFR Rename From 重命名源文件名
RNTO Rename To 重命名目标文件名
ABOR Abort 中止当前传输
DELE Delete 删除文件
RMD Remove Directory 删除目录
MKD Make Directory 创建目录
PWD Print Working Directory 显示当前目录
LIST List 列出目录内容(含详细信息)
NLST Name List 列出文件名(仅名称)
SYST System 查询服务器操作系统类型
STAT Status 查询传输状态
HELP Help 获取帮助
NOOP No Operation 空操作(用来验证控制连接是否存活)

9. FTP 回复码系统

9.1 三位数字编码体系

每个命令都会收到一个三位数字回复码 + 文字描述。数字部分由软件解析,文字部分供人阅读。
三位数字的含义:

第一位(x):总体结果
  1xx → 正在进行(初步肯定,继续等待)
  2xx → 成功完成
  3xx → 暂停,等待更多信息
  4xx → 临时错误(可重试)
  5xx → 永久错误(不必重试)
第二位(y):功能分类
  x0x → 语法相关
  x1x → 信息查询
  x2x → 连接相关
  x3x → 认证和账户
  x4x → 未定义
  x5x → 文件系统
第三位(z):具体类型(每个分类内的细分)

9.2 解码示例

回复码 530

5  3  0
↑  ↑  ↑
│  │  └── 具体错误类型 0
│  └───── 认证/账户相关(x3x)
└──────── 永久错误(5xx)
含义:Not logged in(登录失败,认证相关的永久错误)

回复码 530

第一位: 5
永久错误,不必重试

第二位: 3
认证 / 账户类

第三位: 0
具体错误类型

命令被拒绝,
重试无效

与登录/密码
有关的问题

9.3 常用回复码速查


代码 典型文字 含义
220 Service ready for new user 服务器就绪,等待登录
221 Service closing control connection 再见,控制连接关闭
227 Entering Passive Mode (…) 进入被动模式,含端口信息
230 User logged in, proceed 登录成功
250 Requested file action okay 文件操作成功
331 User name okay, need password 用户名已接受,请输密码
421 Service not available 服务不可用
425 Can’t open data connection 数据连接打不开
426 Connection closed; transfer aborted 连接断了,传输中止
500 Syntax error 命令语法错误
530 Not logged in 未登录(认证失败)
550 File unavailable 文件不存在或无权访问

9.4 多行回复

当回复文字需要多行时,每行前缀规则:

230-Welcome user to FTP server.          ← 代码+连字符,表示"还没结束"
230-You are user #17 of 100 allowed.     ← 继续
230-Please see the file "faq.txt".       ← 继续
230 Logged in.                           ← 代码+空格,表示"最后一行"

这种编码方式被后来的 SMTP(邮件)、NNTP(新闻组)、HTTP(Web)等协议借鉴。

10. FTP 用户界面与用户命令

用户命令 vs FTP 协议命令

用户不需要直接使用 RETRSTOR 等协议命令,用户界面提供了更友好的命令:

用户命令 对应 FTP 协议命令 说明
get 文件名 PASV + RETR 文件名 下载文件(自动处理被动模式)
put 文件名 PASV + STOR 文件名 上传文件
mget *.txt 多次 RETR 批量下载
mput *.jpg 多次 STOR 批量上传
lsdir PASV + LIST 列目录
cd 目录 CWD 目录 切换目录
ascii TYPE A 设置 ASCII 模式
binary TYPE I 设置二进制模式
passive 切换主/被动模式
bye / quit / exit QUIT 退出(三个是同义词)
pwd PWD 显示当前目录
delete 文件 DELE 文件 删除远程文件
rename 旧名 新名 RNFR + RNTO 重命名
?help 本地帮助 显示可用命令列表

用户界面的三大好处:

  1. 更友好:getRETR 好记
  2. 可定制:binaryimage 都能设置二进制模式
  3. 自动化:get 会自动先发 PASV 再发 RETR,用户不用知道这些细节

11. FTP 会话示例分析

下面是一个真实 FTP 会话(启用调试模式,可以看到底层协议命令):

用户命令              协议层交互                          说明
─────────────────────────────────────────────────────────────────────
ftp -d pcguide.com    (建立 TCP 控制连接)               -d 开启调试模式
                      220 ftp199.pair.com ready            服务器就绪
ixl               →   USER ixl                             发送用户名
                  ←   331 User ixl okay, need password     需要密码
****              →   PASS XXXX                            发送密码(显示为****)
                  ←   230 Logged in.                       登录成功
                  →   SYST                                 自动查询系统类型
                  ←   215 UNIX Type: L8                    服务器是 UNIX
pwd               →   PWD                                  查当前目录
                  ←   257 "/usr/home/ixl" is cwd           当前在家目录
cd ftptest        →   CWD ftptest                         尝试进入 ftptest 目录
                  ←   550 No such directory.               目录不存在!
cd ftpdemo        →   CWD ftpdemo                         进入 ftpdemo 目录
                  ←   250 ".../ftpdemo" is new cwd         成功
dir               →   PASV                                 请求被动模式
                  ←   227 Entering Passive Mode (...)      服务器给出端口 193,224
                  →   LIST                                  请求目录列表
                  ←   150 Data connection accepted...      数据连接建立
                      -rw-r--r-- 1 ixl 16 May 22 testfile.txt
                  ←   226 Listing completed.               目录列表传输完毕
asc               →   TYPE A                               设置 ASCII 模式
                  ←   200 Type okay.                       模式设置成功
get testfile.txt  →   PASV                                 再次请求被动模式
                  ←   227 Entering Passive Mode (...)      新端口 193,226(不同端口!)
                  →   RETR testfile.txt                    下载文件
                  ←   150 ...transfer starting (16 bytes)  开始传输
                  ←   226 Transfer completed.              传输完成
                      17 bytes received in 0.10 seconds
del testfile.txt  →   DELE testfile.txt                    删除服务器上的文件
                  ←   250 Deleted.                         删除成功
cdup              →   CDUP                                 返回上级目录
                  ←   250 "...ixl" is new cwd
rmdir ftpdemo     →   RMD ftpdemo                         删除目录
                  ←   250 Directory removed.               删除成功
quit              →   QUIT                                 退出
                  ←   221 Goodbye.                        再见

观察两次数据连接的端口变化:

  • dir 时:服务器端 193,224 端口,客户端使用某端口
  • get 时:服务器端 193,226 端口,客户端使用另一端口
    每次数据连接都用新端口,避免 TCP 复用延迟问题。

12. TFTP:精简版文件传输协议

12.1 为什么需要 TFTP?

考虑一种设备:无盘工作站(没有硬盘)。

启动过程(Bootstrapping):
第一步:通过 BOOTP/DHCP 获取 IP 地址(自己解决)
第二步:从网络服务器下载操作系统镜像 → 需要文件传输
问题:
  FTP 需要完整的 TCP 实现
  TCP 的代码量相当大
  启动用的 ROM 芯片空间有限
  → FTP 太重,放不进 ROM!
解决方案:TFTP(极度精简的文件传输)
  TFTP 基于 UDP(代码量极小)
  只支持读/写两个操作
  整个实现可以塞进 ROM 芯片

12.2 FTP vs TFTP 全面对比

TFTP

传输层:UDP
(快速、无连接、不可靠)

认证:无!

命令:极少(RRQ/WRQ/DATA/ACK/ERROR)

操作:仅读和写

数据类型:netascii 或 octet

端口:69(服务器监听)

FTP

传输层:TCP
(可靠、有序、流式)

认证:用户名+密码

命令:数十条

操作:读、写、删除、
重命名、目录操作…

数据类型:ASCII/EBCDIC/
Image/Local

端口:21(控制)
20(数据,主动模式)


特性 FTP TFTP
传输层协议 TCP UDP
连接性 有明确的连接状态 逻辑连接(无 TCP 握手)
认证 用户名/密码
命令数量 数十条 5~6 种消息类型
文件操作 读/写/删/改/目录管理 仅读/写
数据类型 ASCII/EBCDIC/Image/Local netascii / octet
适用场景 通用文件传输 嵌入式设备、网络启动
程序大小 较大 极小(可放 ROM)
性能 高(TCP 流水线) 低(锁步确认)
安全性 基本(明文密码)

13. TFTP 操作流程详解

13.1 连接标识与端口机制

TFTP 没有 TCP 连接,用**传输标识符(TID)**区分不同传输:

客户端随机选一个端口(如 3145)作为 TID
          ↓
发送初始请求到服务器的 UDP 端口 69
          ↓
服务器随机选一个端口(如 1114)作为自己的 TID
          ↓
后续通信:客户端 3145 ↔ 服务器 1114
          端口 69 不再参与此次传输

为什么服务器不用 69 端口回复?
因为服务器可能同时处理多个客户端请求,用不同的随机端口来区分不同的传输会话,端口 69 始终保持空闲以接受新连接。

13.2 锁步通信(Lock-Step)

TFTP 每发一个数据包,必须等对方确认后才能发下一个:

发送方                接收方
   |                     |
   |  DATA block 1       |
   |-------------------->|
   |  ACK block 1        |
   |<--------------------|
   |  DATA block 2       |
   |-------------------->|
   |  ACK block 2        |
   |<--------------------|
   |  ...                |

优点: 简单,实现容易,不需要滑动窗口等复杂机制
缺点: 慢!每次只有 512 字节在"路上",高延迟网络尤其差

13.3 文件结束标志

TFTP 用一种巧妙方式标记文件结束:

规则:每个数据块标准大小 512 字节
如果最后一块 < 512 字节 → 传输结束!
特殊情况:如果文件大小恰好是 512 的倍数(如 1024 字节)
  第 1 块:512 字节(未结束)
  第 2 块:512 字节(还没结束,因为收到了 512 字节)
  第 3 块:0 字节(这才是结束信号!)

因此,最后一个数据包包含 0 到 511 字节,收到后即知传输完成,无需额外的结束消息。

13.4 读操作(RRQ)流程

以传输 1200 字节文件为例(3 块:512 + 512 + 176):

客户端                                    服务器
   |                                         |
   |  RRQ "filename" netascii               |
   |  (读请求,到服务器端口 69)            |
   |---------------------------------------->|
   |                                         |
   |  DATA block 1 (512 bytes)              |
   |  (从服务器的随机 TID 端口发出)        |
   |<----------------------------------------|
   |  ACK block 1                           |
   |---------------------------------------->|
   |                                         |
   |  DATA block 2 (512 bytes)              |
   |<----------------------------------------|
   |  ACK block 2                           |
   |---------------------------------------->|
   |                                         |
   |  DATA block 3 (176 bytes)              |  ← 少于512字节 = 最后一块!
   |<----------------------------------------|
   |  ACK block 3                           |
   |---------------------------------------->|
   |                                         |
        传输完成,逻辑连接自动结束

13.5 写操作(WRQ)流程

同样是 1200 字节文件,但客户端上传:

客户端                                    服务器
   |                                         |
   |  WRQ "filename" netascii               |
   |---------------------------------------->|
   |                                         |
   |  ACK block 0                           |  ← 用 block 0 确认收到写请求
   |<----------------------------------------|
   |                                         |
   |  DATA block 1 (512 bytes)              |
   |---------------------------------------->|
   |  ACK block 1                           |
   |<----------------------------------------|
   |                                         |
   |  DATA block 2 (512 bytes)              |
   |---------------------------------------->|
   |  ACK block 2                           |
   |<----------------------------------------|
   |                                         |
   |  DATA block 3 (176 bytes)              |  ← 最后一块
   |---------------------------------------->|
   |  ACK block 3                           |
   |<----------------------------------------|
        传输完成

读和写的区别:

  • 读:服务器先发数据(用第一块数据作为"确认了读请求"的信号)
  • 写:服务器先发 ACK block 0(明确告诉客户端"可以开始发了")

13.6 巫师学徒 Bug(Sorcerer’s Apprentice Bug)

原始 TFTP 规范有一个著名的 Bug,名字来自迪士尼电影《幻想曲》中米老鼠砍扫帚的片段:

问题场景:
1. 服务器发送 DATA block 2
2. ACK block 2 延迟(网络拥塞),服务器超时重发 DATA block 2
3. 客户端收到 DATA block 2,发出 ACK block 2
4. 服务器收到 ACK,发出 DATA block 3
5. 延迟的原始 DATA block 2 也到了!客户端再发 ACK block 2
6. 服务器收到重复 ACK block 2,再发一次 DATA block 2!
7. 此后每个数据块都被发两次,ACK 也两次
→ 消息数量翻倍,越来越混乱,就像砍扫帚越砍越多
修复方案:
  只有收到重复 DATA 的一方才能重发 ACK
  收到重复 ACK 的一方 不 重发 DATA
  (单向允许重复,打破反馈循环)

14. TFTP 选项协商

1995 年 RFC 1782 为 TFTP 加入了选项协商机制,允许在传输开始前商定参数:

14.1 三种可协商选项


选项代码 含义 说明
blksize 数据块大小 默认 512 字节,可协商更大值(如 1468)提高效率
interval 超时间隔(秒) 调整重传计时器,适应不同网络延迟
tsize 传输文件大小 提前告知文件总大小,接收方可预分配空间

14.2 带选项的读操作流程

正常读(无选项):

客户端→服务器:RRQ
服务器→客户端:DATA block 1
客户端→服务器:ACK block 1
...

带选项的读:

客户端→服务器:RRQ + 选项列表(如 blksize=1024)
服务器→客户端:OACK(选项确认,列出接受的选项)
客户端→服务器:ACK block 0(确认收到 OACK)
服务器→客户端:DATA block 1(用协商好的参数)
客户端→服务器:ACK block 1
...

注意: 读操作多了一个"ACK block 0"的步骤,因为 TFTP 不允许同一设备连续发两个数据报,服务器的 OACK 需要被确认后才能发第一块数据。
写操作带选项:

客户端→服务器:WRQ + 选项列表
服务器→客户端:OACK(代替普通的 ACK block 0)
客户端→服务器:DATA block 1
...(后续与普通写相同)

15. TFTP 消息格式

TFTP 共定义 6 种消息类型,所有消息的第一个字段都是操作码(Opcode):

15.1 读请求(RRQ, Opcode=1)/ 写请求(WRQ, Opcode=2)

+----------+------------+-----+------+-----+-------+-----+
| Opcode   |  Filename  |  0  | Mode |  0  | opts  |  0  |
|  2 bytes | variable   |     | var  |     | var.. |     |
+----------+------------+-----+------+-----+-------+-----+
  • Filename:文件名字符串,以 0 字节结尾
  • Mode:netasciioctet,以 0 字节结尾
  • opts(可选):选项名=选项值对,各以 0 字节结尾

15.2 数据消息(DATA, Opcode=3)

+----------+-----------+------------------+
| Opcode=3 | Block #   |       Data       |
|  2 bytes |  2 bytes  |  0 to 512 bytes  |
+----------+-----------+------------------+
  • Block #:从 1 开始顺序编号
  • Data:0~512 字节,小于 512 字节表示最后一块

15.3 确认消息(ACK, Opcode=4)

+----------+-----------+
| Opcode=4 | Block #   |
|  2 bytes |  2 bytes  |
+----------+-----------+
  • Block #:被确认的数据块编号;0 表示确认写请求或 OACK

15.4 错误消息(ERROR, Opcode=5)

+----------+------------+-----------+-----+
| Opcode=5 | Error Code | Error Msg |  0  |
|  2 bytes |  2 bytes   |  variable |     |
+----------+------------+-----------+-----+

错误码 含义
0 未定义错误,见错误消息文字
1 文件未找到
2 访问违规
3 磁盘满或分配超限
4 非法 TFTP 操作
5 未知传输 ID
6 文件已存在
7 无此用户
8 不接受选项协商结果(客户端拒绝 OACK)

15.5 选项确认(OACK, Opcode=6)

+----------+-------+-----+--------+-----+
| Opcode=6 | opt1  |  0  | value1 |  0  | ...(重复多个选项)
|  2 bytes | var   |     | var    |     |
+----------+-------+-----+--------+-----+

服务器在 OACK 中列出所有接受的选项及其值(可能与客户端请求的值不同)。

16. FTP 与 TFTP 综合对比

使用场景选择指南

否(嵌入式/ROM)

仅读/写单文件

需要目录管理/
删除/重命名等

否(内部网络)

需要文件传输

设备资源充足?
(有 TCP 栈、内存够)

需要哪些功能?

使用 TFTP

需要认证?

使用 FTP

典型应用场景


场景 推荐协议 原因
网站文件上传管理 FTP 需要目录操作、权限控制
软件分发下载 匿名 FTP 或 HTTP 公开访问,无需复杂认证
网络设备启动(路由器/交换机) TFTP 设备内存有限,只需下载配置或固件
无盘工作站引导 TFTP 同上,空间极度有限
备份路由器配置 TFTP 快速、简单,内部网络无需认证
跨系统文本文件传输 FTP(ASCII 模式) 自动处理换行符转换

关键数字速记

FTP 控制端口:  21
FTP 数据端口:  20(主动模式)
TFTP 端口:     69
TFTP 数据块:   512 字节/块(默认)
FTP 回复码:    3位数字,百位=状态,十位=类别

TCP/IP 电子邮件系统详解

目录

  1. 什么是电子邮件系统?
  2. 电子邮件的发展历史
  3. 邮件传输的五个步骤
  4. 邮件通信模型——四个关键角色
  5. 电子邮件地址格式
  6. 邮件消息格式:RFC 822
  7. MIME:让邮件支持图片和附件
  8. SMTP:邮件投递协议
  9. POP3:收取邮件协议
  10. IMAP:更强大的邮件访问协议
  11. 整体架构总结

1. 什么是电子邮件系统?

电子邮件(Email)不是一个单一的协议或程序,而是一个完整的系统,由多个协议和组件共同配合完成。
你可以把它想象成一套现代邮政系统的电子版:

现实邮政系统          电子邮件系统
-----------          -------------
写信            ←→   撰写邮件
投递到邮箱       ←→   提交给 SMTP 服务器
邮局转运        ←→   SMTP 服务器之间传递
送达对方邮箱    ←→   存入收件人邮箱
收件人取信      ←→   POP3 / IMAP 收取邮件

电子邮件的核心优势:

  • 极快速度:几秒内送达全球
  • 一对多:一封信同时发给多人
  • 异步通信:发件人和收件人不需要同时在线

2. 电子邮件的发展历史

时间轴:
1971年
  └─ RFC 196:最早的邮件协议 "Mail Box Protocol",用于远程打印文档
1970年代中期
  └─ 基于 FTP 实现邮件传输(把邮件当文件传)
1980年
  └─ RFC 772:邮件传输协议 MTP(Mail Transfer Protocol),基于 Telnet + FTP
1981年
  └─ SMTP 诞生(RFC 788):第一个专门为邮件设计的传输协议
1982年
  └─ RFC 821(SMTP正式标准)+ RFC 822(邮件消息格式标准)同时发布
1984年
  └─ POP(邮局协议)诞生,让用户可以把邮件下载到本地电脑
1988年
  └─ POP3 发布,成为最流行的邮件收取协议
1992年
  └─ MIME 标准发布,让邮件可以携带图片、音频、附件等
1990年代末
  └─ Web 邮件出现(如 Hotmail),通过浏览器收发邮件
2001年
  └─ RFC 2821(新版 SMTP)+ RFC 2822(新版消息格式)发布

3. 邮件传输的五个步骤

电子邮件从发出到被读取,经历五个标准步骤

步骤1:撰写邮件(Mail Composition)
  用户用邮件客户端写邮件
  邮件 = 邮件头(Header)+ 邮件正文(Body)
         ↓                    ↓
    类似信封上的地址        类似信的内容
步骤2:提交邮件(Mail Submission)
  用户点击"发送",邮件通过 SMTP 提交给本地邮件服务器
  ↓
  类似把信投进邮箱
步骤3:投递邮件(Mail Delivery)
  本地 SMTP 服务器通过 DNS 找到收件人的服务器地址
  然后通过 SMTP 直接发送过去
  ↓
  类似邮局用飞机/卡车运送信件
步骤4:接收与存储(Mail Receipt)
  收件人的 SMTP 服务器收到邮件
  存入收件人的"邮箱(Inbox)"等待取件
  ↓
  类似信件放入收件人的信箱格
步骤5:读取邮件(Mail Access)
  收件人用 POP3 或 IMAP 协议从服务器取回邮件阅读
  ↓
  类似收件人去邮局取信

关键思想:发件人和收件人不需要同时在线。邮件会在服务器上等待,直到收件人来取。

4. 邮件通信模型——四个关键角色

整个邮件系统涉及四个设备/角色

[发件人的电脑]          [发件人的SMTP服务器]
  用户撰写邮件     →   接收并转发邮件
  存入"发件队列"        (通常由ISP运营)
       |                      |
       |   SMTP协议           |   SMTP协议
       ↓                      ↓
[发件人客户端]    →    [收件人的SMTP服务器]
                              |
                              | 存入收件人邮箱
                              ↓
                       [收件人的电脑]
                    用 POP3 或 IMAP 取件

用一张流程图表示:

SMTP

SMTP

邮件存入邮箱

POP3 或 IMAP

发件人电脑
撰写邮件

发件人
SMTP服务器

收件人
SMTP服务器

收件人邮箱

收件人电脑
阅读邮件

5. 电子邮件地址格式

标准格式

邮件地址的格式大家都很熟悉:
用户名 @ 域名 \text{用户名} @ \text{域名} 用户名@域名
例如:zhangsan@gmail.com

  • zhangsan → 用户名(指定是哪个人)
  • gmail.com → 域名(指定邮件服务器在哪里)

DNS MX 记录的作用

当 SMTP 服务器要给 zhangsan@gmail.com 发邮件时,它不直接用域名连接,而是先查询 DNS MX 记录

发件服务器问 DNS:
  "gmail.com 的邮件服务器是哪台?"
DNS 回答:
  "MX记录指向 smtp.gmail.com,IP地址是 x.x.x.x"
发件服务器:
  直接连接 smtp.gmail.com 发送邮件

MX记录的好处

  • 公司可以用 用户@公司名.com 这样好记的地址,背后实际由另一台服务器处理
  • 换服务器时不需要改用户地址
  • 可以设多个服务器分担负载

历史上的特殊地址格式


类型 格式示例 说明
标准DNS user@domain.com 现在通用的格式
UUCP格式 host1!host2!host3!user 早期用感叹号分隔路径,已淘汰
FidoNet格式 p4.f205.n141.z1.fidonet.org 早期独立网络的地址
网关格式 user%domain1@domain2 跨系统网关用,现已少见

6. 邮件消息格式:RFC 822

为什么需要统一格式?

不同电脑、不同软件要能互相读懂邮件,必须遵守同一套格式规范。这个规范叫 RFC 822(2001年更新为 RFC 2822,但习惯上还叫 RFC 822)。

邮件的基本结构

+----------------------------------+
|          邮件头(Header)         |  ← 控制信息(收件人、发件人、日期等)
|  From: alice@example.com         |
|  To: bob@example.com             |
|  Date: Mon, 15 May 2026 10:00:00 |
|  Subject: 你好                   |
+----------------------------------+
|           空行                   |  ← 必须有一个空行分隔
+----------------------------------+
|          邮件正文(Body)         |  ← 实际内容
|  嗨 Bob,                        |
|  这是一封测试邮件...              |
+----------------------------------+

RFC 822 的重要特点

  • 所有内容都是纯 ASCII 文本(美国英文字符)
  • 每行不超过 998 个字符(推荐78个以内)
  • 每行以 回车+换行(CRLF) 结尾

常用邮件头字段说明


字段名 是否必须 作用说明
Date: 必须 邮件发送的日期和时间
From: 必须 发件人邮件地址
To: 通常必须 主要收件人列表
Cc: 可选 抄送收件人(所有人都能看到)
Bcc: 可选 密送收件人(其他收件人不知道)
Subject: 通常存在 邮件主题
Message-ID: 推荐有 邮件唯一标识码
In-Reply-To: 回复时有 指明这是对哪封邮件的回复
Reply-To: 可选 希望对方回复到这个地址

Bcc(密送)是怎么实现的?

发送给:
  To: 张三
  Cc: 李四
  Bcc: 王五  ← 密送
实际情况:
  张三收到:能看到 To(张三)、Cc(李四),看不到 Bcc
  李四收到:能看到 To(张三)、Cc(李四),看不到 Bcc
  王五收到:服务器在发给王五前把 Bcc 字段删掉或修改

7. MIME:让邮件支持图片和附件

RFC 822 的局限性

RFC 822 规定邮件只能包含 ASCII 文本,这意味着:

  • 不能发图片、音频、视频
  • 不能发 Word、Excel 等二进制文件
  • 中文、日文等非英文字符无法正确显示

MIME 的解决思路

MIME(多用途互联网邮件扩展)的核心思想很巧妙:

“RFC 822 规定只能用 ASCII 文本,但没说这些文本不能是经过编码的数据!”
所以 MIME 把二进制数据编码成 ASCII 文本,然后附上说明告诉收件方怎么解码。

原始图片数据(二进制):
  11010100 00100111 11110111 ...(不是合法ASCII)
经过 Base64 编码后(ASCII文本):
  SDc9Pjv/2wBDAQoLCw4NDhwQ...(可以放进邮件)
收件方解码后还原成原始图片

MIME 添加的关键头字段


头字段 作用
MIME-Version: 1.0 标记这是MIME邮件
Content-Type: 说明内容类型(如图片/文本/音频)
Content-Transfer-Encoding: 说明编码方式(如base64)
Content-ID: 内容唯一标识
Content-Description: 内容描述

Content-Type 的格式

Content-Type:  大类型 ⏟ type / 子类型 ⏟ subtype  [; 参数] \text{Content-Type: } \underbrace{\text{大类型}}_{\text{type}} / \underbrace{\text{子类型}}_{\text{subtype}} \text{ [; 参数]} Content-Type: type 大类型/subtype 子类型 [; 参数]
例如:

  • text/plain → 纯文本
  • text/html → HTML网页
  • image/jpeg → JPEG图片
  • audio/mpeg → MP3音频
  • application/pdf → PDF文件
  • multipart/mixed → 混合内容(多个附件)

MIME 支持的内容大类


大类型 含义 常见子类型
text 文本 plain, html, css
image 图片 jpeg, gif, tiff
audio 音频 mpeg(MP3), basic
video 视频 mpeg, quicktime
application 应用文件 pdf, msword, zip
multipart 多部分组合 mixed, alternative
message 嵌套邮件 rfc822
model 三维模型 vrml, iges

多部分邮件(multipart)的结构

当一封邮件包含正文文字和一张图片附件时,结构如下:

邮件头:
  Content-Type: multipart/mixed; boundary="分隔符ABC"
邮件体:
  --分隔符ABC
  Content-Type: text/plain
  你好,这是邮件正文。
  --分隔符ABC
  Content-Type: image/jpeg; name="photo.jpg"
  Content-Transfer-Encoding: base64
  SDc9Pjv/2wBDAQoLCw4NDhwQ...(编码后的图片数据)
  --分隔符ABC--   ← 最后加两个横线表示结束

Base64 编码原理

Base64 的原理是把每 3个字节(24位)变成 4个ASCII字符(每个代表6位):
3  字节 × 8  位 = 24  位 = 4 × 6  位 3 \text{ 字节} \times 8 \text{ 位} = 24 \text{ 位} = 4 \times 6 \text{ 位} 3 字节×8 =24 =4×6 
编码示意:

原始3字节(二进制):
  11010100  00100111  11110111
拆成4个6位组:
  110101 | 000010 | 011111 | 110111
    53       2       31      55
查表得到ASCII字符:
  53→'1'  2→'C'  31→'f'  55→'3'
最终发送: "1Cf3"(4个ASCII字符代替原来3个字节)

Base64 的缺点:数据量会增加约 1 3 \frac{1}{3} 31,因为3字节变成4字符(每字符仍占1字节):
膨胀率 = 4 3 ≈ 133 % \text{膨胀率} = \frac{4}{3} \approx 133\% 膨胀率=34133%

8. SMTP:邮件投递协议

SMTP 是什么?

SMTP(简单邮件传输协议)负责把邮件从一台服务器传送到另一台服务器,就像邮政系统中的运输环节。
SMTP 工作在 TCP 端口 25 上。

现代 SMTP 投递流程(基于 DNS)

早期 SMTP 用"中继"(Relay)方式,像接力赛一样一站一站传。现在有了 DNS 的 MX 记录,可以直接投递:

1. 查询 DNS MX 记录

2. 返回收件人SMTP服务器地址

3. 直接建立 TCP 连接

4. 邮件存入收件箱

发件人 SMTP 服务器

DNS 服务器

收件人 SMTP 服务器

收件人邮箱

SMTP 会话的三个阶段

阶段1:建立连接(握手)
  收件方:220 mail.example.com 服务就绪
  发件方:EHLO smtp.sender.org
  收件方:250-mail.example.com 你好
          250-SIZE         ← 支持的扩展功能列表
          250-DSN
          250 PIPELINING
阶段2:传送邮件(事务)
  发件方:MAIL FROM:<alice@sender.org>   ← 说明发件人
  收件方:250 发件人OK
  发件方:RCPT TO:<bob@example.com>      ← 说明收件人
  收件方:250 收件人OK
  发件方:DATA                           ← 开始发送邮件内容
  收件方:354 请输入邮件,以单独一行的"."结束
  发件方:(发送完整邮件内容)
          .                              ← 单独一行的点表示结束
  收件方:250 邮件已接收
阶段3:关闭连接
  发件方:QUIT
  收件方:221 再见,关闭连接

SMTP 命令速查


命令 作用
HELO 普通问候,开始会话
EHLO 扩展问候,支持新功能
MAIL FROM: 指定发件人地址
RCPT TO: 指定收件人地址(可多次使用)
DATA 开始传输邮件内容
QUIT 结束会话
VRFY 验证邮件地址是否有效
RSET 取消当前邮件事务
NOOP 什么都不做(测试连接用)

SMTP 回复码结构

回复码是三位数字 x y z xyz xyz,每位有特定含义:
x ⏟ 结果类型 y ⏟ 功能分类 z ⏟ 具体含义 \underbrace{x}_{\text{结果类型}} \underbrace{y}_{\text{功能分类}} \underbrace{z}_{\text{具体含义}} 结果类型 x功能分类 y具体含义 z
第一位(x)表示成功/失败

第一位 含义
2xx 成功完成
3xx 命令接受,等待更多信息
4xx 临时错误(可以重试)
5xx 永久错误(不要重试)

常见回复码

回复码 含义
220 服务就绪(连接成功)
250 命令执行成功
354 请开始发送邮件内容
221 再见,关闭连接
421 服务暂时不可用
550 邮件地址不存在/拒绝
552 邮箱已满

SMTP 安全问题

SMTP 早期设计时互联网很小,大家互相信任,因此没有任何安全机制。这带来了严重问题:

  • 垃圾邮件(Spam):攻击者可以用任意发件人地址发大量邮件
  • 身份伪造:SMTP 本身无法验证发件人身份
  • 开放中继:早期服务器会帮任何人转发邮件
    现代的防护措施:
  • 检查连接 IP,只接受授权来源
  • 限制中继功能
  • 要求 AUTH 身份验证
  • 限制单次发送数量和邮件大小
  • 记录所有访问日志

9. POP3:收取邮件协议

POP3 是什么?

POP3(邮局协议第3版)是最流行的邮件收取协议,工作在 TCP 端口 110
它使用离线模式:把邮件从服务器下载到本地,通常下载后从服务器删除。

服务器端                本地电脑
  [邮箱]  ──POP3──>   [邮件客户端]
  (清空)              (保存邮件)

POP3 会话的三个状态

POP3 会话是线性的,依次经过三个状态:

登录成功

发送QUIT

登录失败

TCP连接建立

授权状态
Authorization

事务状态
Transaction

更新状态
Update

连接断开

连接结束

状态1:授权状态(Authorization)

服务器:+OK POP3 服务就绪
客户端:USER bob@example.com
服务器:+OK
客户端:PASS 123456
服务器:+OK bob@example.com 有 3 封邮件

状态2:事务状态(Transaction)

客户端:STAT                  ← 查询邮箱状态
服务器:+OK 3 1500            ← 3封邮件,共1500字节
客户端:LIST                  ← 列出所有邮件
服务器:+OK
        1 600
        2 500
        3 400
        .                     ← 单独一行的点表示列表结束
客户端:RETR 1                ← 下载第1封邮件
服务器:+OK
        (邮件内容)
        .
客户端:DELE 1                ← 标记第1封为待删除
服务器:+OK 邮件1已标记删除

状态3:更新状态(Update)

客户端:QUIT                  ← 退出
(服务器此时才真正删除被标记的邮件)
服务器:+OK 再见

POP3 命令速查


命令 作用
USER 用户名 发送用户名
PASS 密码 发送密码
STAT 查询邮箱中邮件数量和总大小
LIST 列出所有邮件编号和大小
RETR 编号 下载指定编号的邮件
DELE 编号 标记指定邮件为待删除
RSET 撤销所有删除标记
TOP 编号 行数 只下载邮件头和前N行(可选)
QUIT 退出并执行实际删除

POP3 的优缺点

优点

  • 实现简单,几乎所有客户端都支持
  • 下载后可离线阅读
  • 占用服务器空间少
    缺点
  • 邮件下载到一台电脑后,其他设备看不到
  • 没有服务器端邮件夹管理
  • 难以追踪哪些邮件已读/未读

10. IMAP:更强大的邮件访问协议

IMAP 与 POP3 的根本区别

IMAP(互联网消息访问协议)让邮件始终保留在服务器上,支持多设备同步访问。

POP3 模式(离线):         IMAP 模式(同步):
  服务器 ──下载──> 手机       服务器 <──> 手机
  (服务器删除)               服务器 <──> 电脑
                              服务器 <──> 平板
                              (三端同步,都能看到)

IMAP 会话的四个状态

LOGIN 或 AUTHENTICATE 成功

SELECT 或 EXAMINE 成功

CLOSE 关闭邮箱

SELECT 新邮箱

LOGOUT

LOGOUT

LOGOUT

TCP连接建立

未认证状态
Not Authenticated

已认证状态
Authenticated

已选择状态
Selected

登出状态
Logout

连接断开

IMAP 的核心功能

状态1:未认证状态 - 只能登录

服务器:* OK mail.example.com 服务就绪
客户端:a001 LOGIN bob@example.com 密码
服务器:a001 OK 登录成功

注意:IMAP 每条命令前有一个标签(如 a001),服务器回复时带同样标签,这样可以并发发送多条命令。
状态2:已认证状态 - 管理邮件夹

客户端:a002 LIST "" "*"          ← 列出所有邮件夹
服务器:* LIST () "/" INBOX
        * LIST () "/" Sent
        * LIST () "/" Drafts
        a002 OK LIST 完成
客户端:a003 SELECT INBOX          ← 选择收件箱
服务器:* 15 EXISTS                ← 共15封邮件
        * 3 RECENT                 ← 3封新邮件
        * FLAGS (\Seen \Answered \Flagged \Deleted \Draft)
        a003 OK INBOX 已选择

状态3:已选择状态 - 操作具体邮件

客户端:a004 FETCH 1:3 (FLAGS ENVELOPE)  ← 获取第1-3封邮件的标志和摘要
客户端:a005 SEARCH UNSEEN               ← 搜索未读邮件
服务器:* SEARCH 2 5 7                   ← 第2、5、7封未读
客户端:a006 STORE 2 +FLAGS (\Seen)      ← 标记第2封为已读
客户端:a007 FETCH 5 BODY[]              ← 下载第5封完整内容

IMAP 命令分组


状态 可用命令
任意状态 CAPABILITY, NOOP, LOGOUT
未认证状态 LOGIN, AUTHENTICATE, STARTTLS
已认证状态 SELECT, EXAMINE, CREATE, DELETE, RENAME, LIST, STATUS, APPEND
已选择状态 FETCH, STORE, SEARCH, COPY, EXPUNGE, CLOSE, CHECK

IMAP vs POP3 对比


对比项 POP3 IMAP
邮件保存位置 下载到本地 保留在服务器
多设备支持 差(只有一台有邮件) 好(所有设备同步)
服务器端文件夹 不支持 支持
部分下载 不支持 支持(只下载头部或附件)
搜索功能 需要本地搜索 服务器端搜索
协议复杂度 简单 较复杂
端口 110 143

11. 整体架构总结

各协议的分工

SMTP
提交邮件

SMTP
服务器间传输

POP3 或 IMAP
收取邮件

邮件客户端
Outlook/Thunderbird

发件人
SMTP服务器

收件人
SMTP服务器

邮件客户端
Outlook/Thunderbird

协议端口总结


协议 端口 作用
SMTP 25 服务器之间传送邮件
SMTP(提交) 587 客户端向服务器提交邮件
POP3 110 客户端收取邮件(离线模式)
IMAP 143 客户端访问邮件(同步模式)

邮件格式的层次关系

一封完整的邮件(MIME格式)
├── RFC 822 邮件头
│   ├── From:、To:、Date:、Subject: 等
│   └── MIME-Version:、Content-Type: 等MIME头
└── 邮件正文
    ├── 如果是纯文本:直接就是文字
    └── 如果是MIME多部分:
        ├── 第一部分:正文文字(text/plain 或 text/html)
        ├── 第二部分:图片附件(image/jpeg)Base64编码
        └── 第三部分:文件附件(application/pdf)Base64编码

三种邮件访问模式对比


模式 代表协议 邮件保存在 适合场景
在线模式 Telnet直连 / IMAP 服务器 专业用户,多地访问
离线模式 POP3 本地电脑 单台设备,低带宽
断开同步模式 IMAP 服务器+本地副本 多设备用户,需要离线阅读
Web模式 HTTP(浏览器) 服务器 临时设备,无需安装客户端

TCP/IP 万维网(WWW)与 HTTP 协议详解

目录

  1. 万维网是什么?
  2. 超文本与 HTML
  3. URL 地址格式
  4. HTTP 版本演进
  5. HTTP 的工作模型
  6. HTTP 连接方式:短连接与持久连接
  7. HTTP 消息格式
  8. HTTP 方法(命令)
  9. HTTP 状态码
  10. HTTP 头部字段详解
  11. HTTP 实体与编码
  12. 内容协商与质量值
  13. HTTP 缓存机制
  14. 代理服务器
  15. HTTP 安全与 Cookie
  16. 整体架构总结

1. 万维网是什么?

一句话理解

万维网(WWW,World Wide Web)不是互联网本身,而是运行在互联网上的一个应用系统,它让我们可以通过点击链接,在无数文档之间自由跳转。

超文本的核心思想

在没有超文本之前,文档是"孤岛":

文档A           文档B           文档C
 [内容]          [内容]          [内容]
   ↑               ↑               ↑
互相不知道彼此的存在

有了超文本之后:

文档A ──点击链接──> 文档B ──点击链接──> 文档C
  ↑                   |                   |
  └───────────────────┴───────────────────┘
              相互连接,形成"网"

这就是 Web(网)这个名字的由来。

发展历史时间线

1945年
  └─ Vannevar Bush 提出"Memex"概念:用"索引路径"连接相关文档
1960年
  └─ Ted Nelson 创造"hypertext(超文本)"这个词,设计 Xanadu 系统
1989年
  └─ Tim Berners-Lee 在 CERN(欧洲核研究组织)提出万维网构想
1990年
  └─ 第一个极简版 HTTP(HTTP/0.9)诞生,只能传输文本
1993年
  └─ 第一个图形化浏览器 Mosaic 诞生(NCSA 开发)
     ↑ Marc Andreessen 后来创立了 Netscape
1993年初:全球仅 50 台 HTTP 服务器
1993年底:超过 1000 台
1995年底:每天数千个新网站上线
1996年
  └─ HTTP/1.0 正式发布(RFC 1945)
1999年
  └─ HTTP/1.1 正式发布(RFC 2616),沿用至今

万维网的三大核心组件

万维网 WWW

HTML
超文本标记语言
描述文档长什么样

HTTP
超文本传输协议
在网络上传输文档

URI/URL
统一资源定位符
给每个资源一个唯一地址

Web浏览器
解析并展示HTML

2. 超文本与 HTML

HTML 是什么?

HTML(HyperText Markup Language,超文本标记语言)是一种用标签来描述文档结构的语言。本质上是一个 ASCII 文本文件,但加了特殊的标记。

HTML 的基本结构

<html>              <!-- 整个文档的根标签 -->
  <head>            <!-- 头部:文档的元信息(标题、编码等) -->
    <title>网页标题</title>

  </head>

  <body>            <!-- 正文:用户看到的内容 -->
    <!-- 各种内容标签放这里 -->
  </body>

</html>

常用 HTML 标签速查


标签 作用 示例
<p>...</p> 段落 <p>这是一段文字</p>
<h1>~<h6> 标题(从大到小) <h1>大标题</h1>
<br> 换行 第一行<br>第二行
<b>...</b> 加粗 <b>加粗文字</b>
<i>...</i> 斜体 <i>斜体文字</i>
<a href="URL"> 超链接 <a href="http://www.example.com">点我</a>
<img src="URL"> 显示图片 <img src="logo.gif">
<ul><li> 无序列表 显示为项目符号列表
<table><tr><td> 表格 行列布局
<form> 表单 用户输入和提交数据

超链接的工作原理

HTML 文件中:
  <a href="http://www.pcguide.com">点击访问PC指南</a>

用户点击后:
  浏览器读取 href 属性中的 URL
       ↓
  发送 HTTP GET 请求到 www.pcguide.com
       ↓
  服务器返回对应的 HTML 文件
       ↓
  浏览器显示新页面

3. URL 地址格式

URL 的完整语法

http ⏟ 协议 :// user:pass ⏟ 认证(可选) @ host ⏟ 主机名 : port ⏟ 端口 /path ⏟ 路径 ? query ⏟ 查询 # bookmark ⏟ 书签 \underbrace{\text{http}}_{\text{协议}} \text{://} \underbrace{\text{user:pass}}_{\text{认证(可选)}} \text{@} \underbrace{\text{host}}_{\text{主机名}} \text{:} \underbrace{\text{port}}_{\text{端口}} \underbrace{\text{/path}}_{\text{路径}} \text{?} \underbrace{\text{query}}_{\text{查询}} \text{\#} \underbrace{\text{bookmark}}_{\text{书签}} 协议 http://认证(可选) user:pass@主机名 host:端口 port路径 /path?查询 query#书签 bookmark

各部分的含义


部分 含义 示例 是否必须
http:// 使用 HTTP 协议 http:// 必须
user:pass@ 身份验证信息 admin:123@ 可选,少见
host 服务器域名或IP www.example.com 必须
:port 端口号 :8080(默认80可省略) 可选
/path 资源路径 /news/article.html 通常有
?query 查询参数 ?id=123&page=2 可选
#bookmark 页面内书签 #chapter3 可选

实际例子解析

http://www.myfavoritewebsite.com:8080/chatware/chatroom.php
协议:   http
主机:   www.myfavoritewebsite.com
端口:   8080(非默认80)
路径:   /chatware/chatroom.php

浏览器发送给服务器的请求:

GET /chatware/chatroom.php HTTP/1.1
Host: www.myfavoritewebsite.com:8080

注意:http:// 和主机名被分开了:路径放在请求行,主机名放在 Host 头部。

默认文档

当 URL 只有域名没有路径时,如 http://www.cnn.com

用户输入:http://www.cnn.com
         ↓
服务器自动返回默认文档(通常是 index.html 或 default.html)
         ↓
等同于访问:http://www.cnn.com/index.html

4. HTTP 版本演进

HTTP/0.9
1990年
只能GET文本

HTTP/1.0
1996年
RFC 1945
支持多媒体
借鉴MIME

HTTP/1.1
1999年
RFC 2616
持久连接
缓存增强
虚拟主机

各版本核心特点对比


特性 HTTP/0.9 HTTP/1.0 HTTP/1.1
文件类型 只有文本 多媒体 多媒体
连接方式 每次新建 每次新建 持久连接(默认)
虚拟主机 不支持 不支持 支持(一IP多域名)
缓存支持 基础 完整(26页规范)
管线化 支持
分块传输 支持
内容协商 基础 完整

HTTP/1.0 的主要问题

HTTP/1.0 时代出现了"万维网等待"(World Wide Wait)这个讽刺称号,原因:

加载一个网页需要:
  1. 下载 HTML 文件          → 建立TCP连接 → 请求 → 响应 → 关闭TCP
  2. 下载图片1               → 建立TCP连接 → 请求 → 响应 → 关闭TCP
  3. 下载图片2               → 建立TCP连接 → 请求 → 响应 → 关闭TCP
  4. 下载图片3               → 建立TCP连接 → 请求 → 响应 → 关闭TCP
  ... (一个页面可能有几十张图片)
每次建立 TCP 连接都要三次握手,浪费大量时间!

5. HTTP 的工作模型

基本通信:请求/响应

HTTP 是最简单的一类协议——客户问,服务器答:

HTTP请求 Request

HTTP响应 Response

Web浏览器
HTTP客户端

Web服务器
HTTP服务端

带中间代理的通信链

请求

转发请求

转发请求

响应

转发响应

转发响应

客户端

代理1

代理2

Web服务器

这整条路径称为请求/响应链(Request/Response Chain)。
代理的特点:

  • 对客户端来说,代理像服务器
  • 对服务器来说,代理像客户端
  • 可以透明转发,也可以修改消息(添加缓存、过滤内容等)

缓存对通信链的影响

正常流程(无缓存):
  客户端 → 代理1 → 代理2 → 服务器
                              ↓(找到资源)
  客户端 ← 代理1 ← 代理2 ← 服务器
有缓存时(代理2有缓存):
  客户端 → 代理1 → 代理2
                    ↓(缓存命中!)
  客户端 ← 代理1 ← 代理2
                    (服务器完全不知道这次请求的存在)

6. HTTP 连接方式:短连接与持久连接

短连接(HTTP/0.9 和 HTTP/1.0)

短连接模式(每次请求都要重新握手):
浏览器请求HTML:
  [TCP三次握手] → [HTTP请求] → [HTTP响应] → [TCP四次挥手]
浏览器请求图片1:
  [TCP三次握手] → [HTTP请求] → [HTTP响应] → [TCP四次挥手]
浏览器请求图片2:
  [TCP三次握手] → [HTTP请求] → [HTTP响应] → [TCP四次挥手]
... 浪费大量时间在握手上

类比:每问一个问题,就挂断电话,然后重新拨号,再问下一个问题。

持久连接(HTTP/1.1 默认)

持久连接模式:
[TCP三次握手] → 一次建立,多次使用
                ↓
  [HTTP请求HTML]  → [HTTP响应HTML]
  [HTTP请求图片1] → [HTTP响应图片1]
  [HTTP请求图片2] → [HTTP响应图片2]
  [HTTP请求图片3] → [HTTP响应图片3]
                ↓
[TCP四次挥手] → 全部完成后才断开

管线化(Pipelining)

持久连接的进一步优化:不等上一个响应,就发下一个请求。

普通持久连接(顺序):
  → 请求A         → 请求B         → 请求C
           ← 响应A         ← 响应B         ← 响应C
管线化(流水线):
  → 请求A → 请求B → 请求C
  ← 响应A ← 响应B ← 响应C
  (请求连续发出,响应按顺序返回)

关闭连接

客户端在最后一次请求中加上:

Connection: close

服务器处理完后关闭 TCP 连接。

二进制指数退避

当服务器忙碌而意外断开连接时,客户端重试不能立刻疯狂重连,而是采用二进制指数退避
等待时间 = 2 n  秒(n为重试次数) \text{等待时间} = 2^n \text{ 秒(n为重试次数)} 等待时间=2n 秒(n为重试次数)
例如:第1次失败等1秒,第2次失败等2秒,第3次等4秒,第4次等8秒……这样避免加重服务器负担。

7. HTTP 消息格式

通用消息结构

所有 HTTP 消息(不管是请求还是响应)都遵循同一个基本格式:

┌─────────────────────────────┐
│         起始行 (Start Line)  │  ← 请求行 或 状态行
├─────────────────────────────┤
│       消息头部 (Headers)     │  ← 多行,每行 "名字: 值"
│  Header1: Value1            │
│  Header2: Value2            │
│  ...                        │
├─────────────────────────────┤
│         空行 (CRLF)         │  ← 标志头部结束
├─────────────────────────────┤
│     消息正文 (Body)          │  ← 可选,携带实际数据
│  (文件内容、表单数据等)     │
├─────────────────────────────┤
│    消息尾部 (Trailers)       │  ← 可选,分块传输时使用
└─────────────────────────────┘

请求消息格式

GET /chatware/chatroom.php HTTP/1.1       ← 请求行:方法 路径 版本
Host: www.myfavoritewebsite.com:8080      ← 请求头(必须有Host)
User-Agent: Mozilla/5.0 ...              ← 告诉服务器浏览器类型
Accept: text/html,image/jpeg             ← 告诉服务器客户端能接受的格式
Accept-Language: zh-CN,en;q=0.9         ← 语言偏好
Connection: keep-alive                   ← 要求持久连接
                                          ← 空行(头部结束)
(通常GET请求没有正文)

响应消息格式

HTTP/1.1 200 OK                          ← 状态行:版本 状态码 原因短语
Date: Mon, 15 May 2026 10:00:00 GMT      ← 响应日期
Content-Type: text/html; charset=utf-8  ← 告诉客户端内容类型
Content-Length: 1256                     ← 内容长度(字节)
Server: Apache/2.4                       ← 服务器软件信息
                                          ← 空行(头部结束)
<html>                                   ← 响应正文(HTML文件内容)
  <body>
    <h1>Hello World!</h1>

  </body>

</html>

8. HTTP 方法(命令)

HTTP 用"方法"(Method)这个词代替"命令",来自面向对象编程的概念。

三大常用方法

GET - 获取资源

用途:请求服务器把指定资源发给我
场景:在浏览器地址栏输入URL、点击超链接
示例:
  GET /news/article.html HTTP/1.1
  Host: www.example.com

HEAD - 只要头部,不要正文

用途:和 GET 一样,但服务器只返回头部(不含文件内容)
场景:检查文件是否存在、获取文件大小、查看最后修改时间
示例:
  HEAD /bigfile.zip HTTP/1.1
  Host: www.example.com
  (响应中有文件大小等信息,但不传输文件本身)

POST - 提交数据给服务器

用途:把数据发送给服务器上的某个程序处理
场景:提交登录表单、发布评论、网上购物结账
示例:
  POST /login.php HTTP/1.1
  Host: www.example.com
  Content-Type: application/x-www-form-urlencoded
  Content-Length: 27
  username=alice&password=123

其他方法


方法 作用 是否常用
GET 获取资源 最常用
HEAD 只获取头部信息 常用
POST 提交数据 常用
PUT 上传文件到指定位置 少用
DELETE 删除指定资源 少用
OPTIONS 查询服务器支持哪些方法 少用
TRACE 回显请求,用于调试 诊断用

安全方法 vs 幂等方法

安全方法:不会对服务器造成改变的方法

  • 安全:GET、HEAD、OPTIONS、TRACE
  • 不安全:POST(提交数据会改变服务器状态)、PUT、DELETE
    幂等方法:重复执行多次,结果和执行一次相同
    f ( f ( x ) ) = f ( x ) (幂等的数学定义) f(f(x)) = f(x) \quad \text{(幂等的数学定义)} f(f(x))=f(x)(幂等的数学定义)
  • 幂等:GET、HEAD(多次获取同一资源,结果一样)
  • 非幂等:POST(提交两次订单 = 下了两笔订单,结果不同)

实际意义:非幂等方法(如POST)不应该使用管线化,因为如果连接中断需要重试,重复执行可能造成问题。

9. HTTP 状态码

状态码格式

HTTP 状态码是三位数 x y z xyz xyz,第一位表示大类:
x ⏟ 类别 y y ⏟ 具体编号 \underbrace{x}_{\text{类别}} \underbrace{yy}_{\text{具体编号}} 类别 x具体编号 yy

第一位 含义 通俗理解
1xx 信息性消息 “收到了,继续…”
2xx 成功 “搞定!”
3xx 重定向 “不在这里,去那里找”
4xx 客户端错误 “你的请求有问题”
5xx 服务器错误 “我这边出问题了”

常见状态码详解

2xx 成功类

状态码 原因短语 含义
200 OK 请求成功,资源已返回
201 Created 资源创建成功(常用于PUT)
204 No Content 成功但无返回内容
206 Partial Content 部分内容(用于断点续传)

3xx 重定向类

状态码 原因短语 含义
301 Moved Permanently 永久移走了,以后用新URL
302 Found 临时移到别处,还用原URL
304 Not Modified 资源没变,用你缓存的版本
307 Temporary Redirect 临时重定向(和302类似)

4xx 客户端错误

状态码 原因短语 含义
400 Bad Request 请求格式错误
401 Unauthorized 需要身份验证
403 Forbidden 服务器拒绝,无关权限
404 Not Found 资源不存在(最常见的错误)
405 Method Not Allowed 该资源不支持此方法
408 Request Timeout 请求超时

5xx 服务器错误

状态码 原因短语 含义
500 Internal Server Error 服务器内部错误
501 Not Implemented 服务器不支持此功能
503 Service Unavailable 服务器暂时不可用(过载/维护)
504 Gateway Timeout 网关等待上游服务器超时

100 Continue 的特殊用途

当客户端要上传一个很大的文件时,可以先问服务器"你接受吗?"

客户端发送:
  POST /upload HTTP/1.1
  Expect: 100-continue
  Content-Length: 100000000   ← 1亿字节,很大!
服务器回应:
  HTTP/1.1 100 Continue       ← "可以,发过来吧"
  或
  HTTP/1.1 413 Entity Too Large ← "太大了,我不收"
如果是 100:客户端继续发送文件内容
如果是错误:客户端不必浪费流量传输大文件

10. HTTP 头部字段详解

HTTP 的大部分功能都通过头部字段来实现。头部字段分四大类:

HTTP 头部字段

通用头部
General Headers
请求和响应都可用

请求头部
Request Headers
只用于请求消息

响应头部
Response Headers
只用于响应消息

实体头部
Entity Headers
描述消息体中的资源

通用头部(General Headers)


头部名 作用 示例
Cache-Control 缓存控制指令 Cache-Control: no-cache
Connection 连接管理 Connection: close
Date 消息发出时间 Date: Mon, 15 May 2026 10:00:00 GMT
Transfer-Encoding 传输编码方式 Transfer-Encoding: chunked
Via 经过哪些代理 Via: 1.1 proxy.example.com

请求头部(Request Headers)


头部名 作用
Host 目标主机名(HTTP/1.1 中唯一强制头部)
Accept 客户端能接受的媒体类型
Accept-Language 客户端偏好的语言
Accept-Encoding 客户端支持的压缩方式
User-Agent 浏览器/客户端名称和版本
Referer 从哪个页面跳转过来的
Authorization 身份验证凭据
If-Modified-Since 条件请求:只有资源修改过才发送
Range 请求资源的某一部分(断点续传)

响应头部(Response Headers)


头部名 作用
Server 服务器软件信息
Location 重定向目标URL
WWW-Authenticate 告诉客户端如何认证
ETag 资源的唯一标识(用于缓存验证)
Age 资源在缓存中存放的时间

实体头部(Entity Headers)


头部名 作用
Content-Type 资源的媒体类型(如 text/html
Content-Length 资源的字节数
Content-Encoding 资源的压缩方式(如 gzip
Content-Language 资源使用的语言
Last-Modified 资源最后修改时间
Expires 资源过期时间

Cache-Control 指令详解

缓存控制是 HTTP/1.1 最复杂的头部之一:

指令 用于 含义
no-cache 请求/响应 每次使用前必须向服务器验证
no-store 请求/响应 绝对不要缓存(敏感数据用)
max-age=秒数 请求/响应 缓存最长有效时间
public 响应 允许共享缓存存储
private 响应 只允许私有缓存(不能存到代理)
must-revalidate 响应 过期后必须向服务器重新验证

11. HTTP 实体与编码

实体(Entity)是什么?

在 HTTP 中,消息正文中携带的数据叫做实体。通常就是服务器返回的文件(HTML、图片、视频等)。

HTTP 与 MIME 的关系

HTTP 借鉴了邮件系统的 MIME 规范,但不完全兼容

MIME(用于邮件):              HTTP(用于Web):
- 只能用 ASCII 文本            - 可以直接传二进制数据
- 需要 Base64 编码图片         - 不需要 Base64,直接传字节
- 有 MIME-Version 头           - 没有 MIME-Version 头
- Content-Transfer-Encoding   - 不用这个头

关键区别:MIME 必须把图片用 Base64 编码变成文字再传(因为邮件系统只支持文字),而 HTTP 可以直接传二进制字节,效率更高。
Base64 编码会增加 1 3 \frac{1}{3} 31 的数据量:
HTTP 不需要 Base64,节省了 ≈ 33 %  的传输量 \text{HTTP 不需要 Base64,节省了} \approx 33\% \text{ 的传输量} HTTP 不需要 Base64,节省了33% 的传输量

HTTP 的两级编码体系

HTTP 把编码分成了两层,比 MIME 更灵活:

内容编码 Content-Encoding
端到端,整个传输过程有效

传输编码 Transfer-Encoding
逐跳,每一跳单独决定

接收端

原始数据

编码后的实体
例如:gzip压缩的HTML

传输中的消息
例如:分块传输

先还原传输编码
再还原内容编码

原始数据

内容编码(Content Encoding):

  • 作用于实体本身
  • 整个端到端有效
  • 主要用于压缩:gzipdeflatecompress
    传输编码(Transfer Encoding):
  • 作用于整条消息
  • 每一跳可以不同
  • 主要用于解决消息长度问题:chunked(分块传输)

分块传输(Chunked Transfer)

当服务器不知道响应有多长时(动态生成的内容),用分块传输:

HTTP/1.1 200 OK
Content-Type: text/html
Transfer-Encoding: chunked
Trailer: Expires
29                                 ← 第1块的长度(16进制,= 41字节)
<html><body><p>The file you requested is 
5                                  ← 第2块长度(5字节)
3,400
23                                 ← 第3块长度(35字节)
bytes long and was last modified: 
1d                                 ← 第4块长度(29字节)
Sun, 20 Mar 2005 21:12:00 GMT
13                                 ← 第5块长度(19字节)
.</p></body></html>

0                                  ← 长度为0,表示结束
Expires: Sun, 27 Mar 2005 21:12:00 GMT   ← 尾部字段(Trailer)

块长度都用十六进制表示,0 代表最后一块。
对比普通响应(已知长度):

HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 129              ← 直接告诉长度
<html><body>...</body></html>

12. 内容协商与质量值

为什么需要内容协商?

同一个资源可能有多个版本:

  • 同一篇文章有中文版、英文版、法文版
  • 同一张图片有 JPEG 版(小文件)和 TIFF 版(高质量)
    内容协商让客户端和服务器商量,返回最合适的版本。

两种协商方式

服务器驱动协商:客户端说"我的偏好是…",服务器自己选:

Accept: text/html, text/plain;q=0.5
Accept-Language: zh-CN, en;q=0.8
Accept-Encoding: gzip, deflate

代理驱动协商:服务器先返回可用版本列表,客户端再选:

第一步:客户端请求资源
第二步:服务器返回 300 Multiple Choices(有多个版本)
第三步:客户端选择想要的版本,再次请求

质量值(Quality Value)

客户端用 q=数值 来表示偏好程度,范围 [ 0 , 1 ] [0, 1] [0,1]

  • q = 1 q = 1 q=1:最喜欢(不写 q 值时默认为 1)
  • q = 0.5 q = 0.5 q=0.5:一般
  • q = 0 q = 0 q=0:绝对不要
    例子:三语用户,偏好中文,其次西班牙语,法语凑合,德语不要:
Accept-Language: zh-CN, sp;q=0.7, fr;q=0.3, de;q=0

翻译成人话:“请给我中文版;没有的话西班牙语也行;再没有法语也凑合;反正不要德语。”
Accept 头的通配符用法:

Accept: text/html, text/*;q=0.6, */*;q=0.1

含义:

  • 最想要 HTML 文档(q=1)
  • 其他文本格式也行(q=0.6)
  • 实在不行,什么格式都收(q=0.1)

13. HTTP 缓存机制

缓存的核心思想

Web 用户经常反复请求同样的资源。与其每次都从服务器取,不如把最近用过的存起来:

不用缓存(每次都要完整走一遍):
  客户端 → 代理1 → 代理2 → 服务器
  客户端 ← 代理1 ← 代理2 ← 服务器
使用缓存(代理2有缓存,直接返回):
  客户端 → 代理1 → 代理2
  客户端 ← 代理1 ← 代理2
                    服务器根本不知道有这次请求!

好处:

  • 减少网络流量
  • 加快响应速度(就近取,不用跑远路)

缓存在哪里?

[用户电脑上的浏览器缓存]  ←── 最近,只有你自己受益
         ↑
[公司/ISP 代理服务器缓存] ←── 居中,整个组织受益
         ↑
[Web 服务器缓存]          ←── 最远,所有用户受益,
                               但每次还是要走网络

三角关系:离用户越近 = 个人受益越大;离用户越远 = 受益人数越多。

缓存的主要问题

缓存老化(Staleness):缓存里的内容可能已经过时。

你今天早上缓存了 CNN 首页
你下午再看,但缓存里还是早上的新闻
用户看到的是"旧新闻"——这就是缓存过期问题

解决方法:

  • 服务器在响应头中加 ExpiresCache-Control: max-age=秒数 告诉缓存多久过期
  • 缓存过期后,必须向服务器验证(Validation)是否真的变了
    条件请求用于验证:
客户端(有旧缓存):
  GET /news.html HTTP/1.1
  If-Modified-Since: Mon, 14 May 2026 08:00:00 GMT
服务器回应(如果没变):
  HTTP/1.1 304 Not Modified   ← 没变,用你的缓存就行,我不发内容了
服务器回应(如果变了):
  HTTP/1.1 200 OK             ← 变了,给你新版本
  (附带新的 HTML 内容)

304 状态码节省的流量

假设一个网页 200KB,每天被访问 100 万次,内容很少改变:

  • 没有 304 机制:每次传 200KB → 每天 200 KB × 100 万 = 200 GB 200\text{KB} \times 100\text{万} = 200\text{GB} 200KB×100=200GB
  • 有 304 机制:大部分请求只返回几十字节的"没变" → 流量可以减少 90 % 90\% 90% 以上

14. 代理服务器

代理是什么?

代理(Proxy)是位于客户端和服务器之间的中间人:

发给代理

代客户端请求

返回给代理

转发给客户端

客户端

代理服务器

真实Web服务器

代理的主要用途

安全过滤

客户端的请求 → 代理检查 → 发现包含恶意内容/访问违规网站 → 拦截

缓存加速

用户A请求 baidu.com → 代理获取并缓存
用户B请求 baidu.com → 代理直接从缓存返回(不去服务器)

卫星网络延迟优化(实例):

问题:卫星来回延迟 500ms,一个有 20 张图片的页面需要:
  500ms(HTML)+ 20×500ms(每张图)= 10500ms ≈ 10秒!
解决:ISP 的代理预取页面中所有图片:
  500ms(HTML + 代理同时获取所有图片)= 约 1-2秒

代理 vs 缓存的区别


对比项 代理服务器 缓存
是什么 独立的中间设备 可以在任何设备上实现
如何使用 客户端需要手动配置 自动启用
主要功能 转发+过滤+缓存+认证 存储最近使用的资源
关系 代理可以包含缓存 缓存可以在代理里

15. HTTP 安全与 Cookie

HTTP 认证方式

基本认证(Basic Auth)

1. 客户端请求受保护资源:
   GET /secret.html HTTP/1.1
2. 服务器要求认证:
   HTTP/1.1 401 Unauthorized
   WWW-Authenticate: Basic realm="My Site"
3. 客户端发送凭据(Base64编码的 用户名:密码):
   GET /secret.html HTTP/1.1
   Authorization: Basic dXNlcjpwYXNz
4. 验证通过,返回资源

问题:Base64 只是编码,不是加密,容易被截获。
摘要认证(Digest Auth):使用 MD5 等加密算法,比基本认证更安全,但实现更复杂。

HTTPS = HTTP + SSL

对于敏感数据(网银、购物、登录),需要使用 HTTPS:

普通 HTTP:
  http://www.bank.com   → 端口 80,明文传输,可被监听
安全 HTTPS:
  https://www.bank.com  → 端口 443,SSL/TLS 加密,无法被监听

Cookie:状态管理

HTTP 本身是无状态的:服务器处理完每个请求就"忘记"了,不记得上一次请求是谁。
这对购物车、登录状态等应用来说是大问题:

无状态的问题:
  第1次请求:用户登录
  第2次请求:服务器不认识你了,要你再登录
  第3次请求:服务器还是不认识你...

Cookie 解决了这个问题:

1. 第一次请求

2. 返回响应 + 设置Cookie
Set-Cookie: user_id=12345

3. 以后每次请求都带上Cookie
Cookie: user_id=12345

4. 服务器认出你了

客户端

服务器

Cookie 就是服务器存到客户端电脑上的一小块数据,记录用户信息。

Cookie 的隐患


隐患 描述
敏感信息泄露 Cookie 被截获可能暴露登录信息
跨站追踪 广告商通过第三方 Cookie 追踪你访问了哪些网站
第三方 Cookie 加载了广告图片的网站可以悄悄给你设 Cookie

第三方 Cookie 的例子

你访问 www.news.com:
  → 页面里有一个来自 ads.tracker.com 的广告图片
  → ads.tracker.com 给你设置了 Cookie
  → 下次你访问 www.shopping.com(也有该广告商的广告)
  → ads.tracker.com 看到了你的 Cookie
  → 它知道你同时访问了 news.com 和 shopping.com
  → 开始给你推送相关广告

这就是为什么现代浏览器开始限制第三方 Cookie。

16. 整体架构总结

一次完整的 Web 访问流程

用户在浏览器输入 http://www.example.com/index.html,到页面显示出来的全过程:

步骤1:DNS 解析
  www.example.com → 查询 DNS → 得到 IP 地址 1.2.3.4
步骤2:建立 TCP 连接
  浏览器 → TCP 三次握手 → 1.2.3.4:80
步骤3:发送 HTTP 请求
  GET /index.html HTTP/1.1
  Host: www.example.com
  Accept: text/html
  ...
步骤4:服务器处理并响应
  HTTP/1.1 200 OK
  Content-Type: text/html
  Content-Length: 5000
  ...
  (HTML内容)
步骤5:浏览器解析 HTML
  发现有 3 张图片引用 → 发出 3 个新的 GET 请求(持久连接,不重新握手)
步骤6:接收图片数据,渲染页面
步骤7:发送 Connection: close,关闭 TCP 连接

各协议和技术的分工

HTTP/1.1

HTTPS

用户操作
输入URL/点击链接

URL解析
提取主机名和路径

DNS解析
域名→IP地址

TCP连接
端口80/443

HTTP版本?

持久连接
可管线化

SSL/TLS加密层
然后HTTP

发送HTTP请求
GET/POST/HEAD...

缓存命中?

直接返回缓存
304 Not Modified

服务器处理
返回200 OK + 资源

浏览器解析
渲染显示

端口号速查


协议 端口 说明
HTTP 80 标准网页访问
HTTPS 443 加密网页访问
HTTP(备用) 8080 有时用于测试环境

HTTP 状态码分类速查


类别 范围 通俗记忆
1xx 100-199 “收到了,继续”
2xx 200-299 “成功!”
3xx 300-399 “去别处找”
4xx 400-499 “你的锅”
5xx 500-599 “我的锅”

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐