浏览器中输入URL后发生了什么?


浏览器中输入URL后发生了什么?

会经过下面的主要流程:

DNS域名解析 => 建立TCP连接 => 发起HTTP请求 => 接收响应结果 => 浏览器解析HTML渲染

1.DNS域名解析

DNS原理

DNS(Domain Name Server)用来返回某个域名对应主机的ip的服务器。

根DNS (.)

只负责提供各类顶级DNS服务器ip地址. 是域名解析的入口.

顶级DNS (TLD, Top Level Domain)

负责提供二级域名的DNS服务器IP地址. 每一个顶级域名都有对应的DNS的服务器,它们通常由专门的机构公司来维护. 比如.comVerisign Global Registry Services公司维护,.eduEducause公司维护. 它们各自提供自家域名下的子域名(二级域名)的名称服务. 通常我们所说的"购买域名"就是向这些公司的数据库注册一条记录.

权威DNS

负责提供三级域名对应的主机IP地址。由域名购买者提供,大多数域名注册公司同时提供了权威DNS托管服务。

解析过程

获取域名对应的服务器ip的过程:

1、浏览器缓存

当用户通过浏览器访问某域名时,浏览器首先会在自己的缓存中查找是否有该域名对应的IP地址(若曾经访问过该域名且没有清空缓存便存在);

2、系统Hosts

当浏览器缓存中无域名对应IP则会自动检查用户计算机系统Hosts文件是否有该域名对应IP;

3、本地域名服务器

当在用户客服端查找不到域名对应IP地址,则将进入本地DNS缓存中进行查询。通常这个本地域名DNS会配置成运营商(ISP)指定的DNS,但也不是必须的。

4、迭代查询

当以上均未完成,则会由本地DNS开始进行迭代查询:

  • 向根域名服务器查询,得到顶级域名服务器的IP地址
  • 向顶级域名服务器查询,得到权威域名服务器的IP地址
  • 向权威域名服务器查询,最终得到域名的ip

都不行则进入根服务器进行查询。全球仅有13台根域名服务器,1个主根域名服务器,其余12为辅根域名服务器。根域名收到请求后会查看区域文件记录,若无则将其管辖范围内顶级域名(如.com)服务器IP告诉本地DNS服务器;

5、保存结果到缓存

本地域名服务器把返回的结果保存到缓存,以备下一次使用,同时将该结果反馈给客户端,客户端通过这个IP地址与web服务器建立链接。

image-20220124043217147

2.建立TCP连接

TCP三次握手与四次挥手

为什么tcp是三次握手不是两次

image-20220124043730116

“已失效的连接请求报文段”的产生在这样一种情况下:client发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达server。本来这是一个早已失效的报文段。但server收到此失效的连接请求报文段后,就误认为是client再次发出的一个新的连接请求。于是就向client发出确认报文段,同意建立连接。假设不采用“三次握手”,那么只要server发出确认,新的连接就建立了。由于现在client并没有发出建立连接的请求,因此不会理睬server的确认,也不会向server发送数据。但server却以为新的运输连接已经建立,并一直等待client发来数据。这样,server的很多资源就白白浪费掉了。采用“三次握手”的办法可以防止上述现象发生。例如刚才那种情况,client不会向server的确认发出确认。server由于收不到确认,就知道client并没有要求建立连接。”

image-20220124044221239

TCP连接关闭时进行四次握手过程:

客户端:“你好,我这边没有数据要传了,我要关闭咯。”

服务端:“收到~我看一下我这边有没数据要传的。”

服务端:“我这边也没有数据要传啦,我们可以关闭连接咯~”

客户端:“ok~”

3.发起HTTP请求

这个过程会涉及到HTTP缓存的问题。

http缓存

浏览器缓存可以分为两种模式,强缓存协商缓存

强缓存(无HTTP请求,无需协商

直接读取本地缓存,无需跟服务端发送请求确认,http返回状态码是200(from memory cache或者from disk cache ,不同浏览器返回的信息不一致的)。

image-20220124045243370

  • 对应的Http header有:
    • Cache-Control
    • Expires

协商缓存(有HTTP请求,需协商

浏览器虽然发现了本地有该资源的缓存,但是不确定是否是最新的,于是想服务器询问,若服务器认为浏览器的缓存版本还可用,那么便会返回304(Not Modified) http状态码。

  • 对应的Http header有:

    • Last-Modified(缺点只能精确到1s)
      • ETag
Http Header 描述
Cache-Control 指定缓存机制,优先级最高
Pragma http1.0字段,已废弃,为了兼容一般使用no-cache
Expires http1.0字段,指定缓存的过期时间
Last-Modified http1.0字段,资源最后一次的修改时间
ETag 唯一标识请求资源的字符串,会覆盖Last-Modified

image-20220124050019783

4.接收响应结果

接收服务器对请求处理后返回的数据,服务器具体怎么处理的,这个在前端就不具体展开了。

5.浏览器解析HTML渲染

  1. 从HTML解析出DOM Tree(DOM树)
  2. 从CSS解析出CSSOM Tree(CSS规则树)
  3. JavaScript代码由JavaScript引擎处理
  4. DOM树建立后根据CSS样式进行构建内部绘图模型,生成RenderObject树(渲染树)
  5. 根据网页层次结构构建RenderLayer树,同时构建虚拟绘图上下文(重排)
  6. 依赖2D和3D图形库渲染成图像结果呈现在浏览器中(重绘)

css的下载过程不会阻塞解析,但JS会等待其下载并执行完成后才会继续解析。JS下载时,会并行下载其他的资源。

webkit内核浏览器渲染过程为例:

image-20220124050518910

重绘与重排(回流)

一旦渲染树构建完成,就要开始绘制(paint)页面元素了。当DOM的变化包括

  • 添加或删除可见的DOM元素
  • 元素位置改变
  • 元素本身的尺寸发生改变
  • 内容改变
  • 页面渲染器初始化
  • 浏览器窗口大小发生改变

导致浏览器不得不重新计算元素的几何属性,并重新构建渲染树,这个过程称为“重排”。完成重排后,要将重新构建的渲染树渲染到屏幕上,这个过程就是“重绘”。

简单的说,重排负责元素的几何属性更新,重绘负责元素的样式更新。而且,重排必然带来重绘,但是重绘未必带来重排。比如,改变某个元素的背景,这个就不涉及元素的几何属性,所以只发生重绘。

减少重绘和重排的措施包括:

  • 一次性改变样式:
var el = document.querySelector('.el');
el.style.borderLeft = '1px';
el.style.borderRight = '2px';
el.style.padding = '5px';
var el = document.querySelector('.el');
el.style.cssText = 'border-left: 1px; border-right: 2px; padding: 5px';
//或者
// css 
.active {
    padding: 5px;
    border-left: 1px;
    border-right: 2px;
}
// javascript
var el = document.querySelector('.el');
el.className = 'active';
  • 隐藏元素,进行修改后,然后再显示该元素
let ul = document.querySelector('#mylist');
ul.style.display = 'none';
appendNode(ul, data);
ul.style.display = 'block';
  • 使用文档片段创建一个子树,然后再拷贝到文档中
let fragment = document.createDocumentFragment();
appendNode(fragment, data);
ul.appendChild(fragment);
  • 将原始元素拷贝到一个独立的节点中,操作这个节点,然后覆盖原始元素
let old = document.querySelector('#mylist');
let clone = old.cloneNode(true);
appendNode(clone, data);
old.parentNode.replaceChild(clone, old);
  • 缓存布局信息
current = div.offsetLeft;
div.style.left = 1 + ++current + 'px';
div.style.top = 1 + ++current + 'px';

总结

用户输入https://www.baidu.com/后,浏览器会判断缓存中是否由该域名的DNS信息,如果有则使用缓存中的DNS,否则查询用户本地Hosts文件,如果没有,则去ISP的DNS服务器查询IP,一般到这里就能查到了;

拿到IP后就会发起请求,此时会建立TCP连接,一个完整的TCP连接包含3次握手和4次挥手,然后发起HTTP网络请求,此时会判断是否由缓存,缓存分为强缓存和协商缓存,强缓存就是本地缓存,保存在内存或者用户磁盘中,强缓存是不会发送HTTP请求的,协商缓存则会发送HTTP请求来判断缓存是否过期,如果缓存可用,服务器会返回304状态码,接收到响应的数据后,浏览器会解析并渲染HTML,会将HTML中的DOM生成DOM树,将CSS生成CSS规则树,然后合并起来交给渲染引擎处理最后呈现到页面中,用户就可以看到界面了。

如果用户修改元素的几何属性,会触发浏览器的重排,如果用户修改元素的样式,例如颜色相关的属性则会造成重绘,重排一定会造成重绘,但重绘不一定造成重排,如果要考虑性能,那么写代码时就应该避免触发浏览器的频繁重排,例如:

改变样式的时候,最好一次性改变,可以把多个样式写成一个类名。

如果需要大量修改元素的几何位置,可以先将元素进行隐藏,修改完后显示元素,这个过程只会触发浏览器2次重排,远比一行行修改后触发几十上百次重排要划算。

其实都是为了应付面试,这种知识根本不可能深入理解的,除非你造一个mini版的浏览器,要自己实现一遍里面的各种parser和render,你不实践一遍,你怎么可能理解里面的细节呢?就像现在很多教Vue、React源码的,都开始手写一个mini版本了,就是因为你不写,光脑子里面背下那些概念,没有深入理解,屁用没有,顶多吹吹牛逼,显得很厉害,实际遇到棘手问题还是解决不了,况且,编程还是重在实践的学科。

记录一下,有时间研究怎么手写一个浏览器,哈哈,其实还是很有趣的。