从地址栏输入URL到页面显示发生了什么
这是一道经典的面试题,既考察了网络又考察了浏览器的原理。请求报文和响应报文有参考知乎文章。
(1)输入 URL
在浏览器输入 URL 默认是向服务器发送 get 请求。
(2)查看 HTTP 缓存
在输入 URL 之后,浏览器会优先查看本地有没有缓存该资源,如果有,继续看该资源是否为最新,是则直接使用,不是则要向服务器发送请求获取最新资源。
HTTP 缓存可以查看我写过的文章:juejin.cn/post/719738…
(3)解析 URL,获取协议、主机、端口等
假如本地没有缓存该资源,那么就要发送请求了,在发送请求之前,我们必须知道获取关键信息,比如该请求 URL 的协议、主机、端口等。
(4)浏览器组装 HTTP 请求报文
在获取关键信息之后,浏览器就可以组装 HTTP 请求报文,HTTP 请求报文由几个部分组成:
- 请求行:
格式如下,由请求方法、请求路径、HTTP 版本组成
GET /tribe HTTP 1.1
- 请求头:
以键值对的形式呈现,常见的请求头有:
(1)Accept:text/html,image/*:告诉服务器,浏览器可以接受文本,网页图片。
(2)
Accept-Encoding:gzip,compress:可以接受 gzip,compress压缩后数据。(3)
Host:localhost:8080:浏览器要找的主机。(4)IF-MODIFIED-Since:Tue,11Jul 2000 18:23:51:在协商缓存中用于判断资源是否过期。
(5)Referer:'http://localhost:8080/test/abc.html' :告诉服务器该请求从哪来。
(6)User-Agent:Nozilla/4.0(Com...):告诉服务器发送请求的浏览器的内核。
(7)
Cookie:HTTP 请求发送时,会把保存在该请求域名下的所有 cookie 值一起发送给 web 服务器。(8)Connection:close/Keep-Alive:保持链接,发完数据后,不关闭链接。
(9)
Range:用于断点续传。 Range:bytes=0-5 指定第一个字节的位置和最后一个字节的位置。用于告诉服务器自己想取对象的哪部分。
-
空行:用来区分头部和实体,如果在请求头故意加空行,那么空行后的内容将会被全部视为请求体/响应体。
-
请求体:请求的具体数据,所携带的参数会被放到这里。
(5)浏览器获取主机 IP 地址(将域名-> IP 地址)
在地址栏中输入的网站域名,例如 www.baidu.com。
浏览器首先会查询 本地 DNS 缓存,如果缓存中有该域名对应的 IP 地址,浏览器会直接使用缓存中的 IP 地址,否则进入下一步,DNS解析的方式有两种:
递归查询
递归查询是指请求方向 DNS 服务器发送域名查询请求,并要求对方负责对此进行递归式查询,最终得到该域名对应的 IP 地址或者查询不到结果为止。
递归查询需要 DNS 服务器把查询请求一层层地向根域名服务器发送,直到找到能够回答该域名对应 IP 地址的服务器为止。一般来说,客户端计算机会向自己 ISP 提供的 DNS 服务器发起递归查询请求。
迭代查询
迭代查询是指请求方在向 DNS 服务器发送域名查询请求后,DNS 服务器不会代替你再向其他 DNS 服务器发送请求,而是在本地进行查询并将查询结果返回给请求方。如果响应中没有查询结果,则请求方需要重新向另一个 DNS 服务器发送查询请求。如此往复,直到找到查询结果为止。
一般来说,当 DNS 服务器收到递归查询请求但无法处理时,会返回一个“指针”(名称服务器),然后客户端再向该名称服务器发起迭代查询请求。
综上所述
递归查询需要 DNS 服务器将查询请求进行一层层地向根域名服务器发送,直到找到能够回答该域名对应 IP 地址的服务器为止。
迭代查询则是在本地进行查询并将查询结果返回给请求方,并在响应中提供下一步的查询指针,需要请求方重新向另一个 DNS 服务器发送查询请求获取下一层的结果。
两种方式都是用于解决域名与 IP 地址之间的映射关系,但是递归查询和迭代查询的区别在于查询过程的深度和权限控制。
(6)三次握手建立 TCP 连接
当浏览器获取到 IP 地址之后,就可以发送请求了,发送请求需要建立 TCP 连接。
三次握手建立 TCP 连接:
- 第一次握手:客户端发送 SYN 表示想建立连接,并有 seq=x,代表初始发送数据的序列号;
- 第二次握手:服务端发送 SYN=1,ACK=1,ack=seq+1,seq=y;
- 第三次握手:客户端发送 SYN=1,ACK=1,ack=y+1
为什么不可以两次握手?
因为 TCP 连接是可靠连接,它要尽最大努力保证可靠性,三次握手其实是客户端和服务端互相确认自身和对方接收能力和发送能力正常的过程,两次握手无法让双方都确认自身和对方的数据发送和接收能力正常。
(7)服务端响应
服务端对该请求进行响应,如果命中协商缓存,则响应 304,如果是新资源,且成功,那么响应 200。
HTTP 响应报文由以下几部分组成:
- 响应行:
格式如下,由协议版本、状态码、状态描述组成
HTTP 1.1 200 OK
- 响应头:
以键值对的形式呈现,常见的响应头有:
Cache-Control
(1)Cache-Control:private 默认为private 响应只能够作为私有的缓存,不能在用户间共享。
(2)Cache-Control:public 浏览器和缓存服务器都可以缓存页面信息。
(3)Cache-Control:must-revalidate 对于客户机的每次请求,代理服务器必须向服务器验证缓存是否过时。
(4)Cache-Control:no-cache浏览器和缓存服务器都不应该缓存页面信息。
(5)Cache-Control:max-age=1010 秒之内,直接从本地缓存中取,不要发送请求到服务器索取资源。
(6)Cache-Control:no-store请求和响应的信息都不应该被存储在对方的磁盘系统中。Content-Type:text/html;charset=UTF-8
告诉客户端,资源文件的类型,还有字符编码,客户端通过utf-8对资源进行解码,然后对资源进行html解析。通常我们会看到有些网站是乱码的,往往就是服务器端没有返回正确的编码。Content-Encoding:gzip
告诉客户端,服务端发送的资源是采用gzip编码的,客户端看到这个信息后,应该采用gzip对资源进行解码。Transfer-Encoding:chunked
这个响应头告诉客户端,服务器发送的资源的方式是分块发送的。一般分块发送的资源都是服务器动态生成的,在发送时还不知道发送资源的大小,所以采用分块发送,每一块都是独立的,独立的块都能标示自己的长度,最后一块是0长度的,当客户端读到这个0长度的块时,就可以确定资源已经传输完了。Expires:Sun, 1 Jan 1994 01:00:00 GMT
这个响应头也是跟缓存有关的,告诉客户端在这个时间前,可以直接访问缓存副本,很显然这个值会存在问题,因为客户端和服务器的时间不一定会都是相同的,如果时间不同就会导致问题。
这个响应头是没有 Cache-Control:max-age 准确的,因为max-age=date中的date是个相对时间。Last-Modified: Dec, 26 Dec 2019 17:30:00 GMT
所请求的对象的最后修改日期Connection:keep-alive
这个字段作为回应客户端的 Connection:keep-alive,告诉客户端服务器的tcp连接也是一个长连接,客户端可以继续使用这个tcp连接发送http请求。Etag: "637060cd8c284d8af7ad3082f209582d"
一个对象的标志值,就一个对象而言,比如一个html文件,如果被修改了,其Etag也会被修改,所以,ETag的作用跟Last-Modified的作用差不多,主要供WEB服务器判断一个对象是否改变了。
比如前一次请求某个html文件时,获得了其 ETag,当这次又请求这个文件时,浏览器就会把先前获得ETag值发送给WEB服务器,然后WEB服务器会把这个ETag跟该文件的当前ETag进行对比,然后就知道这个文件有没有被修改了。Refresh: 5; 'url=baidu.com'
用于重定向,或者当一个新的资源被创建时。默认会在5秒后刷新重定向。
Access-Control-Allow-Origin: *:星号代表所有网站可以跨域资源共享,如果当前字段为*那么 Access-Control-Allow-Credentials 就不能为 true
Access-Control-Allow-Origin: www.baidu.com 指定哪些网站可以跨域资源共享Access-Control-Allow-Methods:GET,POST,PUT,DELETE 允许哪些方法来访问
Access-Control-Allow-Credentials: true 是否允许发送 cookie。
默认情况下,COOKIE不包括在CORS请求之中。设为true,即表示服务器明确许可,COOKIE可以包含在请求中,一起发给服务器。这个值也只能设为true,如果服务器不要浏览器发送COOKIE,删除该字段即可。如果access-control-allow-origin为*,当前字段就不能为true
-
空行
-
响应体:响应的具体数据。
(8)视情况关闭 TCP 连接 或重用
若关闭 TCP 则要进行四次握手。
四次挥手断开连接
- 第一次挥手:客户端发送 FIN=1 表示想断开连接,并发送 seq=x;
- 第二次挥手:服务端发送 ACK=1,ack=x+1,seq=y;
- 第三次挥手:服务端再次发送 FIN=1,表示其也想断开连接,并发送 ack=x+1(跟第二次一样),seq=n(新的),ACK=1;
- 第四次挥手:客户端发送 ACK=1,seq=x+1,ack=n+1
成功断开连接
为什么第二次挥手和第三次挥手不能合并为一次?
回顾四次挥手,先由A主动提出断开连接,既然A是主动要断开,那么其必然不再往B发送数据;B先回应A,我知道你想断开连接了,但是我还有数据没发完,所以等我发完,我会主动发送断开连接的请求给你的,到时候你再同意就好了。(假如A断开连接的请求发给B时,B并没有还未发送的数据,此时应该可以变成三次,但一般情况下是四次)
(9)对响应进行解码
浏览器接收到响应数据之后,可以根据响应报文中的 Content-Encoding 字段,主动对该数据进行压缩,使用且使用 Content-Encoding 指明的方式来解压。如 Content-Encoding:gzip
(10)对解码之后的数据进行解析,以 HTML 文档为例
我们先聊聊浏览器的底层架构
浏览器主要有5个进程
(1)主进程:负责页面显示,用户交互,子进程管理,存储功能。
(2)GPU 进程:绘制 UI 界面,主要负责跟图形相关的操作。
(3)网络进程:加载页面的网络资源。
(4)插件进程:负责插件的运行。
(5)渲染进程:核心任务是将 HTML、CSS、JavaScript 转换为用户可与之交互的网页, 排版引擎 Blink 和 V8 引擎都运行在该进程中,chrome 默认为每个 Tab 标签创建一个渲染进程,渲染进程运行在沙箱模式下。
沙箱模式:沙箱模式(Sandbox)是指在计算机系统中为运行一个应用程序或者进程提供一种安全环境的技术。沙箱模式可以限制该进程的权限,防止恶意程序对系统进行攻击或者进行非法操作。
在 Chrome 的沙箱模式下,每个渲染进程只有访问自身进程内的资源和代码执行权限,无法访问操作系统和其他进程的资源和代码,从而达到了保护系统和用户数据的目的。如果渲染进程中有恶意程序或漏洞,也只会影响当前进程,不会对其他进程和系统造成危害。因此,Chrome 的沙箱模式可以有效提高浏览器的安全性和稳定性,防止恶意程序的攻击。
重点讲述渲染进程
(1)GUI 渲染线程:负责渲染浏览器界面,解析 HTML CSS,构建 DOM 树和 render 树,布局和绘制等;
(2)JS 引擎线程:解析 JavaScript 脚本,运行代码;
(3)事件触发线程:辅助 JS 引擎,定时器被放入定时器触发线程中执行完毕后,会将回调函数放入该线程维护的任务队列当中,异步 http 请求线程同理,会将异步请求的回调函数添加进任务队列中去,等待 JS 引擎的处理;
(4)定时器触发线程:定时器所在线程;
(5)异步 http 请求线程:用于处理 XMLHttpRequest 请求;
渲染线程和 JS 引擎线程是互斥的关系,两者不能同时运行。
了解了浏览器的内部架构后,就可以来看具体是如何解析 HTML 文档的,这里可以看我之前写过的文章:juejin.cn/post/719663…
(12)构建渲染树
从DOM树的根节点遍历所有可见节点,不可见节点包括:script,meta 这样本身不可见的标签。被css隐藏的节点,如display: none;
对每一个可见节点,找到对应的 CSSOM 规则并应用;
发布可视节点的内容和计算样式。
(13)GUI 线程负责将渲染树渲染到页面上
注意:GPU 进程负责的是将渲染树上的图层绘制成位图,是不同的概念。