Caiwen的博客

计网-应用层

2026-02-02 03:39

网络应用模型有两种:客户/服务器模型(C/S),对等模型(P2P)

互联网指的是全球性的计算机网络互联系统。万维网指的是网站相关的网络。

1. 电子邮件

完整的邮件系统包含三个部分:

  • 用户代理(User Agent):运行在用户手机上的软件

  • 邮件服务器:负责把用户代理发来的邮件再给发到别的邮件服务器上。同时也负责暂存别的邮件服务器发来的邮件。

  • 协议

    邮件发送协议:SMTP

    邮件读取协议:POP3、IMAP

    用户代理和邮件服务器之间的通信未必使用邮件协议。

1.1 SMTP

SMTP 基于 TCP 协议,端口号为 25。

MIME(多用途互联网邮件扩展)

SMTP 协议要求,发送的数据包的内容都需要是 7 比特 ASCII 码。如果需要包含其他的字符,或是二进制文件,则需要使用 MIME,将内容编码为 ASCII 字符。

MIME 包括了以下三部分:

  • 定义了 5 个新的首部字段:MIME 版本、内容描述、0内容标识、传送编码、内容类型
  • 标准化了多种邮件内容格式,为多媒体电子邮件的表示方法提供了统一规范
  • 定义了传送编码机制

1. 寻找 SMTP 服务器

当我们要向某个邮箱地址,比如 3102733279@qq.com,发送邮件的时候,我们首先需要找到其对应的 SMTP 服务器。qq.com 本身并不是 SMTP 服务器。真正的 SMTP 服务器存在其 MX 记录上:

shell
1
2
3
4
5
6
7
8
$ nslookup -type=mx qq.com Server: 192.168.77.227 Address: 192.168.77.227#53 Non-authoritative answer: qq.com mail exchanger = 20 mx2.qq.com. qq.com mail exchanger = 10 mx3.qq.com. qq.com mail exchanger = 30 mx1.qq.com.

我们可以看到,qq.com 对应了多个 SMTP 服务器。exchanger 旁边的数字表明了服务器的优先级,数字越小优先级越大。

2. 握手

于是我们就可以使用 telnet mx3.qq.com 25 来连接 SMTP 服务器了。SMTP 协议端口号为 25。

shell
1
2
3
4
5
$ telnet mx3.qq.com 25 Trying 240e:ff:f100:1009::107... Connected to mx3.qq.com. Escape character is '^]'. 220 newxmmxsza56-0.qq.com MX QQ Mail Server.

连接后,服务器会主动响应一个 220 开头的内容。

在 SMTP 协议中,服务器的响应格式是 [数字] [其他解释内容...]220 就表示连接成功。

然后我们输入 HELO [域名] 来回应,服务器会做如下的响应:

txt
1
2
3
250-newxmmxsza63-1.qq.com-21.238.7.160-36686480 250-SIZE 66060288 250 OK

现代的 SMTP 还可以使用 EHLO 指令(最开头的 E 是扩展的意思,表明是原来 HELO 指令的扩展)。和 HELO 区别在于服务器可能会返回更加详细的信息(比如服务器是否支持加密连接,是否支持流水线特性等)。

值得注意的是,上面的 250-SIZE 66060288 表明服务器支持的最大邮件大小为 66060288 字节。

HELO 指令后面的域名并不太重要,实测 QQ 的 SMTP 服务器甚至不会校验是否是域名格式。这个握手过程应该主要是走个形式。

3. 设置发件人

输入 MAIL FROM: <[邮件地址]> ,如 MAIL FROM: <aaa@aaa.com> 表明邮件的发送者。

但实际上 MAIL FROM 更重要的地方在于指定了退信地址。当后面出现发件人不存在,或是邮件内容不合法时,SMTP 除了会响应一个错误,还会生成一个退信,发给 MAIL FROM 表明的地址。

对于本身就是退信的邮件,或是一些不需要知道有没有成功投递的邮件(比如一些通知什么的),MAIL FROM 这里可以直接设置为 <> 表明不需要退信。

但是实测发现,MAIL FROM 设为空的话,QQ 邮箱会对邮件发送者进行更严格的检查,很容易触发 550 Suspected bounce attacks。同时,如果你要生成一个退信邮件的话,邮件内容还要遵循 DSN 格式。

同时,为了防止有人冒充发件人,SMTP 服务器还会对 MAIL FROM 所填的地址进行 SPF 检查:

  • 首先查询提供的发件人地址的域名的 txt 记录,判断是否有 SPF 检查相关的内容。如果没有的话就默认通过了检查。如果有的话,那么就形如这样:

    shell
    1
    2
    3
    4
    5
    6
    7
    8
    $ nslookup -type=txt qq.com Server: 192.168.77.227 Address: 192.168.77.227#53 Non-authoritative answer: qq.com text = "v=spf1 include:spf.mail.qq.com -all" Authoritative answers can be found from:
  • 检查 include 字段,发现是域名,那么再对于这个域名,递归这个过程。

    shell
    1
    2
    3
    4
    5
    6
    7
    8
    $ nslookup -type=txt spf.mail.qq.com Server: 192.168.77.227 Address: 192.168.77.227#53 Non-authoritative answer: spf.mail.qq.com text = "v=spf1 include:qq-a.mail.qq.com include:qq-b.mail.qq.com include:qq-c.mail.qq.com include:biz-a.mail.qq.com include:biz-b.mail.qq.com include:biz-c.mail.qq.com include:biz-d.mail.qq.com -all" Authoritative answers can be found from:
  • include 可以有多个,对每个是域名的 include 都进行这个递归过程,直到最后 include 的内容是 IP 地址。通过递归遍历这样的“树形结构”,可以获得一个 IP 列表。然后 SMTP 会检查当前客户端的 IP 地址是否位于这个列表中,如果不存在则 SPF 检查失败。

4. 设置收件人

输入 RCPT TO: <[收件人邮箱地址]> 则可以设置收件人。RCPT TO 可以执行多次。SMTP 也会检查这个邮箱地址是否存在,不存在的话会响应错误,并生成一个退信邮件给 MAIL FROM

5. 设置邮件内容

输入 DATA 指令,然后就可以输入邮件内容了。输入 . 并回车表明邮件内容结束。

这里需要注意转义问题。如果邮件内容刚好存在一个只有一个 . 的行,那么邮件发送者需要将其写为 ..。一般地,如果改行只存在点,并且有 nn 个点,那么邮件发送时需要写成 n+1n+1 个点。

邮件内容的格式如下:

Unknown
1
2
3
4
5
From: [发送者邮件地址] To: [接收者邮件地址] Subject: [邮件主题] [邮件内容...]

邮件的头部中,From 是必须的,其他的都不是必须的。

如果 FromMAIL FROM 一致的话,就没什么问题。如果不一致的话,则会触发 DKIM 校验:

  • 首先检查邮件的头部是否有 DKIM-Signature 字段,没有的话直接校验失败。这个字段中主要有用的是指明了邮件内容的一个加密摘要(将邮件内容取摘要之后再用私钥加密)和一个 selector
  • 然后,SMTP 服务器去查询 [selector]._domainkey.[From 中的域名] 的 TXT 记录,来获取公钥,然后将 DKIM-Signature 字段内的加密摘要解密,如果解密不了则说明有人伪造了 From,直接校验失败。解密成功的话,再与邮件内容本身的摘要对比,如果不匹配,则说明邮件内容被篡改,校验失败。

当 DKIM 校验失败后,会再根据 DMARC 策略决定之后的行为:

  • SMTP 服务器查询 _dmarc.[From 中的域名] 的 TXT 记录,格式大概如下:

    Unknown
    1
    2
    # QQ 邮箱的 DMARC 策略 v=DMARC1; p=quarantine; rua=mailto:mailauth-reports@qq.com
  • 其中有用的是 p 属性,表明了 DKIM 校验失败之后的行为。如果 pnone,则说明校验成功后仍继续发送。为 quarantine 说明邮件也仍继续发送,但应该被隔离,一般会被放入收件者的垃圾邮件箱里。为 reject 说明 SMTP 应拒绝发送这个邮件并响应错误。

  • rua 属性比较有意思,其表明当有人没通过 DKIM 校验之后,应该把这个事件通知到哪个邮箱里。

如果邮件成功被发送的话,像是 QQ 邮箱,还会有如下的行为:

  • 如果 MAIL FROMFrom 不一致,则会在邮件界面显示发件人为 From,但是表明由 MAIL FROM 的邮箱代发。
  • 如果 RCPT TOTo 不一致,则邮件会发送给 RCPT 指明的邮件地址,邮件页面的收件人显示 To 的邮箱,但是标明密送给 RCPT TO 的邮箱。

6. 退出

邮件发送完毕之后,TCP 连接并不会自动关闭。因此可以在一个 TCP 连接中完成多个邮件的发送。

如果需要退出,则发送 QUIT 指令,SMTP 服务器将会断开连接。

1.2 POP3 和 IMAP

  • POP3

    基于 TCP,端口号为 110。

    POP3 支持两种操作:下载并保留和下载并删除

  • IMAP

    相比于 POP3 更强大,支持用户在服务器端创建文件夹、在不同文件夹之间移动邮件、对远程邮箱中的邮件进行搜索等操作。

    IMAP 还支持选择性地获取邮件内容,用户代理可以只下载邮件的特定部分,比如只获取头部,或是只提取多部分 MIME 邮件中的某部分。这些特性在低带宽环境下尤为实用。

2. DNS

DNS 协议基于 UDP 协议,跑在 53 端口上。

2.1 层次域名空间

域名由一系列标号组成,标号之间采用 . 来分割,形成一个层次结构。标号从右往左依次为顶级域名、二级域名、三级域名...

域名中的标号有如下规则:

  • 标号中的英文字母不区分大小写
  • 标号中除了 - 连字符以外不能有其他符号
  • 每个标号不超过 63 个字符,整个域名(含分隔点)的总长度不超过 255 个字符。

在域名系统中,各级域名由其上一级的域名管理机构管理。顶级域名由互联网名称与数字地址分配机构(ICANN)管理。

2.2 域名服务器

整个 DNS 由三层服务器组成:

  • 最顶层是根域名服务器,可以在 https://www.iana.org/domains/root/files 的 Root Hints 文件中找到所有的根服务器地址,全球一共有 13 个根服务器,但有很多节点,来同步根服务器的内容。这 13 个根服务器使用 anycast 技术,将所有的请求分发给其他的节点。

    每个根服务器中存储着所有顶级域名对应的 dns 服务器。其内容位于上述网站中的 Root Zone File 中。

    根服务器的 IP 不太会更改,所以一般是硬编码到代码中的。

  • 然后是顶级域名服务器,每一个顶级域名都有对应的顶级域名服务器。

  • 再往下是权限域名服务器/授权域名服务器。

  • 本地域名服务器。

2.3 域名解析过程

当我们要把一个域名解析成 IP 地址时,会进行如下的过程:

  1. 请求本地 DNS 服务器

    一般设备连入网络后,会自动分配一个 DNS 服务器地址,这往往是 ISP 提供的 DNS 服务器。我们也可以自己将设备上的 DNS 服务器地址设置成其他的公共 DNS 服务器。

    域名解析的请求会先发送给本地的 DNS 服务器,DNS 服务器会先检查请求的域名的解析结果是否被缓存了,如果已经被缓存则直接响应结果,反之则继续从根服务器查询。这种本地 DNS 服务器代替主机去查询的方式也叫做递归查询。

  2. 从根服务器开始查询

    注意,这一过程是本地 DNS 服务器进行的。因此,一般设备很少会直接从根服务器开始查。

    理论上这里存在递归查询的方式,即本地 DNS 服务器请求根服务器,然后根服务器再去查询,最后由根服务器把查询结果返回给本地 DNS 服务器,本地 DNS 再返回给主机。但是这样会给根服务器带来较大的负载,实际中几乎不会这样做。

    本地 DNS 服务器向根域名服务器查询通常采用迭代查询:

    比如我们要查 www.qq.com,首先向根服务器查询整个域名,根服务器查不到,只会返回 .com 对应的 NS 记录,比如:a.gtld-servers.net,这还是个域名,但是一般这个域名对应的 IP 也记录在根服务器中。

    于是 DNS 服务器再向 a.gtld-servers.net 指向的服务器查询,此时服务器会返回 qq.com 对应的 NS 记录,比如 ns1.qq.com,然后向 a.gtld-servers.net 查询得到 ns1.qq.com 对应的 IP。

    于是 DNS 服务器再向 ns1.qq.com 查询,此时服务器返回了一个 www.qq.com 对应的 NS 记录,比如 ns4.qq.com

    于是我们再向这个服务器发起查询,最终得到 www.qq.com 对应的 CNAME 记录,指向 ins-r23tsuuf.ias.tencent-cloud.net。于是本地 DNS 服务器又对 ins-r23tsuuf.ias.tencent-cloud.net 进行和上述一样的查询过程,直到拿到一个 A 记录。

    我们看到,如果当前的 DNS 服务器没有要查询的记录,那么就响应一个查询域名后缀对应的 NS 记录,告诉本地 DNS 服务器再向这个服务器进行查询。NS 记录都会先指向一个域名,然后这个域名的 A 记录(即 IP 地址)又直接记录在同级的 DNS 服务器中。

本地 DNS 服务器会将上述过程的查询结果缓存起来。缓存的过期时间取决于 DNS 记录的 TTL 属性。

一个 DNS 记录包含:名称、类型、对应的值和 TTL。其中的 TTL 表示本地 DNS 服务器缓存这个记录的过期时间(单位为秒)。

有如下的类型:

  • A 类型:直接表明这个域名对应的 IP 地址

  • NS 类型:表明后面再解析当前域名的子域名时,需要向哪个 DNS 服务器发起解析请求。NS 类型的记录的值必须是域名。需要再解析这个域名来间接获得 DNS 服务器的 IP 地址。

    qq.com 有一个 NS 类型的记录 ns1.qq.com,但这个域名可以直接从 .com 的 DNS 服务器中获得相关的记录,所以并不会出现循环依赖的问题。

  • CNAME 类型:相当于是一个转发。比如域名 xx.com 有一个 CNAME 类型记录 yy.com,那么解析到 xx.com 发现这个 CNAME 类型记录后,本地 DNS 服务器会转而去再解析 yy.com

  • MX 类型:在 SMTP 协议中已经提到。

3. BitTorrent

BitTorrent 是基于 TCP 的 P2P 文件传输协议。

BitTorrent 中,由各个节点之间相互进行传输文件,这些节点叫做 peer。

使用 BT 下载的第一步是要找到某个文件当前在线的 peer,这就分成了如下的两种方法:

  • Tracker

    每个文件都有一个种子文件,种子文件中记录了文件的哈希值,大小,分块大小,每个分块的哈希值,以及 Tracker。Tracker 是一个服务器,记录一个文件当前在线的 peer。当我们要下载文件的时候,可以将自己的 IP 地址注册到 Tracker 中,然后 Tracker 会给我们返回一个在线 peer 的列表。

  • DHT

    Tracker 的缺点很明显,如果 Tracker 服务器宕机,那么就无法找到 peer 了。DHT 可以解决这一点。首先下载文件不再需要种子文件,而是只需要一个磁力链接。这个链接主要记录了要下载的文件的哈希值。然后需要通过已经获知的处于 DHT 网络中的节点来加入 DHT 网络。

    DHT 网络中每个节点都有一个 ID,文件也有一个 ID,为其哈希值。节点和文件之间的距离定义为两个 ID 的异或值。注意,根据异或的性质,距离某个文件为某个距离的节点 ID 一定是有且仅有一个的。

    加入 DHT 网络之后就可以向所有已连接的节点 xx 询问,在 xx 所连接的节点中,距离文件最近的节点有哪些(这里的连接不是 TCP 连接,而是一种得知有哪些节点在 DHT 网络中的概念。每个 DHT 网络中的节点都会维护一个数据结构来存储它所知道的其他位于 DHT 网络中的节点)。然后重复这个过程,直到找不出距离文件最近的节点了,那么已知的最近的这些节点就相当于是该文件的负责节点。

    然后负责节点就跟 Tracker 比较像了,可以向其注册自己,也可以从其获得 peer 列表。

    由于 DHT 网络中的节点是会随时上线/下线的,每个文件的负责节点也会发生变化。所以每个 peer 还要定期向 DHT 网络中重新查找负责节点并宣告自己的在线状态。

寻找到 peer 之后就会向这些 peer 发起 TCP 连接,连接的 peer 就是自己的邻居。

BT 中的文件是被分成若干个块来传输的。在传输开始时,我们没有关于文件的任何数据,所以会随机向邻居索要文件块。在传输了一定量的数据后,文件的大部分块已经获取到了。此时采取 rarest first 策略,优先索要在所有邻居中最稀有的块。

BT 需要每个节点都既有上传又有下载,为了防止有人只下载不上传,BT 还存在一个 tit-for-tat 策略。初始时,一个节点的邻居都是阻塞状态。处于阻塞状态的邻居是无法从该节点下载到文件块。每隔 10s,当前节点会统计从各个邻居下载文件块的速度,并把速度最快的几个邻居改为非阻塞状态,这意味着当前节点从这些邻居中获利,于是就允许这些邻居从自己这里下载数据,达到一种互惠的局面。除此之外,每隔 30s,当前节点还会随机挑选一个邻居,将其设置为非阻塞状态,相当于当前节点先退让一步,让邻居可以从自己这里下载,进而有希望让自己也能从这个邻居这里下载。

4. FTP

FTP 是一种文件传输协议,提供如下的功能:

  • 支持不同类型的主机系统(硬件,操作系统等均可不同)之间的文件传输。
  • 通过用户权限管理,提供对远程 FTP 服务器上文件的管理能力。
  • 通过匿名 FTP 方式,实现公用文件的共享。

基于 TCP 协议,在工作时使用两个并行的 TCP 连接:

使用两个连接的理由是:可以使得 FTP 变得更简单,容易实现,更有效率。同时在文件传输过程中还可以通过控制连接来控制传输过程,终止或是暂停传输。

  • 控制连接(端口号 21)

    传输控制命令(登录、目录操作、传送命令等)。控制连接在整个会话期间保持打开状态。

  • 数据连接

    当服务器的控制连接收到传输数据的请求后,会再创建一个数据连接专门进行数据的传输,传输结束后数据连接就会关闭。

    这里的数据传输不光是指文件本身的数据。比如传输文件列表数据,也是走的这个数据连接而不是控制连接。

    数据连接支持两种传输模式:

    • 主动模式(PORT 模式)

      客户端会随机选择一个本地端口,并通过 PORT 命令将该端口号告知服务器,随后服务器通过 20 端口主动连接到客户端指定的端口以传输数据。

    • 被动模式(PASV 模式)

      客户端发送 PASV 命令,服务器收到后在本地随机开放一个端口,并通过控制连接将该端口号返回给客户端,客户端再主动连接到该端口以传输数据。

如果没有说明,默认情况下可以认为是采用主动模式。

采取主动模式还是被动模式由客户端决定。

使用独立的控制连接传输命令,其控制信息属于带外传送。

FTP 在修改文件时需要先把文件下载到本地,修改后再传回服务器,这就使得修改文件的效率较低。

FTP 允许用户指定文件的类型与格式。FTP 提供交互式访问。

5. HTTP

HTTP 是一种面向事务的应用层协议。是无状态的。是无连接的。

HTTP 1.0

HTTP 1.0 仅支持非持续连接,每个网页元素的传输都需要单独建立一个 TCP 连接。请求一个元素所需要的时间为:元素本身数据传输的时间+2×RTT\text{元素本身数据传输的时间} + 2\times RTT(一个 RTT 用于完成建立 TCP 连接的前两次握手,另一个 RTT 用于发送请求并接收响应)

HTTP 1.1

HTTP 1.1 支持持续连接,在 HTTP 请求头上可以设置 Connection: Keep-Alive 来要求服务器在发送完被请求的元素之后仍保持这条连接。

持续连接又分为非流水线和流水线两种工作方式:

  • 非流水线

    客户端在等到前一个响应到达之后才发送下一个请求。

    每获取一个对象均需要至少一个 RTT。

  • 流水线

    客户端连续发送多个对象的请求,服务器也连续发送响应。

    如果所有请求与响应均能连续传输,则获取全部引用对象最好情况下只需要 1 RTT。

HTTP 2

有如下特性:

  • 解决队头阻塞

    HTTP 1.1 中,请求网页资源时只需要使用一个 TCP 连接,但是这样又会出现队头阻塞的问题。比如一个网页上面包含了一个很大的视频文件,还包含了若干个比较小的其他文件。那么浏览器在加载网页时,如果这个很大的视频文件先进行传输,那么就会阻塞后面的资源,后面的资源必须等到这个视频传输完毕才能继续。

    浏览器会选择建立多个 TCP 连接来解决这个问题,但是也不够好:对头阻塞只是被分摊了,没有消失,传输大文件的那个连接上的剩下的资源的请求也被推迟了。同时 TCP 的拥塞控制是确保每个 TCP 连接都能平分整个带宽,建立了多个 TCP 连接会打破这个公平性。

    HTTP 2 的解决方案时,把一个请求或者响应分成若干个小的切片,然后所有请求/响应的切片交替轮流发送。

  • 数据压缩

    在对请求/响应进行切片时,还会对切片进行压缩。

  • 优先级

    HTTP 2 可以根据资源之间的依赖关系,来决定给哪些资源比较大的优先级,或是手动设置优先级。优先级高的资源在传输时会占当前 TCP 连接的较多的带宽。

  • 主动响应

    服务器在响应某个请求时,还可以顺手主动把相关的资源主动发给客户端。比如一个 HTML 中又包含了其他的 CSS 和 JS,浏览器在收到 HTML 之后大概率会再对这些 CSS 和 JS 请求,于是服务端就直接主动把 CSS 和 JS 响应回去。

6. 错题

域名和 IP 不是一一对应的关系,比如 CDN 和拿域名做负载均衡这种。

C。最多查询:根服务器查、.com 查、xyz.com 查、abc.xyz.com 查。一个技巧是域名有几个部分就要查几次。

不是 D。

最后更新于:2026-07-26 15:30

Caiwen
本文作者
一只蒟蒻,爱好编程和算法

推荐文章