前言
当键入网址后,到网页显示,其间发生了什么?也许在平时浏览网页的时候就会想过这个问题,现在学习了计算机网络,让我更加深入的理解了网络拓扑的过程。这篇笔记记录了简单网络收发的过程。
我们自顶而下,从客户端浏览器出发,到服务器端再返回的顺序开始这篇笔记。

Rerferences (Except textbook):
- 《图解网络》:https://xiaolincoding.com/network/
- 《计算机网络:自顶向下方法》
客户端应用层
在前文中我们已经了解了TCP/IP协议中的层次结构。我们从应用层开始一路往下。
HTTP
URL、URN、URI
假设我们的客户端为浏览器,我们看到一个长长的地址,例如本文的地址:https://hoyue.fun/network1_2.html,我们把它称为 URL 。
URL 代表着是 统一资源定位符 (Uniform Resource Locator)。它无非就是一个给定的独特资源在 Web 上的地址。理论上说,每个有效的 URL 都指向一个唯一的资源。这个资源可以是一个 HTML 页面,一个 CSS 文档,一幅图像,等等。浏览器中顶部输入的地址栏,输入的就是URL。
与URL非常类似的,我们还有一个名词是 URI 。 URI 是 统一资源标识符 (Uniform Resource Identifier),是一个指向资源的字符串。你可能会有些疑惑,URL和URI不都是指向一个资源吗?它们的区别在于:URI用于 标识 一个资源,而URL用于表示资源的 地址 。
在理解这两个的区别之前,再引入一个名词 URN 。 URN 是 统一资源名称 (UniformResourceName)指的是资源而不指定其位置或是否存在。
而URI就包含了URL(作为地址)和URN(指向资源)。

URI 属于 URL 更高层次的抽象,一种字符串文本标准。就是说,URI 属于父类,而 URL 属于 URI 的子类。URL 是 URI 的一个子集。
举个例子:
URI:
URL: http://www.example.com/path/to/file.html
URN: mailto
@bbb.com
所以它们三者的关系是:
URL 一定是 URI,但是URI 不一定是URL 。因为URI还包含了URN,如mailto
扯远了,我们主要来了解URL就好了。
URL格式
浏览器需要解析你给出的URL地址,首先我们需要了解一下URL的组成。根据URL标准,它的结构为:
其中用{}的部分,即端口号、查询和ID是可选的。
例如:

- http :HTTP协议的名称。
- www.server.com :是web服务器地址,其中server.com是域名,这里也可以直接填写IP地址。
- /dir1/file1.html :是文件file1.html的资源层级UNIX文件路径。
对于UNIX文件路径,是指从当前web服务器的根地址开始的,而不是整个服务器的根,如图:

而我们省略的端口,对于HTTP协议,默认端口号为80,而HTTPS加密协议,默认端口为443。即http://www.server.com/实际上和http://www.server.com:80/ 是一样的。
我们上网的时候时常发现,有时候我们的网址是http://www.server.com/ 并没有指定文件名,但是为什么也能访问呢?它访问的是什么文件呢。
当没有路径名时,就代表访问根目录下事先设置的 默认文件 ,也就是/index.html或者/default.html这些文件,这样就不会发生混乱了。
生成HTTP请求
对URL 进行解析之后,浏览器确定了 Web 服务器和文件名,接下来浏览器根据这些信息来生成 HTTP 请求消息了。
我们在WEB技术中学习过,HTTP请求的格式,这里重新整理一下:

我们根据这个格式,来生成一个获取http://www.server.com/dir1/file1.html的HTTP请求:
GET /dir1/file1.html HTTP/1.1HOST: www.server.com我们成功请求了,至此应用层HTTP的部分就结束了。
DNS
通过浏览器解析 URL 并生成 HTTP 消息后,需要委托操作系统将消息发送给Web服务器。
但在发送之前,还有一项工作需要完成,那就是 查询服务器域名对应的 IP 地址 ,因为委托操作系统发送消息时,必须提供通信对象的 IP 地址。
有一种服务器就专门保存了Web服务器域名与IP的对应关系,它就是 DNS 服务器(DomainNameSystem)。它使用TCP/UDP的53端口。
DNS的信息是层级存储的,越靠右的位置表示其层级越高。例如域名:www.server.com。下面是一个查询的过程:
- 客户端首先会发出一个 DNS 请求,问 www.server.com 的 IP 是啥,并发给 本地 DNS 服务器(也就是客户端的 TCP/IP 设置中填写的 DNS 服务器地址)。
- 本地域名服务器收到客户端的请求后,如果缓存里的表格能找到 www.server.com,则它直接返回 IP 地址。如果没有,本地 DNS 会去问它的根域名服务器。根域名服务器是最高层次的,它不直接用于域名解析,但能指明一条道路。
- 根 DNS 收到来自本地 DNS 的请求后,发现后置是 .com,让本地DNS去问.com顶级域名服务器。
- 本地 DNS 收到顶级域名服务器的地址后,发起请求询问 .com顶级域名服务器 。
- 顶级域名服务器发现管理这个域名的服务器是server.com,于是就让本地DNS去问这个 权威 DNS 服务器 。
- 本地 DNS 于是转向问权威 DNS 服务器。
- 权威 DNS 服务器查询后将对应的 IP 地址 X.X.X.X 告诉本地 DNS。
- 本地 DNS 再将 IP 地址返回客户端,客户端和目标建立连接。
如图:

当请求成功后,操作系统会生成DNS缓存,存在本地DNS中,可以下次访问不用去查询这么多步骤。
至此,我们知道了HTTP请求的报文(message)中服务器的IP 地址。接下来就是委托操作系统进行传输了。
传输层
TCP
报文格式
HTTP 是基于 TCP 协议传输的,我们先了解一下 TCP 协议。 TCP 即传输控制协议(英语:Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议。
我们来看它的报文头部的格式:

- 来源连接端口 (16位长)-识别发送连接端口,目的连接端口(16位长)-识别接收连接端口。这两个都是必不可少的,不然数据就不知道应该发给哪个应用。
- 序列号 (32位长)你可以理解为它的ID或一个标识符。
- 确认序列 (32位长)当ACK = 1时有效。目的是确认发出去对方是否有收到。一般为已经收到的数据的字节长度加1。
- 资料偏移 (4位长),就是我们的offset位,以4字节为单位计算出的数据段开始地址的偏移值。
- 保留 (3比特长)须置为0。
- 后面是一些 状态位 ,我们只需要了解一些常见的,例如:
SYN(Synchronize)是 建立一个连接 ,ACK(Acknowledge)是 确认 ,RST是 重新连接 ,FIN是 结束连接 等。 - 窗口 (WIN,16位长)表示从确认号开始,本报文的发送方可以接收的字节数,即接收窗口大小。用于流量控制。通信双方各声明一个窗口(缓存大小),标识自己当前能够的处理能力。
- 校验和 (Checksum,16位长)对整个的TCP报文段,包括TCP头部和TCP数据,以16位字进行计算所得。这是一个强制性的字段。
- 紧急指针 (16位长)—本报文段中的紧急数据的最后一个字节的序号。
- 选项字段 ,最多40字节,表示选项内容。
三次握手
TCP 建立连接的过程叫做握手,握手需要在客户和服务器之间交换三个TCP 报文段,称之为 三次握手 (Three-way Handshaking),为了 保证双方都有发送和接收的能力,同时为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误 。
下面可能会出现如下状态:
| 状态 | 解释 |
|---|---|
CLOSED | 初始状态,连接关闭 |
LISTEN | 服务器端侦听状态,等待连接请求 |
SYN_SENT | 客户端发送了连接请求,等待确认 |
SYN_RCVD | 服务器端收到连接请求,发送后等待ACK |
ESTABLISHED | 连接建立,数据传输状态 |
一开始,客户端和服务端都处于 CLOSED状态。先是服务端主动监听某个端口,处于LISTEN状态。

(1) 客户端主动发起连接,TCP协议会组建一个数据包,并设置 SYN 标志位,表示该数据包是用来建立同步连接的。同时生成一个 序列号 Seq(假设为1000),之后客户端处于 SYN-SENT 状态。

(2) 服务器端收到数据包,检测到已经设置了 SYN 就知道这是客户端发来的建立连接的”请求包”。服务器端也会组建一个数据包,并设置SYN 和 ACK 标志位,SYN 表示该数据包用来 建立连接 ,ACK 用来 确认 收到了刚才客户端发送的数据包。
此时服务器发送的这个数据包,它的Seq由服务器生成(假设为2000,它与之前客户端发送的没有关系),它的Ack即前文提到的 确认序列号 ,为收到的Seq + 1(收到的Seq为1000,故Ack为1001)。之后服务器处于SYN-RCVD状态。

(3) 客户端收到数据包,检测到已经设置了 SYN 和 ACK 标志位,就知道这是服务器发来的”确认包”。客户端会检测 确认序号 (Ack)字段,看它的值是否为发送时的Seq+1,即1001,如果是就说明连接建立成功。
接下来,客户端会继续组建数据包,并设置 ACK 标志位,表示客户端正确接收了服务器发来的”确认包”。同时,将刚才服务器发来的数据包Seq 加1,得到 2001,并用这个数字来填充”确认号(Ack)“字段。客户端将数据包发出,客户端进入ESTABLISED状态,表示连接已经成功建立。
服务器端收到数据包,检测到已经设置了 ACK 标志位,就知道这是客户端发来的”确认包”。服务器会检测Ack字段,看它的值是否为 2000+1,如果是就说明连接建立成功,服务器进入ESTABLISED状态。

至此,客户端和服务器都进入了ESTABLISED状态,连接建立成功,接下来就可以收发数据了。
三次握手的关键是要确认对方收到了自己的数据包,这个目标就是通过 确认序号 Ack字段实现的。计算机(客户端以及服务器)会记录下自己发送的数据包序号 Seq,待收到对方的数据包后,检测 确认序号 Ack字段,看Ack = Seq + 1是否成立,如果成立说明对方正确收到了自己的数据包。
四次挥手
与三次挥手类似的,但是TCP在断开连接时会有四次挥手的过程。

- 客户端主动调用关闭连接的函数,于是就会发送
FIN报文,这个FIN报文代表客户端不会再发送数据了,进入FIN_WAIT_1状态; - 服务端收到了
FIN报文,然后马上回复一个ACK确认报文,此时服务端进入CLOSE_WAIT状态。在收到FIN报文的时候,TCP 协议栈会为 FIN 包插入一个文件结束符 EOF 到接收缓冲区中,服务端应用程序可以通过 read 调用来感知这个 FIN 包,这个 EOF 会被 放在已排队等候的其他已接收的数据之后 ,所以必须要得继续 read 接收缓冲区已接收的数据; - 接着,当服务端在 read 数据的时候,最后自然就会读到 EOF,接着 read() 就会返回 0,这时服务端应用程序如果有数据要发送的话,就发完数据后才调用关闭连接的函数,如果服务端应用程序没有数据要发送的话,可以直接调用关闭连接的函数 ,这时服务端就会发一个
FIN包,这个 FIN 报文代表服务端不会再发送数据了,之后处于LAST_ACK状态; - 客户端接收到服务端的
FIN包,并发送ACK确认包给服务端,此时客户端将进入TIME_WAIT状态; - 服务端收到 ACK 确认包后,就进入了最后的
CLOSE状态; - 客户端经过 2MSL 时间之后,也进入
CLOSE状态;
TCP 报文生成
在双方建立了连接后,TCP 报文中的数据部分就是存放 HTTP 头部 + 数据,组装好 TCP 报文之后,就需交给下面的网络层处理。
现在我们的报文长这样:

如果我们的HTTP信息太长了怎么办?这个时候TCP会把 HTTP 的数据拆解成一块块的数据发送,而不是一次性发送所有数据。
数据会被以MSS的长度为单位进行拆分,拆分出来的每一块数据都会被放进单独的网络包中。也就是在每个被拆分的数据加上 TCP 头信息,然后交给 IP 模块来发送数据。
其中:
MTU(Maximum Transmission Unit):一个网络包的最大长度,以太网中一般为1500字节。由不同链路层决定。MSS(Maximum Segment Size):除去 IP 和 TCP 头部之后,一个网络包所能容纳的 TCP 数据的最大长度。
长度上:MSS = MTU - TCP头长度 - IP头长度
它们的关系如图:

网络层
历经应用层的HTTP请求生成和传输层的连接建立,现在数据包已经包含了TCP头、HTTP头以及数据。TCP模块在执行连接、收发、断开等各阶段操作时,都需要委托 IP 模块将数据封装成 网络包 发送给通信对象。TCP协议生成的数据包的传输需要经过网络层的传递。
IP
IP 协议可以说是个定位器,它包含了
- 源地址IP,即是客户端输出的 IP 地址;
- 目标IP地址,即通过 DNS 域名解析得到的 Web 服务器 IP。
IP地址的构成这里就不提及了,之后可能还有一篇学习笔记专门学习IP。
IP 报文

因为有些复杂我们就关注其中的一小部分:
- 版本(Version) :版本字段占4bit,通信双方使用的版本必须一致。对于IPv4,字段的值是4,IPv6则是6。
- 协议 (Protocol):占8bit,这个字段定义了该报文数据区使用的协议。其中TCP协议的编号为 0x06 。
- 源地址(Source address) :一个IPv4地址由四个字节共 32 位构成,IPv6地址则是一共 128 位,此字段的值是将每个字节转为二进制并拼在一起所得到的32位值。这个地址并不总是报文的真实发送端,因此发往此地址的报文会被送往NAT设备,并由它被翻译为真实的地址。
- 目的地址(Destination address):与原地址类似。
至此我们的数据包已经被封装成网络包了:

链路层
我们已经添加了IP头部,确认了远程地址。但是网络连接错综复杂,不一定我们与目的地就是直线连接,还需要路由。链路层的MAC用于确认路由中的两点连接。
MAC
MAC (Media Access Control Address),MAC地址用于在网络中唯一标识一个网卡,一台设备若有一或多个网卡,则每个网卡都需要并会有一个唯一的MAC地址。MAC地址共48位(6个字节)。
它的报文头部比较简单,只有:
- 接收方MAC地址
- 发送方MAC地址:读取发送机器网卡的MAC地址即可。
- 协议类型:一般有 0800 标识IP 协议, 0806 标识ARP 协议。
MAC头部又称为以太网头部。

那我们怎么知道接收方的MAC地址呢,我们又没有它的网卡。此时就需要ARP协议帮我们找到路由器的 MAC 地址。
ARP 协议会在以太网中以 广播 的形式,对以太网所有的设备询问这个IP是谁,它的MAC地址是多少。如果有机器响应,它会回复MAC地址。
前提是对方和自己处于同一个子网中,那么通过上面的操作就可以得到对方的 MAC 地址。然后,我们将这个 MAC 地址写入 MAC 头部,MAC 头部就完成了。
我们知道,互联网是由一个个子网组成的,所以这个过程可以发生在各个子网中,直到到达目的地。

所以现在网络包的报文如下所示:

物理层
物理层负责比特流的传输、编码/解码、时钟同步等。我们的网络包在链路层已经被加工好了,现在就要通过物理设备进行传输啦。
网卡
网络包只是存放在内存中的一串二进制数字信息,没有办法直接发送给对方。因此,我们需要将 数字信息转换为电信号 ,才能在网线上传输,也就是说,这才是真正的数据发送过程。这个过程在网卡上实现。
网卡 (network interface controller)是一块被设计用来允许计算机在计算机网络上进行通讯的计算机硬件。网卡严格意义上不完全是物理层设备,它不仅包含了物理层的硬件电路,也包含了完成数据链路层功能的控制逻辑。
网卡工作在物理层和链路层的交界处,实现了对二层数据的处理和传输,以使得报文可以在物理介质上传送。
网卡通过它的驱动获取网络包之后,会将其 复制 到网卡内的缓存区中,接着会在其 开头加上报头和起始帧分界符,在末尾加上用于检测错误的帧校验序列 。
- 起始帧分界符是一个用来表示包起始位置的标记
- 末尾的
FCS(帧校验序列)用来检查包传输过程是否有损坏

最后网卡会将包转为电信号,通过网线发送出去。
交换机与路由器
首先,电信号到达网线接口,交换机里的模块进行接收,接下来交换机里的模块将电信号转换为数字信号。
我们知道交换机的作用就是转发信息,它把我们的信号转发到路由器上。
网络包经过交换机之后,现在到达了路由器,并在此被转发到下一个路由器或目标设备。
路由器会根据 MAC 头部后方的IP头部中的内容进行包的转发操作。

在这个过程中, MAC地址会不断变化,因为它是两点之间的传输,而源 IP 和目标 IP 始终是不会变的 。
终于,经过交换机-路由器的层层转发,数据包发送到了服务器。
服务器接收
去除头部
数据包抵达服务器后,服务器会像拆快递一样一层一层拆开数据包的内容,因为我们的信息经过了多层包装。如图所示:

数据包抵达服务器后,服务器会先去掉数据包的 MAC 头部,查看是否和服务器自己的 MAC 地址符合,符合就将继续接收这个包。
接着继续去掉数据包的 IP 头,发现 IP 地址符合,根据 IP 头中协议项,知道自己上层是 TCP 协议。
于是,去掉 TCP 头部,里面有序列号Seq,查看Seq是否需要,如果需要就放入缓存中然后返回一个 ACK,如果不是就丢弃 。TCP 头部里面还有端口号, HTTP 的服务器正在监听这个端口号。