HTTP

步骤图解

浏览器做的第一步工作是解析 URL

首先浏览器做的第一步工作就是要对 URL 进行解析,从而生成发送给 Web 服务器的请求信息。

URL的结构如下图所示:

URL的结构

当省略数据源路径名时,就代表访问根目录下事先设置的默认文件,也就是 /index.html 或者 /default.html 这些文件

URL 进行解析之后,浏览器就确定了 Web 服务器和文件名,接下来就是根据这些信息来生成 HTTP 请求消息。

浏览器做的第二步工作是真实地址查询 (DNS)

通过浏览器解析 URL 并生成 HTTP 请求消息后,需要委托操作系统将消息发送给 Web 服务器。

但在发送之前,需要先查询服务器域名对应的 IP 地址,因为委托操作系统发送消息时,必须提供通信对象的 IP 地址。而DNS服务器

就是一种专门保存了Web 服务器域名与 IP 的对应关系的服务器。

域名的层级关系

DNS 中的域名都是用来分隔的,比如 www.server.com,这里的句点代表了不同层次之间的界限。在域名中,越靠右的位置表示其层级越高。实际上域名最后还有一个点,比如 www.server.com.,这个最后的一个点代表根域名。就是. 根域是在最顶层,它的下一层就是 .com 顶级域,再下面是 server.com

DNS结构

域名解析的工作流程

  1. 客户端先发送一个DNS请求,请求目标网站(如:www.server.com)的IP地址,并发送给本地的DNS服务器(即本地客户端TCP/IP设置中填写的DNS服务器地址)
  2. 本地DNS服务器收到客户端请求后,如果能在缓存里找到目标网站(如:www.server.com)的IP地址,则直接返回IP地址;如果在缓存里未找到,则根域名服务器请求目标网站的IP地址。根域名服务器是最高层次的,不会直接用于域名解析,但能指明一条道路
  3. 根域名服务器收到来自本地DNS服务器的请求后,发现后缀是.com,则返回.com的顶级域名服务器的IP地址
  4. 本地DNS服务器收到顶级域名服务器的IP后,则继续请求目标网站的IP地址
  5. 顶级域名服务器收到请求后,发现目标网站是www.server.com,则将www.server.com的权威服务器的IP地址返回给本地DNS服务器
  6. 本地DNS服务器向www.server.com的权威服务器发送请求
  7. www.server.com的权威服务器查询后返回目标网站对应的IP地址
  8. 本地DNS再将获取到的IP地址返回给客户端,客户端与目标网站建立连接

每次解析域名都要经过那么多的步骤吗?

答:不是;浏览器会先看自身有没有对这个域名的缓存,如果有,就直接返回,如果没有,就去问操作系统;操作系统也会去看自己的缓存,如果有,就直接返回,如果没有,再去 hosts 文件看,也没有,才会去问「本地 DNS 服务器」

第三步——协议栈

通过 DNS 获取到 IP 后,就可以把 HTTP 的传输工作交给操作系统中的协议栈

协议栈

浏览器通过调用 Socket 库,来委托协议栈工作。协议栈的上半部分有两块,分别是负责收发数据的 TCP 和 UDP 协议,这两个传输协议会接受应用层的委托执行收发数据的操作。

协议栈的下面一半是用 IP 协议控制网络包收发操作,在互联网上传数据时,数据会被切分成一块块的网络包,而将网络包发送给对方的操作就是由 IP 负责的。

IP 下面的网卡驱动程序负责控制网卡硬件,而最下面的网卡则负责完成实际的收发操作,也就是对网线中的信号执行发送和接收操作。

第四步——可靠传输 (TCP)

TCP 包头格式

TCP 包头格式

首先,源端口号目标端口号是不可少的,如果没有这两个端口号,数据就不知道应该发给哪个应用。

接下来有包的号,这个是为了解决包乱序的问题。

还有应该有的是确认号,目的是确认发出去对方是否有收到。如果没有收到就应该重新发送,直到送达,这个是为了解决丢包的问题。

接下来还有一些状态位。例如 SYN 是发起一个连接,ACK 是回复,RST 是重新连接,FIN 是结束连接等。TCP 是面向连接的,因而双方要维护连接的状态,这些带状态位的包的发送,会引起双方的状态变更。

还有一个重要的就是窗口大小。TCP 要做流量控制,通信双方各声明一个窗口(缓存大小),标识自己当前能够的处理能力,别发送的太快,撑死我,也别发的太慢,饿死我。

除了做流量控制以外,TCP还会做拥塞控制,对于真正的通路堵车不堵车,它无能为力,唯一能做的就是控制自己,也即控制发送的速度。

TCP 传输数据之前,要先三次握手建立连接

  • 一开始,客户端和服务端都处于 CLOSED 状态。先是服务端主动监听某个端口,处于 LISTEN 状态。
  • 然后客户端主动发起连接 SYN,之后处于 SYN-SENT 状态。
  • 服务端收到发起的连接,返回 SYN,并且 ACK 客户端的 SYN,之后处于 SYN-RCVD 状态。
  • 客户端收到服务端发送的 SYNACK 之后,发送对 SYN 确认的 ACK,之后处于 ESTABLISHED 状态,因为它一发一收成功了。
  • 服务端收到 ACKACK 之后,处于 ESTABLISHED 状态,因为它也一发一收了。

所以三次握手目的是保证双方都有发送和接收的能力

TCP 分割数据

如果 HTTP 请求消息比较长,超过了 MSS 的长度,这时 TCP 就需要把 HTTP 的数据拆解成一块块的数据发送,而不是一次性发送所有数据。

TCP分割数据

  • MTU:一个网络包的最大长度,以太网中一般为 1500 字节。
  • MSS:除去 IP 和 TCP 头部之后,一个网络包所能容纳的 TCP 数据的最大长度。

数据会被以 MSS 的长度为单位进行拆分,拆分出来的每一块数据都会被放进单独的网络包中。也就是在每个被拆分的数据加上 TCP 头信息,然后交给 IP 模块来发送数据。

第五步——远程定位

TCP 模块在执行连接、收发、断开等各阶段操作时,都需要委托 IP 模块将数据封装成网络包发送给通信对象。

在 IP 协议里面需要有源地址 IP目标地址 IP

  • 源地址IP,即是客户端输出的 IP 地址;
  • 目标地址,即通过 DNS 域名解析得到的 Web 服务器 IP。

因为 HTTP 是经过 TCP 传输的,所以在 IP 包头的协议号,要填写为 06(十六进制),表示协议为 TCP。

第六步——两点传输(MAC)

生成了 IP 头部之后,接下来网络包还需要在 IP 头部的前面加上 MAC 头部。在 MAC 包头里需要发送方 MAC 地址接收方目标 MAC 地址,用于两点之间的传输

一般在 TCP/IP 通信里,MAC 包头的协议类型只使用:

  • 0800 : IP 协议
  • 0806 : ARP 协议

第七步——网卡

网络包只是存放在内存中的一串二进制数字信息,没有办法直接发送给对方。因此,我们需要将数字信息转换为电信号,才能在网线上传输,也就是说,这才是真正的数据发送过程。

负责执行这一操作的是网卡,要控制网卡还需要靠网卡驱动程序

网卡驱动获取网络包之后,会将其复制到网卡内的缓存区中,接着会在其开头加上报头和起始帧分界符,在末尾加上用于检测错误的帧校验序列

第八步——交换机

交换机的设计是将网络包原样转发到目的地。交换机工作在 MAC 层,也称为二层网络设备

交换机的包接收操作

  • 首先,电信号到达网线接口,交换机里的模块进行接收,接下来交换机里的模块将电信号转换为数字信号。

  • 然后通过包末尾的 FCS 校验错误,如果没问题则放到缓冲区。这部分操作基本和计算机的网卡相同,但交换机的工作方式和网卡不同。

  • 计算机的网卡本身具有 MAC 地址,并通过核对收到的包的接收方 MAC 地址判断是不是发给自己的,如果不是发给自己的则丢弃;相对地,交换机的端口不核对接收方 MAC 地址,而是直接接收所有的包并存放到缓冲区中。因此,和网卡不同,交换机的端口不具有 MAC 地址

  • 将包存入缓冲区后,接下来需要查询一下这个包的接收方 MAC 地址是否已经在 MAC 地址表中有记录了。交换机根据 MAC 地址表查找 MAC 地址,然后将信号发送到相应的端口

第九步——路由器

网络包经过交换机之后,现在到达了路由器,并在此被转发到下一个路由器或目标设备。

这一步转发的工作原理和交换机类似,也是通过查表判断包转发的目标。

不过在具体的操作过程上,路由器和交换机是有区别的。

  • 因为路由器是基于 IP 设计的,俗称三层网络设备,路由器的各个端口都具有 MAC 地址和 IP 地址;
  • 交换机是基于以太网设计的,俗称二层网络设备,交换机的端口不具有 MAC 地址。

路由器的包接收操作

首先,电信号到达网线接口部分,路由器中的模块会将电信号转成数字信号,然后通过包末尾的 FCS 进行错误校验。

如果没问题则检查 MAC 头部中的接收方 MAC 地址,看看是不是发给自己的包,如果是就放到接收缓冲区中,否则就丢弃这个包。

总的来说,路由器的端口都具有 MAC 地址,只接收与自身地址匹配的包,遇到不匹配的包则直接丢弃。

第十步——服务器与客户端

数据包抵达服务器后,服务器会先扒开数据包的 MAC 头部,查看是否和服务器自己的 MAC 地址符合,符合就将包收起来。

接着继续扒开数据包的 IP 头,发现 IP 地址符合,根据 IP 头中协议项,知道自己上层是 TCP 协议。

于是,扒开 TCP 的头,里面有序列号,需要看一看这个序列包是不是我想要的,如果是就放入缓存中然后返回一个 ACK,如果不是就丢弃。TCP 头部里面还有端口号, HTTP 的服务器正在监听这个端口号。

于是,服务器自然就知道是 HTTP 进程想要这个包,于是就将包发给 HTTP 进程。

服务器的 HTTP 进程看到,原来这个请求是要访问一个页面,于是就把这个网页封装在 HTTP 响应报文里。

HTTP 响应报文也需要穿上 TCP、IP、MAC 头部,不过这次是源地址是服务器 IP 地址,目的地址是客户端 IP 地址。

穿好头部衣服后,从网卡出去,交由交换机转发到出城的路由器,路由器就把响应数据包发到了下一个路由器,就这样跳啊跳。

最后跳到了客户端的城门把守的路由器,路由器扒开 IP 头部发现是要找城内的人,于是又把包发给了城内的交换机,再由交换机转发到客户端。

客户端收到了服务器的响应数据包后,同样也非常的高兴,客户能拆快递了!

于是,客户端开始扒皮,把收到的数据包的皮扒剩 HTTP 响应报文后,交给浏览器去渲染页面,一份特别的数据包快递,就这样显示出来了!

最后,客户端要离开了,向服务器发起了 TCP 四次挥手,至此双方的连接就断开了。