从输入url到页面显示中间发生了什么

182 阅读19分钟

整体概述

浏览器输入 URL 到页面渲染的整个过程是由浏览器各个进程之间的配合完成。

在开始正式流程之前,我们快速说明下几个主要进程的职责。

  1. 浏览器主进程: 主要负责用户交互(包括地址栏,书签栏,前进后退按钮等部分)、子进程管理和文件储存等功能。
  2. 网络进程:处理网络请求,从网上获取数据。
  3. 渲染进程:主要职责是负责一个 tab 内关于网页呈现的所有事情:把从网络下载的 HTML、CSS、JavaScript、图片等资源解析为可以显示和交互的页面。他会为每一个tab页面创建一个渲染进程。js引擎v8和排版引擎Blink包含在渲染进程中。因为渲染进程所有的内容都是通过网络获取的,会存在一些恶意代码利用浏览器漏洞对系统进行攻击,所以运行在渲染进程里面的代码是不被信任的。这也是为什么 Chrome 会让渲染进程运行在安全沙箱里,就是为了保证系统的安全。
  4. GPU进程:本来是负责处理3D css的,后来慢慢的UI界面也交给GPU来绘制。
  5. 插件进程:负责控制一个网页用到的所有插件,如 flash,因为插件很容易崩溃,把它放到独立的进程里不要让它影响别人。

这里我们把ui线程和存储器线程放到浏览器主进程来讲,chrome浏览器会根据机器的性能来控制是否将浏览器进程中的线程进行拆分,而且不同浏览器采用了不同的架构模式。

  • UI线程: 控制浏览器上的按钮及输入框;
  • 存储器线程: 控制文件等的访问;

介绍完浏览器各进程,现在说明浏览器输入 URL 到页面渲染的整个过程:

  1. 用户输入url,处理输入信息,主进程开始导航,交给网络进程处理。
  2. 网络进程向服务器发起网络请求,若返回的状态时是 301 或 302,则浏览器主进程需要重定向到其他 URL,一切从头开始。
  3. 服务器处理完响应后,浏览器对响应进行解码(例如gzip压缩),浏览器主进程根据响应头中的 Content-Type 字段判断响应体的数据类型。若是 application/octet-stream 字节流类型,浏览器会按下载类型来处理,该请求会被提交给浏览器的下载管理器,流程结束。若是 HTML,Safe Browsing 会查看网页是否安全,不安全网络进程会展示警告页,此外 CORB 检测(跨域资源共享)也会触发确保敏感数据不会被传递给渲染进程。HTML验证没问题,则通知渲染进程,发起提交文档。如果资源可缓存,则进行缓存。
  4. 渲染进程接收到提交文档信息,渲染进程开始准备,并跟网络进程建立传输数据的管道。
  5. 渲染进程接受完网络进程服务器响应体完整数据后,浏览器会更新页面,安全状态,url,前进后退的历史。默认情况,每个页面一个渲染进程。但若处于同一站点(同根域名+协议),那么渲染进程就会复用。

支线1:如果你监听过 beforeunload 事件,这个事件再次涉及到 浏览器进程 和 渲染器进程 的交互,当当前页面关闭时(关闭 Tab ,刷新等等),浏览器进程 需要通知 渲染器进程 进行相关的检查,对相关事件进行处理;

支线2:除了上述流程,有些页面还拥有 Service Worker (服务工作线程),Service Worker 让开发者对本地缓存及判断何时从网络上获取信息有了更多的控制权,如果 Service Worker 被设置为从本地 cache 中加载数据,那么就没有必要从网上获取更多数据了。

URL释义

URL(Uniform Resource Locator),统一资源定位符,用于定位互联网上资源,俗称网址。

比如 developer.mozilla.org/zh-CN/docs/… 遵守以下的语法规则:

scheme://host.domain:port/path/filename

各部分解释如下:

  • scheme:定义因特网服务的类型。常见的协议有 http、https、ftp、file,其中最常见的类型是 http,而 https 则是进行加密的网络传输。
  • host:定义域主机(http 的默认主机是 www)
  • domain:定义因特网域名,比如 mozilla.org
  • port:定义主机上的端口号(http 的默认端口号是 80,https 的默认端口号是 443)
  • path:定义服务器上的路径(如果省略,则文档必须位于网站的根目录中)。
  • filename:定义文档/资源的名称

用户输入

用户在地址栏输入关键字后,浏览器主进程需要判断用户输入的是 URL 还是 query,如果是搜索内容(query)则会拼接url,否则直接访问url

  • 若判断为搜索内容,地址栏使用默认搜索引擎,合成新的带搜索关键字的 URL(q 后为搜索关键字)

    cn.bing.com/search?q=te…

  • 若判断内容符合 URL 规则,则地址栏会根据输入内容,合称为完整的 URL

    www.baidu.com => www.baidu.com/

输入关键字后回车,页面请求资源过程,浏览器主进程通过 IPC (进程间通信) 把 URL 请求发送至网络进程,网络进程收到后发起真正的 URL 请求。浏览器主进程中的ui线程控制 tab 标签上的图标变成 loading 状态,此时页面没有立即替换为目标页面,需要等待提交文档阶段结束,页面内容才会被替换。

缓存处理

请求的时候网络进程首先会先去查找浏览器缓存,因为可能请求的资源已经在浏览器上缓存过了。

此时本地资源没有过期,服务器上的资源如果没有增删改,就会直接用缓存过的资源,而不需要重新向服务器请求并下载资源,这样服务器减少了请求,用户也节省了带宽,所以我们时常会看到为304的状态码。

详细了解可以阅读这篇文章,浏览器缓存控制讲解

如果没有缓存,网络进程会执行 DNS 查询,因为域名要转换成IP才能访问到服务器,所以要去查域名对应IP。

域名解析

浏览器输入网址进行请求时,首先要进行域名解析,因为浏览器并不能直接通过域名找到对应的服务器,而是要通过 IP 地址。

大家这里或许会有个疑问----计算机既可以被赋予 IP 地址,也可以被赋予主机名和域名。比如 www.hackr.jp。那怎么不一开始就赋予个 IP 地址?这样就可以省去解析麻烦。

我们先来了解下什么是 IP 地址

IP 地址

IP 地址是指互联网协议地址,是 IP Address 的缩写。IP 地址是 IP 协议提供的一种统一的地址格式,它为互联网上的每一个网络和每一台主机分配一个逻辑地址,以此来屏蔽物理地址的差异。IP 地址是一个 32 位的二进制数,比如 127.0.0.1 为本机 IP。

域名就相当于 IP 地址乔装打扮的伪装者,带着一副面具。它的作用就是便于记忆和沟通。

用户通常使用主机名或域名来访问对方的计算机,而不是直接通过 IP 地址访问。因为与 IP 地址的一组纯数字相比,用字母配合数字的表示形式来指定计算机名更符合人类的记忆习惯。

但要让计算机去理解名称,相对而言就变得困难了。因为计算机更擅长处理一长串数字。

为了解决上述的问题,DNS 服务应运而生。

什么是域名解析

DNS 提供通过域名查找 IP 地址,或逆向从 IP 地址反查域名的服务。

DNS 是一个网络服务器,我们的域名解析简单来说就是在 DNS 上查询一条信息记录。

例如 baidu.com  220.114.23.56(服务器外网IP地址)80(服务器端口号)

浏览器查找域名对应IP

  1. 浏览器缓存:浏览器会按照一定的频率缓存 DNS 记录。
  2. 操作系统缓存:如果浏览器缓存中找不到需要的 DNS 记录,那就去操作系统。
  3. host文件dns记录查找。
  4. 路由缓存:路由器也有 DNS 缓存。
  5. ISP 的 DNS 服务器:ISP 是互联网服务提供商(Internet Service Provider)的简称,ISP 有专门的 DNS 服务器应对 DNS 查询请求。
  6. 根服务器:ISP 的 DNS 服务器还找不到的话,它就会向根服务器发出请求,进行迭代查询(DNS 服务器先问根域名服务器 com 域名服务器的 IP 地址,然后再问 google.com 域名服务器,依次类推)。

如果想加速以上及之后的http请求过程的话可以使用缓存服务器CDN,CDN过程如下:

  1. 用户输入url地址后,本地DNS会解析url地址,不过会把最终解析权交给CNAME指向的CDN的DNS服务器。
  2. CDN的DNS服务器会返回给浏览器一个全局负载均衡IP。
  3. 用户会根据全局负载均衡IP去请求全局负载均衡服务器。
  4. 全局负载均衡服务器会根据用户的IP地址,url地址,会告诉用户一个区域负载均衡设备,让用户去请求它。
  5. 区域负载均衡服务器会为用户选择一个离用户较近的最优的缓存服务器,并把ip地址给到用户。
  6. 用户向缓存服务器发送请求,如果请求不到想要的资源的话,会一层层向上一级查找,直到查找到为止。

小结

浏览器通过向 DNS 服务器发送域名,DNS 服务器查询到与域名相对应的 IP 地址,然后返回给浏览器,浏览器再将 IP 地址打在协议上,同时请求参数也会在协议搭载,然后一并发送给对应的服务器。

如果请求协议是HTTPS,那么还需要建立SSL/TLS连接。

接下来介绍向服务器发送 HTTP 请求的整个阶段,整个阶段分为三个主要部分:TCP 三次握手、HTTP 请求及响应、TCP 四次挥手。

TCP 三次握手

在客户端发送数据之前会用客户端 IP 地址发起 TCP 三次握手用以同步客户端和服务端的序列号和确认号,并交换 TCP 窗口大小信息来建立 TCP 连接。

先清楚一个概念,http请求与TCP连接之间的关系:在客户端向服务端请求和返回的过程中,是需要去创建一个TCP connection,因为http是不存在连接这样一个概念的,它只有请求和响应这样一个概念,请求和响应都是一个数据包,中间要通过一个传输通道,这个传输通道就是在TCP里面创建了一个从客户端发起和服务端接收的一个连接。

握手时序

TCP标示

  • SYN(synchronous 建立联机)
  • ACK(acknowledgement 确认)
  • Seq(Sequence number 顺序号码)

三次握手时序图

  1. 第一次握手: 建立连接,客户端发送SYN=1、随机产生Seq=client_isn的数据包到服务器主机,等待服务器确认。

  2. 第二次握手: 服务器主机收到请求后确认联机(可以接受数据),发起第二次握手请求,ACK=(A的Seq+1)、SYN=1,随机产生Seq=server_isn的数据包到客户端。

  3. 第三次握手: 客户端收到后检查ACK是否正确,若正确,客户端会再发送确认包。ACK=服务器主机的Seq+1、Seq=client_isn,服务器主机收到后确认Seq值与ACK值,若正确,则建立连接。

如果是https连接,要在tcp三次握手之后创建加密连接,来加密通信的上层数据(建立 SSL/TLS 连接)

TCP/1.1 浏览器为每一个域名维护了6个TCP连接,超过排队等待。 TCP/2.0 浏览器为每个域名维护1个TCP持久连接。

三次握手数据包分析

这里采用的是 wireshark 官网地址 www.wireshark.org,是一个很好的网络数据包抓取和分析软件。

示例采用的网址 news.baidu.com,windows下打开cmd、Mac下打开终端 ping 下得到 ip 可以利用 wireshark 工具进行一次ip地址过滤,只分析指定的数据。

  1. 第一次握手,客户端发送一个TCP,标志位为SYN,Seq(序列号)=0,代表客户端请求建立连接,如下图所示

  2. 第二次握手,服务器发回数据包,标志位为[SYN, ACK],ACK设置为客户端第一次握手请求的Seq+1,即ACK=0+1=1,再随机产生一个Seq的数据包到客户端。

  3. 第三次握手请求,客户端再次发送确认数据包,标识位为ACK,把服务器发来的Seq+1,即ACK=0+1,发送给服务器,服务器成功收到ACK报文段之后,连接就建立成功了。

为什么需要三次握手

谢希仁著《计算机网络》中讲“三次握手”的目的是“为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误”。

我们考虑一个场景,客户端先发送了 SYN(seq = 90) 报文,然后客户端宕机了,而且这个 SYN 报文还被网络阻塞了,服务端并没有收到,接着客户端重启后,又重新向服务端建立连接,发送了 SYN(seq = 100) 报文(注意不是重传 SYN,重传的 SYN 的序列号是一样的)。

看看三次握手是如何阻止历史连接的:

客户端连续发送多次 SYN 建立连接的报文,在网络拥堵情况下:

  1. 一个「旧 SYN 报文」比「最新的 SYN 」 报文早到达了服务端;
  2. 那么此时服务端就会回一个 SYN + ACK 报文给客户端;
  3. 客户端收到后可以根据自身的上下文,判断这是一个历史连接(序列号过期或超时),那么客户端就会发送 RST 报文给服务端,表示中止这一次连接。

HTTP 请求

TCP 三次握手结束后,开始发送 HTTP 请求报文。

请求报文由请求行(Request Line)、请求头(Request Headers)、请求体(Payload)三个部分组成,如下图所示:

  • 请求行包含请求方法、URL、协议版本

    • 请求方法包含 8 种:GET、POST、PUT、DELETE、HEAD、OPTIONS、PATCH、TRACE。
    • URL 即请求地址,由 <协议>://<主机>:<端口>/<路径>?<参数> 组成
    • 协议版本即 http 版本号
    POST  /chapter17/user.html HTTP/1.1
    

    以上代码中“POST”代表请求方法,“/chapter17/user.html”表示 URL,“HTTP/1.1”代表协议和协议的版本。现在比较流行的是 Http1.1 版本

  • 请求头包含请求的附加信息,由 关键字/值 组成,每行一对,关键字和值用英文冒号“:”分隔。

    请求头通知服务器有关于客户端请求的信息。它包含许多有关的客户端环境和请求正文的有用信息。其中比如:Host,表示主机名,虚拟主机;Connection, HTTP/1.1 增加的;使用 keep-alive,即持久连接,一个连接可以发多个请求;User-Agent,请求发出者,兼容性以及定制化需求。

  • 请求体,可以承载多个请求参数的数据,包含回车符、换行符和请求数据,并不是所有请求都具有请求体。

    name=tom&password=1234&realName=tomson
    

    上面URL请求,请求体承载着 name、password、realName 三个请求参数。

HTTP 响应

服务器

服务器是网络环境中的高性能计算机,它侦听网络上的其他计算机(客户机)提交的服务请求,并提供相应的服务,比如网页服务、文件下载服务、邮件服务、视频服务。而客户端主要的功能是浏览网页、看视频、听音乐等等,两者截然不同。每台服务器上都会安装处理请求的应用——web server。常见的 web server 产品有 apache、nginx、IIS 或 Lighttpd 等。

web server 担任管控的角色,对于不同用户发送的请求,会结合配置文件,把不同请求委托给服务器上处理相应请求的程序进行处理(例如 CGI 脚本,JSP 脚本,servlets,ASP 脚本,服务器端 JavaScript,或者一些其它的服务器端技术等),然后返回后台程序处理产生的结果作为响应。

服务器接受到请求信息后,检查HTTP请求头是否包含缓存验证信息,验证缓存是否过期,返回304等对应状态码,或根据请求信息生成响应数据

MVC 后台处理阶段

后台开发现在有很多框架,但大部分都还是按照 MVC 设计模式进行搭建的。

MVC 是一个设计模式,将应用程序分成三个核心部件:模型(model)-- 视图(view)-- 控制器(controller),它们各自处理自己的任务,实现输入、处理和输出的分离。

  1. 视图(view)

    它是提供给用户的操作界面,是程序的外壳。

  2. 模型(model)

    模型主要负责数据交互。在 MVC 的三个部件中,模型拥有最多的处理任务。一个模型能为多个视图提供数据。

  3. 控制器(controller)

    它负责根据用户从"视图层"输入的指令,选取"模型层"中的数据,然后对其进行相应的操作,产生最终结果。控制器属于管理者角色,从视图接收请求并决定调用哪个模型构件去处理请求,然后再确定用哪个视图来显示模型处理返回的数据。

    这三层是紧密联系在一起的,但又是互相独立的,每一层内部的变化不影响其他层。每一层都对外提供接口(Interface),供上面一层调用。

    至于这一阶段发生什么?简而言之,首先浏览器发送过来的请求先经过控制器,控制器进行逻辑处理和请求分发,接着会调用模型,这一阶段模型会获取 redis db 以及 MySQL 的数据,获取数据后将渲染好的页面,响应信息会以响应报文的形式返回给客户端,最后浏览器通过渲染引擎将网页呈现在用户面前。

http 响应报文

响应报文由响应行(Response Line)、响应头部(Response Headers)、响应主体(Response)三个部分组成。如下图所示:

  1. 响应行包含:协议版本,状态码,状态码描述

    状态码规则如下:

    • 1xx:指示信息--表示请求已接收,继续处理。
    • 2xx:成功--表示请求已被成功接收、理解、接受。
    • 3xx:重定向--要完成请求必须进行更进一步的操作。
    • 4xx:客户端错误--请求有语法错误或请求无法实现。
    • 5xx:服务器端错误--服务器未能实现合法的请求。
  2. 响应头部包含响应报文的附加信息,由 名/值 对组成

  3. 响应主体包含回车符、换行符和响应返回数据,并不是所有响应报文都有响应数据

TCP 四次挥手

当数据传送完毕,需要断开 tcp 连接,此时发起 tcp 四次挥手。

  • 主动方向被动方发送报文,Fin=1、Ack=Z、Seq=X,表示已经没有数据传输了。并进入 FIN_WAIT_1 状态。(第一次挥手:由浏览器发起的,发送给服务器,我请求报文发送完了,你准备关闭吧)
  • 被动方发送报文,Ack=X+1、Seq=Z,表示同意关闭请求。此时主机主动方进入 FIN_WAIT_2 状态。(第二次挥手:由服务器发起的,告诉浏览器,我请求报文接受完了,我准备关闭了,你也准备吧)
  • 被动方向主动方发送报文段,Fin=1、Ack=X、Seq=Y,请求关闭连接。并进入 LAST_ACK 状态。(第三次挥手:由服务器发起,告诉浏览器,我响应报文发送完了,你准备关闭吧)
  • 主动方向被动方发送报文段,Ack=Y、Seq=X。然后进入等待 TIME_WAIT 状态。被动方收到主动方的报文段以后关闭连接。主动方等待一定时间未收到回复,则正常关闭。(第四次挥手:由浏览器发起,告诉服务器,我响应报文接受完了,我准备关闭了,你也准备吧)

浏览器渲染

渲染进程中主要包含以下线程:

  • 主线程 Main thread
  • 工作线程 Worker thread
  • 排版线程 Compositor thread
  • 光栅线程 Raster thread

其主要工作流程如下所示

  1. 浏览器创建Document对象并解析HTML,将解析到的元素和⽂本节点添加到⽂档中,此时 document.readystateloading(正在加载)。
  2. HTML解析器遇到没有 asyncdefer 的 script 时,将他们添加到⽂档中,然后执⾏⾏内或外部脚本。这些脚本会同步执⾏,并且在脚本下载和执⾏时解析器会暂停。这样就可以⽤ document.write() 把⽂本插⼊到输⼊流中。同步脚本经常简单定义函数和注册事件处理程序,他们可以遍历和操作script和他们之前的⽂档内容。
  3. 当解析器遇到设置了 async 属性的 script 时,开始下载脚本并继续解析⽂档。脚本会在它下载完成后尽快执⾏,但是解析器不会停下来等它下载。异步脚本禁止使⽤ document.write(),它们可以访问⾃⼰script和之前的⽂档元素
  4. 当⽂档完成解析,document.readystate 变成 interactive(可交互)
  5. 所有 defer 脚本会按照在⽂档出现的顺序执⾏,延迟脚本能访问完整⽂档树,禁止使⽤ document.write()
  6. 浏览器在 Document 对象上触发 DOMContentLoaded 事件
  7. 此时⽂档完全解析完成,浏览器可能还在等待如图⽚等内容加载,等这些内容完成载⼊ 并且所有异步脚本完成载⼊和执⾏,document.readState 变为complete(完成),window 触发 load 事件。

有关浏览器页面渲染的详细过程,可以看这篇文章:渲染中回流与重绘讲解