作为一个前端,被常问到输入一个url到到页面渲染中间发生了什么,每次都是说个大概,每个环节发生了什么,有哪些细节,还真不知道,今天终于痛下决心恶补下这个过程,好家伙,不看不知道,一看吓一跳,过程细节这么多,话不多说,进入学习阶段。
一、输入网址
在输入url的过程中,网址栏会智能匹配你输入的url字符串,从你的历史记录,书签中找到输入字符串对应的url,给你提示与之相对应你曾经访问过哪些网址。如访问曾访问过的页面,则访问的速度会大大加快。
二、DNS解析
百度得知DNS(Domain Name System,域名系统),因特网上作为域名和IP地址相互映射的一个分布式数据库,能够方便用户访问互联网,而不用去记能够被机器识别的ip。通过主机名,我们就可以的改主机对应的IP的过程叫做域名解析。
1.当我们发起请求,浏览器会解析该域名。首先查找本地硬盘的hosts文件(可加快域名解析)里面的IP地址。
2.当在本地hosts文件中没有找到,浏览器会发送一个DNS请求到本地DNS服务器。本地DNS服务器有网络接入商提供,例如移动,联通,电信等等
3、本地DNS查询缓存记录(递归方式查询),如存在,直接返回结果,如不存在,会向DNS根服务器进行查询
4、因为根DNS服务器没有记录具体的域名和IP的对应关系,所以会告诉本地DNS服务器,你需要去域服务器上搜索,同时会提供域服务器的地址。(迭代解析)
5、本地DNS服务器继续向域服务器发出请求。域服务器收到请求后,也不会直接返回域名和IP地址的对应关系,而是告诉本地DNS服务器,解析该域名服务器的地址。
6、本地DNS服务器向解析域名的服务器发送请求,得到一个域名和IP地址对应关系,此时本地DNS服务器不光要把IP地址返回给用户电脑,还要把这个对应关系保存在缓存,以备下次别的用户查询时们可以直接返回结果,加快访问速度。
递归解析
当局部DNS服务器不能解答用户机的DNS查询时,会对其他DNS服务器进行查询。一般是先向该域名的根域服务器进项查询,在由根域服务器向下查询,最后把查询的结果返回到局部DNS服务器,再由局部DNS服务器返回给用户的DNS
迭代解析
当局部DNS服务器自己不能回答客户机的DNS查询时,也可以通过迭代查询的方式进行解析,局部DNS服务器不是自己向其他DNS服务器进行查询,而是把能解析该域名的其他DNS服务器的IP地址返回给用户DNS程序,用户DNS程序再继续向这些DNS服务器进行查询,直到得到查询结果为止。
域名空间的命名
按其功能命名空间中用来描述 DNS 域名称的五个类别的介绍详见下图(面向度娘)

负载均衡
谈到DNS,我们就不得不说到负载均衡,因为他可以解决我们我们服务器因为访问过多导致宕机的可怕后果。负载均衡的原理是在DNS服务器中为同一个主机域名配置多个IP地址,在响应DNS查询时,DNS服务器对每个查询以主机记录的IP按顺序返回不同的解析结果,将用户的访问引导到不同机器上,从而达到分流的效果。
三、向服务器发送HTTP请求
当我们拿到域名对应IP后,浏览器会议一个随意的端口号(1024<prot<65535)向服务器的WEB程序(常见的是nginx)的80端口发起TCP的连接请求。这个连接请求到达服务器后,进入到网卡,然后进入内核的TCP/IP协议栈(用于识别该连接请求,解封包,然后一层一层剥开),还有可能要经过Netfilter放火墙(属于内核模块)的过滤,最终才能到达WEB程序,最终才能建立起TCP/IP的连接。
建立连接如图所示(万能度娘助阵)

当我们建立TCP连接后,发送一个http请求,一个典型的http request hearder一般需要包括请求的方法(GET,POST,PUT,DELETE,当然不常用还有HEAD,OPTION、TRACE)同时会有一些请求信息,请求信息包含三个部分:
<1>请求方法URI协议/版本
<2>请求头
<3>请求的正文
当我们建立TCP连接后,
POST/sample.jspHTTP/1.2 //方法URL版本
Accept: application/json, text/plain,
Accept-Language:zh-cn
Connection:Keep-Alive
Host:localhost
User-Agent:Mozila/4.0(compatible;MSIE5.01;Window NT5.0)
Accept-Encoding:gzip,deflate
如果我们为了接口的安全从而增加接口鉴权的功能,则也会在请求头中携带信息
appid: ****
noncestr:****
timestamp:****
sign:***
这些也是接口鉴权常用的信息
提示: 最后一个请求头之后是空行,发送回车符合换行符,通知服务器以下不在有请求头了,接下里就是请求的正文了。
当请求完成后需要断开连接(没错,依然还是度娘)

重点来了,小本本记好。
TCP的三次握手
第一次握手:用户端讲标志位SYN置为1,随机产生一个值为seq=x的数据包发送到服务器,用户端X进入SYN_SENT状态,等待服务器确认。
第二次握手:服务器收到数据包后由标志位SYN=1知道用户端请求建立连接,服务端将标志位SYN和ACK都置为1,ack=x+1,随机产生一个值seq=y,并将该数据包发送给用户端以确认连接请求,服务端进入SYN_RCVD状态。
第三次握手:用户端收到确认后,检查ack是否为x+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=y+1,并将该数据包发送到用户端,服务端检查ack是否为y+1,ACK是否为1,如果正确则连接建立成功,用户端和服务端进入ESTABLISHED状态,完成三次握手,这个时候用户端与服务端就可以进行数据传输了。
TCP四次挥手
第一次挥手:用户端发送一个FIN,用来关闭Client到服务器的数据传输,Client进入FIN_WAIT_1状态
第二次挥手:服务器收到FIN后,发送一个ACK给用户端,确认序号为收到序号+1,服务器进入CLOSE_WAIT状态。
第三次挥手:服务器发送一个FIN,用来关闭服务器到用户端的数据传输,服务器进入LAST_ACK状态。
第四次挥手:用户端收到FIN后,用户端进入TIME_WAIT状态,接着发送一个ACK给服务器,确认序号为收到序号+1,服务器进入CLOSED状态。连接断开
特别提示
服务端在LISTEN状态下,收到建立连接请求的SYN报文后,把ACK和SYN放在一个报文里发送给客户端。而关闭连接时,当收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还能接收数据,己方也未必全部数据都发送给对方了,所以己方可以立即close,也可以发送一些数据给对方后,再发送FIN报文给对方来表示同意现在关闭连接,因此,己方ACK和FIN一般都会分开发送。
为什么会需要三次握手呢?(菜鸡的我,邀请度娘帮助)
《计算机网络》第四版中讲“三次握手”的目的是“为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误”
书中的例子是这样的,“已失效的连接请求报文段”的产生在这样一种情况下:client发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达server。本来这是一个早已失效的报文段。但server收到此失效的连接请求报文段后,就误认为是client再次发出的一个新的连接请求。于是就向client发出确认报文段,同意建立连接。
假设不采用“三次握手”,那么只要server发出确认,新的连接就建立了。由于现在client并没有发出建立连接的请求,因此不会理睬server的确认,也不会向server发送数据。但server却以为新的运输连接已经建立,并一直等待client发来数据。这样,server的很多资源就白白浪费掉了。采用“三次握手”的办法可以防止上述现象发生。例如刚才那种情况,client不会向server的确认发出确认。server由于收不到确认,就知道client并没有要求建立连接。”。主要目的防止server端一直等待,浪费资源。
四、服务器永久响应重定向
服务器给浏览器响应一个301永久重定向响应,这样浏览器就会访问“http://www.google.com/” 而非“http://google.com/”。
为什么服务器一定要重定向而不是直接发送用户想看的网页内容呢?其中一个原因跟搜索引擎排名有关。如果一个页面有两个地址,就像http://www.yy.com/和http://yy.com/,搜索引擎会认为它们是两个网站,结果造成每个搜索链接都减少从而降低排名。而搜索引擎知道301永久重定向是什么意思,这样就会把访问带www的和不带www的地址归到同一个网站排名下。还有就是用不同的地址会造成缓存友好性变差,当一个页面有好几个名字时,它可能会在缓存里出现好几次。
重定向小知识
状态码301和302都表示重定向,也就是浏览器在拿到服务器返回的状态码时会自动跳转到一个新的URL地址,这个地址可以Location中取到。(相同点)
301表示旧地址A的资源已经被永久地移除了(这个资源不可访问了),搜索引擎在抓取新内容的同时也将旧的网址交换为重定向之后的网址;302表示旧地址A的资源还在(仍然可以访问),这个重定向只是临时地从旧地址A跳转到地址B,搜索引擎会抓取新的内容而保存旧的网址。如果考虑到SEO优化的条件,302会远远好于301
重定向原因:
(1)网站调整(如改变网页目录结构);
(2)网页被移到一个新地址;
(3)网页扩展名改变(如应用需要把.php改成.Html或.shtml)。
这种情况下,如果不做重定向,则用户收藏夹或搜索引擎数据库中旧地址只能让访问客户得到一个404页面错误信息,访问流量白白丧失;再者某些注册了多个域名的网站,也需要通过重定向让访问这些域名的用户自动跳转到主站点等。
五、服务器处理请求
后端在固定的接口接收到TCP报文开始,会对TCP连接进行处理,对HTTP协议进行解析,并按照报文格式进行进一步封装成HTTP Request对象。通常请求会反向代理到服务器中,因为当网站访问量非常大,网站越来越慢,一台服务器已经不够用了。于是将同一个应用部署在多台服务器上,将大量用户的请求分配给多台机器处理。此时,客户端不是直接通过HTTP协议访问某网站应用服务器,而是先请求到Nginx,Nginx再请求应用服务器,然后将结果返回给客户端,这里Nginx的作用是反向代理服务器。同时也带来了一个好处,其中一台服务器万一挂了,只要还有其他服务器正常运行,就不会影响用户使用。
什么是反向代理?
客户端本来可以直接通过HTTP协议访问某网站应用服务器,网站管理员可以在中间加上一个Nginx,客户端请求Nginx,Nginx请求应用服务器,然后将结果返回给客户端,此时Nginx就是反向代理服务器。
六、服务端返回一个HTTP响应
服务器收到我们请求后,会处理我们的请求,然后把处理的结果返回,就是返回一个返回一个HTTP响应。
HTTP响应与HTTP请求相似,HTTP响应也由3个部分构成,分别是:
<1>状态行
<2>响应头(Response Header)
<3>响应正文`
HTTP/1.2 200 OK
Date: Fri, 25 Dec 2020 06:30:51 GMT
Content-Type:application/json
Connection: keep-alive
Content-Encoding: gzip
状态行:
状态行由协议版本、数字形式的状态代码、及相应的状态描述,各元素之间以空格分隔。
格式: HTTP-Version Status-Code Reason-Phrase CRLF
例如: HTTP/1.2 200 OK
– 协议版本:是用http1.0还是其他版本
– 状态描述:状态描述给出了关于状态代码的简短的文字描述。比如状态代码为200时的描述为 ok
– 状态代码:状态代码由三位数字组成,第一个数字定义了响应的类别,且有五种可能取值。如下.
1xx:信息性状态码,表示服务器已接收了客户端请求,客户端可继续发送请求。
2xx:成功状态码,表示服务器已成功接收到请求并进行处理。
3xx:重定向状态码,表示服务器要求客户端重定向。
4xx:客户端错误状态码,表示客户端的请求有非法内容。
5xx:服务器错误状态码,表示服务器未能正常处理客户端的请求而出现意外错误。
响应头:
响应头部:由关键字/值对组成,每行一对,关键字和值用英文冒号":"分隔,典型的响应头有:

响应正文:
{"code":"200","msg":"查询购物车商品数量成功","traceId":"ac11b3cf16088778512891092d6a45","data":{"skuCount":1,"sumTotal":1}}
七、浏览器显示HTML
在浏览器没有完整接受全部HTML文档时,它就已经开始显示这个页面了,浏览器是如何把页面呈现在屏幕上的呢?不同浏览器可能解析的过程不太一样,这里我们只介绍webkit的渲染过程,下图对应的就是WebKit渲染的过程,这个过程包括:
解析html以构建dom树 -> 构建render树 -> 布局render树 -> 绘制render树

浏览器在解析html文件时,会”自上而下“加载,并在加载过程中进行解析渲染。在解析过程中,如果遇到请求外部资源时,如图片、外链的CSS、iconfont等,请求过程是异步的,并不会影响html文档进行加载。
解析过程中,浏览器首先会解析HTML文件构建DOM树,然后解析CSS文件构建渲染树,等到渲染树构建完成后,浏览器开始布局渲染树并将其绘制到屏幕上。这个过程比较复杂,涉及到两个概念: reflow(回流)和repain(重绘)。
DOM节点中的各个元素都是以盒模型的形式存在,这些都需要浏览器去计算其位置和大小等,这个过程称为relow;当盒模型的位置,大小以及其他属性,如颜色,字体,等确定下来之后,浏览器便开始绘制内容,这个过程称为repain。
页面在首次加载时必然会经历reflow和repain。reflow和repain过程是非常消耗性能的,尤其是在移动设备上,它会破坏用户体验,有时会造成页面卡顿。所以我们应该尽可能少的减少reflow和repain。
当文档加载过程中遇到js文件,html文档会挂起渲染(加载解析渲染同步)的线程,不仅要等待文档中js文件加载完毕,还要等待解析执行完毕,才可以恢复html文档的渲染线程。因为JS有可能会修改DOM,最为经典的document.write,这意味着,在JS执行完成前,后续所有资源的下载可能是没有必要的,这是js阻塞后续资源下载的根本原因。所以官方推荐js是放在html文档末尾的。
JS的解析是由浏览器中的JS解析引擎完成的,比如谷歌的是V8。JS是单线程运行,也就是说,在同一个时间内只能做一件事,所有的任务都需要排队,前一个任务结束,后一个任务才能开始。但是又存在某些任务比较耗时,如IO读写等,所以需要一种机制可以先执行排在后面的任务,这就是:同步任务(synchronous)和异步任务(asynchronous)。
JS的执行机制就可以看做是一个主线程加上一个任务队列(task queue)。同步任务就是放在主线程上执行的任务,异步任务是放在任务队列中的任务。所有的同步任务在主线程上执行,形成一个执行栈;异步任务有了运行结果就会在任务队列中放置一个事件;脚本运行时先依次运行执行栈,然后会从任务队列里提取事件,运行任务队列中的任务,这个过程是不断重复的,所以又叫做事件循环(Event loop)。
又一个重点,接着拿出来小本本
Event loop
event loop是一个执行模型,在不同的地方有不同的实现。浏览器和NodeJS基于不同的技术实现了各自的Event Loop。
宏任务和微任务
宏任务,macrotask,也叫tasks。 一些异步任务的回调会依次进入macro task queue,等待后续被调用,这些异步任务包括:
setTimeout
setInterval
setImmediate (Node独有)
requestAnimationFrame (浏览器独有)
I/O
UI rendering (浏览器独有)
微任务,microtask,也叫jobs。 另一些异步任务的回调会依次进入micro task queue,等待后续被调用,这些异步任务包括:
process.nextTick (Node独有)
Promise
Object.observe
MutationObserver

由图我们可知其流程为
1、执行全局Script同步代码,这些同步代码有一些是同步语句,有一些是异步语句(比如setTimeout等);
2、全局Script代码执行完毕后,调用栈Stack会清空;
3、从微任务microtask queue中取出位于队首的回调任务,放入调用栈Stack中执行,执行完后microtask queue长度减1;
4、继续取出位于队首的任务,放入调用栈Stack中执行,以此类推,直到直到把microtask queue中的所有任务都执行完毕。注意,如果在执行microtask的过程中,又产生了microtask,那么会加入到队列的末尾,也会在这个周期被调用执行;
5、microtask queue中的所有任务都执行完毕,此时microtask queue为空队列,调用栈Stack也为空;
6、取出宏任务macrotask queue中位于队首的任务,放入Stack中执行;
7、执行完毕后,调用栈Stack为空;
八、浏览器发送请求获取嵌入在 HTML 中的资源
浏览器会发送一个获取请求来重新获得这些文件。比如我要获取外图片,CSS,JS文件等,
至此,从输入url到页面展示的过程基本结束。
本文详细阐述了从输入URL到页面渲染的整个过程,涵盖了DNS解析、TCP连接的三次握手和四次挥手、HTTP请求与响应、服务器处理与重定向、浏览器渲染等内容,揭示了网页加载背后的复杂机制。
234

被折叠的 条评论
为什么被折叠?



