【浏览器工作原理与实践】2. 宏观视角下的浏览器 - TCP 协议和 HTTP 协议

在衡量 Web 页面性能的时候有一个重要的指标叫 “FP(First Paint)”,是指从页面加载到首次开始绘制的时长。这个指标直接影响了用户的跳出率,更快的页面响应意味着更多的 PV、更高的参与度,以及更高的转化率。影响 FP 指标的一个重要的因素是网络加载速度

下面通过数据包如何送达主机”、主机如何将数据包转交给应用数据是如何被完整地送达应用程序这三个角度来讲述数据的传输过程。

IP:把数据包送达目的主机

数据包要在互联网上进行传输,就要符合网际协议(Internet Protocol,简称 IP)标准

计算机的地址就称为 IP 地址,访问任何网站实际上只是你的计算机向另外一台计算机请求信息。

简化的IP网络三层传输模型
简化的 IP 网络三层传输模型

  • 数据包要进行传输,需要符合国际协议(IP)标准,因此想要把数据包从主机 A 发送给主机 B,需要知道主机 B 的 ip 地址。
  • 上层将数据包交给网络层
  • 网络层会将 IP 头附加到数据包上,组成新的 IP 数据包,并交给底层(数据链路层和物理层)
  • 底层通过物理网络将数据包传输给主机 B 的网络层
  • 主机 B 拆开数据包的 IP 头信息,将拆开来的数据交给上层
  • 最终,数据包就到达了主机 B 的上层了。

UDP:把数据包送达应用程序

基于 IP 之上开发能和应用打交道的协议,最常见的是用户数据包协议(User Datagram Protocol,简称 UDP)

每个想访问网络的程序都需要绑定一个端口号。通过端口号 UDP 就能把指定的数据包发送给指定的程序。将域名映射为 IP 的系统就叫做 “域名系统”(Domain Name System ,简称 DNS),DNS 协议运行在 UDP 协议之上,使用端口号 53。

简化的UDP网络四层传输模型
简化的 UDP 网络四层传输模型

  • 上层将数据包交给传输层
  • 传输层会在数据包前面附加上 UDP 头,组成新的 UDP 数据包,再将新的 UDP 数据包交给网络层
  • 网络层再将 IP 头附加到数据包上,组成新的 IP 数据包,并交给底层
  • 数据包被传输到主机 B 的网络层,在这里主机 B 拆开 IP 头信息,并将拆开来的数据部分交给传输层
  • 在传输层,数据包中的 UDP 头会被拆开,并根据 UDP 中所提供的端口号,把数据部分交给上层的应用程序
  • 最终,数据包就旅行到了主机 B 上层应用程序

优点:传输速度却非常快。
缺点

  • 不能保证数据可靠性,容易丢失
  • UDP 协议并不知道如何组装这些数据包,从而把这些数据包还原成完整的文件。

基于这两个问题,引入了传输控制协议(Transmission Control Protocol,简称 TCP)

TCP:把数据完整地送达应用程序

TCP(Transmission Control Protocol,传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议。

相对于 UDP,TCP 有下面两个特点:

  • 对于数据包丢失的情况,TCP 提供重传机制
  • TCP 引入了数据包排序机制,用来保证把乱序的数据包组合成一个完整的文件。

TCP 头提供了用于排序的序列号,以便接收端通过序号来重排数据包。

简化的TCP网络四层传输模型
简化的 TCP 网络四层传输模型

根据下图 —— 完整的 TCP 连接过程图,可以看出一个完整的 TCP 连接的生命周期包括了建立连接传输数据断开连接三个阶段:

一个TCP连接的生命周期
一个 TCP 连接的生命周期

  • 建立连接阶段。这个阶段是通过三次握手来建立客户端和服务器之间的连接。TCP 提供面向连接的通信传输。面向连接是指在数据通信开始之前先做好两端之间的准备工作。所谓三次握手,是指在建立一个 TCP 连接时,客户端和服务器总共要发送三个数据包以确认连接的建立。
  • 传输数据阶段。在该阶段,接收端需要对每个数据包进行确认操作,也就是接收端在接收到数据包之后,需要发送确认数据包给发送端。所以当发送端发送了一个数据包之后,在规定时间内没有接收到接收端反馈的确认消息,则判断为数据包丢失,并触发发送端的重发机制。同样,一个大的文件在传输过程中会被拆分成很多小的数据包,这些数据包到达接收端后,接收端会按照 TCP 头中的序号为其排序,从而保证组成完整的数据。
  • 断开连接阶段。数据传输完毕之后,就要终止连接了,涉及到最后一个阶段四次挥手来保证双方都能断开连接。

TCP 为了保证数据传输的可靠性,牺牲了数据包的传输速度,因为 “三次握手” 和 “数据包校验机制” 等把传输过程中的数据包的数量提高了一倍。

HTTP 协议

浏览器使用 HTTP 协议作为应用层协议,用来封装请求的文本信息;并使用 TCP/IP 作传输层协议将它发到网络上,所以在 HTTP 工作开始之前,浏览器需要通过 TCP 与服务器建立连接。也就是说 HTTP 的内容是通过 TCP 的传输数据阶段来实现的。

TCP和HTTP的关系示意图
TCP 和 HTTP 的关系示意图

浏览器端发起 http 请求流程

HTTP请求流程示意图
HTTP 请求流程示意图

从图中可以看到,浏览器中的 HTTP 请求从发起到结束一共经历了如下八个阶段:构建请求、查找缓存、准备 IP 和端口、等待 TCP 队列、建立 TCP 连接、发起 HTTP 请求、服务器处理请求、服务器返回请求和断开连接。

  1. 构建请求:例如 GET /index.html HTTP1.1

  2. 查找缓存,可点击查看下文缓存部分:真正发起请求之前,浏览器会现在浏览器缓存中查询是否有要请求的文件,如有并没有过期则直接返回资源的副本,并结束请求,否则发起请求

  3. 准备 IP 地址和端口:浏览器会请求 DNS 返回域名对应的 IP ,http 协议默认是 80 端口

  4. 等待 TCP 队列:同一个域名最多建立 6 个 TCP 连接,如果超过 6 个,会进入排队等待状态(将图片放到单独的 cdn 域名可以加快访问速度),如果不超过 6 个会建立 TCP 连接

  5. 建立 TCP 连接:该过程需要 3 次握手

  6. 发送 HTTP 请求:建立请求后,浏览器向服务器发送请求行、请求体和请求头

    • 请求行:包括请求方法、请求 URI(Uniform Resource Identifier)和 HTTP 版本协议
    • 请求体:如果是 POST 请求方法,需要发送请求体,用于发送一些数据给服务器
    • 请求头:包含浏览器的基础信息,操作系统、内核、cookie 等
    HTTP 请求数据格式
    HTTP 请求数据格式

服务器端处理 HTTP 请求流程

  1. 服务器根据请求返回内容:在命令行中输入以下命令:curl -i 注意这里加上了 - i 是为了返回响应行、响应头和响应体的数据,返回的结果如下图所示

    • 响应行:包括协议版本和状态码
    • 响应头:服务器自身的信息。如服务器生成返回数据的时间、返回的数据类型(JSON、HTML、流媒体等类型),以及服务器要在客户端保存的 Cookie 等信息
    • 响应体:服务器响应的数据
    服务器响应的数据格式
    服务器响应的数据格式

  2. 断开连接:关闭 TCP 连接,如果浏览器或者服务器在其头信息中加入了 connection:keep-Alive,TCP 连接在发送后将仍然保持打开状态,省去下次请求时需要建立连接的时间,提升资源加载速度

  3. 重定向:在控制台输入如下命令:curl -I geekbang.org -I 表示只需要获取响应头和响应行数据,而不需要获取响应体的数据。从图中可以看到,响应行返回的状态码是 301,表示需要重定向到另外一个网址,而需要重定向的网址正是包含在响应头的 Location 字段中,接下来,浏览器获取 Location 字段中的地址,并使用该地址重新导航

    服务器返回响应行和响应头(含重定向格式)
    服务器返回响应行和响应头(含重定向格式)

缓存

缓存(Cache)查找流程示意图:

缓存查找流程示意图
缓存查找流程示意图

缓存查找的好处

  • 缓解服务器端压力,提升性能(获取资源的耗时更短了)
  • 对于网站来说,缓存是实现快速资源加载的重要组成部分。

当然,如果缓存查找失败,就会进入网络请求过程了。

对于二次页面打开很快,主要是第一次加载页面过程中,缓存了一些数据,例如:

  • DNS 缓存
  • CDN 缓存
  • 浏览器缓存

DNS 缓存

DNS 缓存:有 dns 的地方,就有缓存。浏览器、操作系统、Local DNS、根域名服务器,它们都会对 DNS 结果做一定程度的缓存。

DNS 缓存过程如下:

  • 首先搜索浏览器自身的 DNS 缓存,如果存在,则域名解析到此完成。
  • 如果浏览器自身的缓存里面没有找到对应的条目,那么会尝试读取操作系统的 hosts 文件看是否存在对应的映射关系,如果存在,则域名解析到此完成。
  • 如果本地 hosts 文件不存在映射关系,则查找本地 DNS 服务器 (ISP 服务器,或者自己手动设置的 DNS 服务器),如果存在,域名到此解析完成。
  • 如果本地 DNS 服务器还没找到的话,它就会向根服务器发出请求,进行递归查询。

CDN 缓存

CDN 缓存:在浏览器本地缓存失效后,浏览器会向 CDN 边缘节点发起请求。类似浏览器缓存,CDN 边缘节点也存在着一套缓存机制。CDN 边缘节点缓存策略因服务商不同而不同,但一般都会遵循 http 标准协议,通过响应头中的 Cache-Control 字段来设置是否缓存该资源

优势:

  • CDN 节点解决了跨运营商和跨地域访问的问题,访问延时大大降低。
  • 大部分请求在 CDN 边缘节点完成,CDN 起到了分流作用,减轻了源服务器的负载。

服务器收到请求头后,会根据 If-None-Match 的值来判断请求的资源是否有更新:

  • 如果没有更新,就返回 304 状态码,相当于服务器告诉浏览器可使用本地的缓存,不请求资源。
  • 如果资源有更新,服务器就直接返回最新资源给浏览器。

浏览器缓存 (http 缓存)

浏览器缓存 (http 缓存):浏览器缓存其实就是浏览器保存通过 HTTP 获取的所有资源,是浏览器将网络资源存储在本地的一种行为。主要是静态资源 html、js、css 及字体图片资源等。当浏览器发现请求的资源已经在浏览器缓存中存有副本,它会拦截请求,返回该资源的副本,并直接结束请求,而不会再去源服务器重新下载。

浏览器缓存存在哪里?

  • 内存缓存(memory cache):存储空间比较小,访问速度快,退出进程时(关闭 Tab 页)数据会被清除。例如 js 及图片
  • 磁盘缓存(disk cache):存储空间比较大,访问速度慢,退出进程时(关闭 Tab 页)数据不会被清除。例如 css 资源

三级缓存原理 (访问缓存优先级)

  • 先在内存中查找,如果有,直接加载
  • 如果内存中不存在,则在硬盘中查找,如果有直接加载
  • 如果硬盘中也没有,那么就进行网络请求
  • 请求获取的资源缓存到硬盘和内存

浏览器缓存的分类

  • 强缓存
  • 协商缓存
强缓存

浏览器在加载资源时,会先根据本地缓存资源的 header 中的信息判断是否命中强缓存,如果命中则直接使用缓存中的资源不会再向服务器发送请求。

这里的 header 中的信息指的是 expirescahe-control

Expires

该字段是 http1.0 时的规范,它的值为一个绝对时间的 GMT 格式的时间字符串,比如 Expires:Mon,18 Oct 2066 23:59:59 GMT。这个时间代表着这个资源的失效时间,在此时间之前,即命中缓存。这种方式有一个明显的缺点,由于失效时间是一个绝对时间,所以当服务器与客户端时间偏差较大时,就会导致缓存混乱(本地时间也可以随便更改)。

Cache-Control(优先级高于 Expires)

Cache-Control 是 http1.1 时出现的 header 信息,主要是利用该字段的 max-age 值来进行判断,它是一个相对时间,例如 Cache-Control:max-age=3600,代表着资源的有效期是 3600 秒。

  • max-age: 缓存将在 xx 秒之后失效,在该缓存资源还未过期的情况下, 如果再次请求该资源,会直接返回缓存中的资源给浏览器。但如果缓存过期了,浏览器则会使用协商缓存,继续发起网络请求,并且在 HTTP 请求头中带上:If-None-Match:”4f80f-13c-3a1xb12a”

cache-control 除了该字段外,还有下面几个比较常用的设置值:

  • no-cache:需要进行协商缓存,发送请求到服务器确认是否使用缓存。
  • no-store:禁止使用缓存,每一次都要重新请求数据。
  • public:可以被所有的用户缓存,包括终端用户和 CDN 等中间代理服务器。
  • private:只能被终端用户的浏览器缓存,不允许 CDN 等中继缓存服务器对其缓存。
  • Cache-Control 与 Expires 可以在服务端配置同时启用,同时启用的时候 Cache-Control 优先级高。
协商缓存

当强缓存没有命中的时候,浏览器会发送一个请求到服务器,服务器根据 header 中的部分信息来判断是否命中缓存。如果命中,则返回 304 ,告诉浏览器资源未更新,可使用本地的缓存。

这里的 header 中的信息指的是 Last-Modify/If-Modify-SinceETag/If-None-Match.

Last-Modify/If-Modify-Since

浏览器第一次请求一个资源的时候,服务器返回的 header 中会加上 Last-Modify,Last-modify 是一个时间标识该资源的最后修改时间(只能精确到秒,所以间隔时间小于 1 秒的请求是检测不到文件更改的。)。

当浏览器再次请求该资源时,request 的请求头中会包含 If-Modify-Since,该值为缓存之前返回的 Last-Modify。服务器收到 If-Modify-Since 后,根据资源的最后修改时间判断是否命中缓存。

如果命中缓存,则返回 304,并且不会返回资源内容,并且不会返回 Last-Modify。

缺点

  • 短时间内资源发生了改变,Last-Modified 并不会发生变化
  • 周期性变化。如果这个资源在一个周期内修改回原来的样子了,我们认为是可以使用缓存的,但是 Last-Modified 可不这样认为,因此便有了 ETag
ETag/If-None-Match

Etag 是基于文件内容进行编码的,可以保证如果服务器有更新,一定会重新请求资源,但是编码需要付出额外的开销。

与 Last-Modify/If-Modify-Since 不同的是,Etag/If-None-Match 返回的是一个校验码。ETag 可以保证每一个资源是唯一的,资源变化都会导致 ETag 变化。服务器根据浏览器上送的 If-None-Match 值来判断是否命中缓存。

与 Last-Modified 不一样的是,当服务器返回 304 Not Modified 的响应时,由于 ETag 重新生成过,response header 中还会把这个 ETag 返回,即使这个 ETag 跟之前的没有变化。

Last-Modified 与 ETag 是可以一起使用的,服务器会优先验证 ETag,一致的情况下,才会继续比对 Last-Modified,最后才决定是否返回 304。

清除浏览器缓存

  1. 在浏览器设置界面中或使用快捷键直接清理缓存:ctrl+shift+delete
  2. 在调试页面中勾选 Disable cache
  3. 使用 ctrl+shift+R 强制刷新,不使用浏览器缓存来刷新页面
  4. 在 js 或 css 后加?v= 版本号

总结

当浏览器再次访问一个已经访问过的资源时,它会这样做:

  1. 看看是否命中强缓存,如果命中,直接使用缓存,不会再向服务器发送请求。
  2. 如果没有命中强缓存,就发请求到服务器检查是否命中协商缓存。
  3. 如果命中协商缓存,服务器会返回 304,告诉浏览器资源未更新,可使用本地的缓存。
  4. 否则,服务器会返回最新的资源。

登录状态是如何保持的?

Cookie 流程图:

Cookie流程图
Cookie 流程图

查看身份验证的两种方式中的 Cookie 部分

简单来说,如果服务器端发送的响应头内有 Set-Cookie 的字段,那么浏览器就会将该字段的内容保持到本地。当下次客户端再往该服务器发送请求时,客户端会自动在请求头中加入 Cookie 值后再发送出去。服务器端发现客户端发送过来的 Cookie 后,会去检查究竟是从哪一个客户端发来的连接请求,然后对比服务器上的记录,最后得到该用户的状态信息。

参考

日问 - http 相关

参考文章:http2 详解