学习笔记:输入url到页面渲染的整个过程
输入url到页面渲染的整个过程
原文:https://github.com/alex/what-happens-when
解析URL
浏览器通过URL知道协议与请求的资源,当协议或主机名不合法时,将地址栏中输入的文字传给默认的搜索引擎。并且浏览器检查输入是否有非ASCII的字符,如果有会使用Punycode编码。
浏览器检查自带的“预加载 HSTS(HTTP严格传输安全)”列表,这个列表里包含了那些请求浏览器只使用HTTPS进行连接的网站。如果网站在这个列表里,浏览器会使用 HTTPS 而不是 HTTP 协议,否则,最初的请求会使用HTTP协议发送。
DNS
DNS 的作用就是通过域名查询到具体的 IP。
DNS查询
- 浏览器检查域名是否在缓存当中(要查看 Chrome 当中的缓存, 打开 chrome://net-internals/#dns)。
- 如果缓存中没有,就去调用 gethostbyname 库函数(操作系统不同函数也不同)进行查询。
- gethostbyname 函数在试图进行DNS解析之前首先检查域名是否在本地 Hosts 里,Hosts 的位置不同的操作系统有所不同
- 如果 gethostbyname 没有这个域名的缓存记录,也没有在 hosts 里找到,它将会向 DNS 服务器发送一条 DNS查询请求。DNS 服务器是由网络通信栈提供的,通常是本地路由器或者 ISP 的缓存 DNS 服务器。
- 查询本地 DNS 服务器
- 如果 DNS 服务器和我们的主机在同一个子网内,系统会按照下面的 ARP 过程对 DNS 服务器进行 ARP查询
- 如果 DNS 服务器和我们的主机在不同的子网,系统会按照下面的 ARP 过程对默认网关进行查询
ARP 过程
我们通过目标 IP 地址,和用于发送 ARP 广播的接口的 MAC 地址来发送 ARP(地址解析协议)广播。得到一个 ARP Reply,获得了DNS 服务器或者默认网关的 IP 地址,继续 DNS 请求:
- 使用 53 端口向 DNS 服务器发送 UDP 请求包,如果响应包太大,会使用 TCP 协议
- 如果本地/ISP DNS 服务器没有找到结果,它会发送一个递归查询请求,一层一层向高层 DNS服务器做查询,直到查询到起始授权机构,如果找到会把结果返回。
- DNS迭代查询与递归查询的区别就是前者是由客户端去做请求,后者是由系统配置的 DNS 服务器做请求,得到结果后将数据返回给客户端。
TCP
使用套接字
当浏览器得到了目标服务器的 IP 地址,以及 URL 中给出来端口号(http 协议默认端口号是 80, https 默认端口号是 443),它会调用系统库函数 socket ,请求一个 TCP流套接字,对应的参数是 AF_INET/AF_INET6 和 SOCK_STREAM 。
- 这个请求首先被交给传输层,在传输层请求被封装成 TCP segment。目标端口会被加入头部,源端口会在系统内核的动态端口范围内选取(Linux下是ip_local_port_range)
- TCP segment 被送往网络层,网络层会在其中再加入一个 IP头部,里面包含了目标服务器的IP地址以及本机的IP地址,把它封装成一个IP packet。
- 这个 TCP packet 接下来会进入链路层,链路层会在封包中加入 frame头部,里面包含了本地内置网卡的MAC地址以及网关(本地路由器)的 MAC 地址。像前面说的一样,如果内核不知道网关的 MAC地址,它必须进行 ARP 广播来查询其地址。
TCP传输
建立连接时
第一次
第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。
第二次
第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;
第三次
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。
完成三次握手,客户端与服务器开始传送数据。
关闭连接时
- TCP客户端发送一个FIN,用来关闭客户到服务器的数据传送。
- 服务器收到这个FIN,它发回一个ACK,确认序号为收到的序号加1。和SYN一样,一个FIN将占用一个序号。
- 服务器关闭客户端的连接,发送一个FIN给客户端。
- 客户端发回ACK报文确认,并将确认序号设置为收到序号加1。
TLS握手
- 客户端发送一个 ClientHello 消息到服务器端,消息中同时包含了它的 Transport LayerSecurity(TLS)版本,可用的加密算法和压缩算法。
- 服务器端向客户端返回一个 ServerHello消息,消息中包含了服务器端的TLS版本,服务器所选择的加密和压缩算法,以及数字证书认证机构(CertificateAuthority,缩写CA)签发的服务器公开证书,证书中包含了公钥。客户端会使用这个公钥加密接下来的握手过程,直到协商生成一个新的对称**
- 客户端根据自己的信任CA列表,验证服务器端的证书是否可信。如果认为可信,客户端会生成一串伪随机数,使用服务器的公钥加密它。这串随机数会被用于生成新的对称**
- 服务器端使用自己的私钥解密上面提到的随机数,然后使用这串随机数生成自己的对称主**
- 客户端发送一个 Finished 消息给服务器端,使用对称**加密这次通讯的一个散列值 服务器端生成自己的 hash值,然后解密客户端发送来的信息,检查这两个值是否对应。如果对应,就向客户端发送一个 Finished 消息,也使用协商好的对称**加密
- 从现在开始,接下来整个 TLS 会话都使用对称秘钥进行加密,传输应用层(HTTP)内容
HTTP 协议
HTTP 服务器请求处理
HTTPD(HTTP Daemon)在服务器端处理请求/响应。最常见的 HTTPD 有 Linux 上常用的 Apache 和 nginx,以及 Windows 上的 IIS。
- HTTPD 接收请求
- 服务器把请求拆分为以下几个参数:
HTTP 请求方法(GET, POST, HEAD, PUT, DELETE, CONNECT, OPTIONS, 或者TRACE)。直接在地址栏中输入 URL 这种情况下,使用的是 GET 方法
域名:google.com
请求路径/页面:/ (我们没有请求google.com下的指定的页面,因此 / 是默认的路径) - 服务器验证其上已经配置了 google.com 的虚拟主机
- 服务器验证 google.com 接受 GET 方法
- 服务器验证该用户可以使用 GET 方法(根据 IP 地址,身份信息等)
- 如果服务器安装了 URL 重写模块(例如 Apache 的 mod_rewrite 和 IIS 的 URLRewrite),服务器会尝试匹配重写规则,如果匹配上的话,服务器会按照规则重写这个请求
- 服务器根据请求信息获取相应的响应内容,这种情况下由于访问路径是 “/” ,会访问首页文件(你可以重写这个规则,但是这个是最常用的)。
- 服务器会使用指定的处理程序分析处理这个文件,假如 Google 使用 PHP,服务器会使用 PHP 解析 index文件,并捕获输出,把 PHP 的输出结果返回给请求者
浏览器
浏览器的功能是从服务器上取回你想要的资源,然后展示在浏览器窗口当中。资源通常是 HTML 文件,也可能是 PDF,图片,或者其他类型的内容。资源的位置通过用户提供的 URI(Uniform Resource Identifier) 来确定。
浏览器高层架构
组成浏览器的组件有:
- 用户界面 用户界面包含了地址栏,前进后退按钮,书签菜单等等,除了请求页面之外所有你看到的内容都是用户界面的一部分
- 浏览器引擎 浏览器引擎负责让 UI 和渲染引擎协调工作
- 渲染引擎 渲染引擎负责展示请求内容。如果请求的内容是 HTML,渲染引擎会解析 HTML 和 CSS,然后将内容展示在屏幕上
- 网络组件 网络组件负责网络调用,例如 HTTP 请求等,使用一个平台无关接口,下层是针对不同平台的具体实现
- UI后端 UI 后端用于绘制基本 UI 组件,例如下拉列表框和窗口。UI 后端暴露一个统一的平台无关的接口,下层使用操作系统的 UI方法实现
- Javascript 引擎 Javascript 引擎用于解析和执行 Javascript 代码
- 数据存储 数据存储组件是一个持久层。浏览器可能需要在本地存储各种各样的数据,例如 Cookie 等。浏览器也需要支持诸如localStorage,IndexedDB,WebSQL 和 FileSystem 之类的存储机制
渲染
HTML 解析
浏览器渲染引擎从网络层取得请求的文档,一般情况下文档会分成8kB大小的分块传输。
HTML 解析器的主要工作是对 HTML 文档进行解析,生成解析树。
解析树是以 DOM 元素以及属性为节点的树。DOM是文档对象模型(Document Object Model)的缩写,它是 HTML 文档的对象表示,同时也是 HTML 元素面向外部(如Javascript)的接口。树的根部是"Document"对象。整个 DOM 和 HTML 文档几乎是一对一的关系。
构建方式:首先,将Response body中的html字符流代码通过状态机进行词法分析拆分成词(token),接下来词会转换为节点(node),不同的节点通过联系构建成dom树
CSS 解析
- 根据 CSS词法和句法 分析CSS文件和style标签包含的内容以及style 属性的值
- 每个CSS文件都被解析成一个样式表对象(StyleSheetobject),这个对象里包含了带有选择器的CSS规则,和对应CSS语法的对象
- CSS解析器可能是自顶向下的,也可能是使用解析器生成器生成的自底向上的解析器
页面渲染
- 通过遍历DOM节点树创建一个“Frame 树”或“渲染树”,并计算每个节点的各个CSS样式值
- 通过累加子节点的宽度,该节点的水平内边距(padding)、边框(border)和外边距(margin),自底向上的计算"Frame
树"中每个节点的首选(preferred)宽度 - 通过自顶向下的给每个节点的子节点分配可行宽度,计算每个节点的实际宽度
- 通过应用文字折行、累加子节点的高度和此节点的内边距(padding)、边框(border)和外边距(margin),自底向上的计算每个节点的高度
- 使用上面的计算结果构建每个节点的坐标
- 当存在元素使用 floated,位置有 absolutely 或 relatively属性的时候,会有更多复杂的计算,详见http://dev.w3.org/csswg/css2/ 和http://www.w3.org/Style/CSS/current-work
- 创建layer(层)来表示页面中的哪些部分可以成组的被绘制,而不用被重新栅格化处理。每个帧对象都被分配给一个层
页面上的每个层都被分配了图形 - 每个层的帧对象都会被遍历,计算机执行绘图命令绘制各个层,此过程可能由CPU执行栅格化处理,或者直接通过D2D/SkiaGL在GPU上绘制
- 上面所有步骤都可能利用到最近一次页面渲染时计算出来的各个值,这样可以减少不少计算量
- 计算出各个层的最终位置,一组命令由 Direct3D/OpenGL发出,GPU命令缓冲区清空,命令传至GPU并异步渲染,帧被送到Window Server。
GPU渲染
- 在渲染过程中,图形处理层可能使用通用用途的 CPU,也可能使用图形处理器 GPU
- 当使用 GPU 用于图形渲染时,图形驱动软件会把任务分成多个部分,这样可以充分利用 GPU强大的并行计算能力,用于在渲染过程中进行大量的浮点计算。