「长文警告⚠️」从 url 输入页面到页面展示到底发生了什么?(多方参考篇)

·  阅读 1712

前言

最近掘金首页,有一篇文章阿里面试官的”说一下从url输入到返回请求的过程“问的难度就是不一样!刷屏了。

我内心 OS:现在面试都这么深了吗?非常恐慌,因为确实是我的短板。

但又能怎么办呢?知道不足就弥补呀。于是我马上开动,花了很长时间才写下这篇文章。

你可以看到,我在下方的参考连接上放了不少,实际上我写文章时参考了更多的内容,只是有些博客实在写的看不懂,或者没讲明白.

在反复斟酌之后,我采用专业术语+口语解释的形式来尽量描绘一个易懂的流程。

一方面是我不想多年后我返回来看我自己的文章,自己都整不明白,这是对自己的不负责也是对所有看到我这篇文章的人不负责,大家的时间都很宝贵,与其浪费时间看完全不懂的博客不如好好睡一觉。

另一方面如果不放专业术语又是对计算机行业规范的不尊重,所以我很为难。

如果你对我写的有异议,请在下方评论区写明你要表达的观点.

如果你觉得我写的还算用心,请顺手给我一个赞👍🏻。非常感谢!!

总体流程

我大概围绕以下总体流程来撰写这篇博客

  • DNS 解析
  • TCP 连接(三次握手)
  • HTTP 请求
  • 服务器处理请求并返回报文
  • 浏览器解析
  • 断开连接(四次挥手)

其中还会参杂一些可能会被问到的点,比如为什么需要三次握手,为什么需要四次挥手等常考问题。

下面详细讲讲每个阶段吧。

一.DNS 解析域名

由于计算机更擅长处理数字,但是让人去记忆ip地址会非常困难,为了解决这种问题,于是就设置了一套域名对应 ip 地址的机制.

那么DNS 解析就是通过你输入的域名找到对应的ip 地址,例如操作系统默认 localhost就会被转化成127.0.0.1被计算机读取.

url 编码规则

url是外国人发明的,在一开始设计的时候, url 就只被允许使用字母+数字+一些特殊符号(例如下划线和句号等等)并形成标准.但是随着日本人,中国人,泰国人开始上网,明显大家语言不通,假设一个中国人在输入 url 时需要中文字怎么办?这就需要用到编码规则.

下面是我根据阮一峰的博客总结的url 中包含中文编码规则:

1.网址路径的编码,用的是utf-8编码。

2.查询字符串是中文的情况下会采用GB2312发送.

3.GET和POST方法的编码,用的是网页的HTML源码编码

<meta http-equiv="Content-Type" content="text/html;charset=xxxx">
复制代码

如果上面这一行最后的charsetUTF-8,则URL就以UTF-8编码;如果是GB2312URL就以GB2312编码。

4.Ajax调用的URL包含汉字也就是说由Javascript生成HTTP请求各浏览器不同处理

浏览器如何通过域名去查询URL对应的IP呢

  • 首先会从浏览器缓存中查找DNS缓存记录
  • 如果找不到就会在本地操作系统中找hosts文件看看里面有没有
  • 网络通过路由器看看有没有DNS缓存
  • 都没有就发送请求给本地DNS服务器进行递归查询
  • 本地DNS 服务器没有就发送请求给根服务器(开始迭代查询)
  • 根服务器不会给 ip 地址而是给对应的域服务器的地址
  • 域服务器返回域名解析器的地址
  • DNS 服务器向解析器发送请求,并获得域名和 ip 地址的映射关系,并做缓存处理

递归查询?迭代查询?

下面这张图很好解释递归查询和迭代查询是什么 简单来说递归查询就是老板,只要吩咐下去(发送一次请求),然后就等着本地 DNS 域名服务器给结果.

迭代查询就是项目经理,问老板要资源(这里的老板是根DNS服务器),老板给一个联系方式,让你去找产品经理(顶级域名服务器),产品又给一个地址,我干不了,你去找开发吧(下面的各级域名服务器),反正就是给地址,需要你自己去问.

小结

浏览器通过向DNS 服务器发送域名,然后由 DNS 服务器解析域名(找到对应的服务器 IP 地址并返回),最后由浏览器跟对应的服务器建立 tcp 连接

二.TCP 连接

TCP 会进行三次握手以同步客户端和服务端的序列号和确认号.

TCP 三次握手的过程如下:

  • 客户端发送一个带 SYN=1,Seq=X 的数据包到服务器端口(第一次握手,由浏览器发起,告诉服务器我要发送请求了)

  • 服务器发回一个带 SYN=1, ACK=X+1, Seq=Y 的响应包以示传达确认信息(第二次握手,由服务器发起,告诉浏览器我准备接受了,你赶紧发送吧)

  • 客户端再回传一个带 ACK=Y+1, Seq=Z 的数据包,代表“握手结束”(第三次握手,由浏览器发送,告诉服务器,我马上就发了,准备接受吧)

为啥需要三次握手而不是两次

TCP 是可靠通信协议,必须保证接收方收到的数据是完整,有序,无差错的.

为了实现可靠传输,通信双方需要判断自己已经发送的数据包是否都被接收方收到, 如果没收到, 就需要重发。 为了实现这个需求, 很自然地就会引出序号(sequence number) 和 确认号(acknowledgement number) 的使用。发送方和接收方始终需要同步( SYNchronize )序号。

需要注意的是, 序号并不是从 0 开始的, 而是由发送方随机选择的初始序列号 ( Initial Sequence Number, ISN )开始。由于 TCP 是一个双向通信协议, 通信双方都有能力发送信息,并接收响应。因此,通信双方都需要随机产生一个初始的序列号,并且把这个起始值告诉对方。

于是整个过程就变成了这样:

结论:

TCP连接是两个端点之间的事,由于TCP连接是可靠连接,所以不管是建立连接还是关闭连接,需要两个端点都要发送请求和收到确认

序列号变了?

上面的图中,我们可以看到浏览器发出的 SYN 会变成 ACK(ack number 变成上一个seq number+1),这是因为 ACK的作用是向对方表示,我期待收到的下一个序号。如果你向对方回复了 ack = 31, 代表着你已经收到了序号截止到30的数据,期待的下一个数据起点是31。

三.发送 HTTP 请求

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

请求报文由请求行(request line)、请求头(header)、请求体三个部分组成,如下图所示:

get 请求没有请求体.它大概长这样

name=tom&password=1234&realName=tomson
复制代码

然后发送请求时会经历 HTTP缓存处理

四.HTTP 缓存(由浏览器执行)

什么是 HTTP 缓存

http缓存指的是: 当客户端向服务器请求资源时,会先抵达浏览器缓存,如果浏览器有“要请求资源”的副本,就可以直接从浏览器缓存中提取而不是从原始服务器中提取这个资源。

常见的http缓存只能缓存get请求响应的资源,http缓存都是从第二次请求开始的。第一次请求资源时,服务器返回资源,并在respone header头中回传资源的缓存参数;第二次请求时,浏览器判断这些请求参数,命中强缓存就直接200,否则就把请求参数加到request header头中传给服务器,看是否命中对比缓存,命中则返回304,否则服务器会返回新的资源。

HTTP缓存分类

根据是否需要重新向服务器发起请求来分类,可分为(强制缓存,对比缓存).

强缓存如果生效,那么不再和服务器发生交互.

对比缓存不管生不生效,都需要跟服务器发生交互.

也就是说强缓存可以不发请求,而对比缓存都得发请求.

跟强缓存相关的 API 有 Expires 和 Cache-Control等,跟对比缓存相关的有 Etag等,两种缓存可以同时存在,但是优先级是强缓存更高.

强缓存

强缓存跟缓存数据的时间有效性有关,当浏览器没有缓存数据的时候,会发送第一次请求给服务器,服务器会在响应头中附带相关的缓存规则,响应头中有关的字段就是Expires/Cache-Control.

Expires

Expires中会表明具体的到期时间,在时间内都可以进行缓存.但是这里会有一个 BUG,因为本地时间跟服务器时间不一定相同,而且本地时间可以修改,比如说我的本地时间往前调整了两年,那么浏览器的强缓存就会一直延续两年,所以这种方式已经过时不用了

Cache-Control

Cache-Control可以设置缓存的时间,比如它有个属性为 max-age,可以设置缓存在多少秒之后失效

image

上面的例子就是设置了多少秒的缓存时间,这种方式比Expires靠谱得多.

对比缓存

对比缓存的意思就是发给服务器做一下资源对比,看看是不是需要缓存数据.

这里的资源对比需要一个标识,这个标识也是第一次请求时服务器返回给浏览器保存在浏览器缓存库中的,那么对比缓存就会发送带这个标识的请求给服务器看看是不是一样的,如果一样的话那就用缓存数据好了.判断成功后会返回304状态码.

对比成功时,由于服务器只返回响应头,不需要返回响应主体,所以数据量大大降低.

这里就需要提到缓存标识,缓存标识一共分两种:修改时间和资源唯一标识Etag

修改时间

修改时间需要用到两个字段Last-Modified / If-Modified-Since

Last-Modified是第一次请求时服务器发送给浏览器的响应头字段,上面记录服务器资源的最后修改时间.

If-Modified-Since是浏览器第二次请求时发送的响应头字段,服务器通过这个响应头字段来对比一下自己的最后修改时间.

如果时间一样,说明资源没有修改过,则响应304状态码,告诉浏览器用缓存数据.

如果时间不一样,说明资源有动过,那么就响应200状态码,并把报文主体也发过去.

唯一标识Etag

这个是由服务器决定规则的标识,当第一次访问时,服务器就会在响应头中带着这个标识,这个标识长这样 image

当再次访问服务器时,浏览器会在请求头中发送If-None-Match这个字段并附上之前的 Etag 标识,如果服务器发现有这个标识就会跟自己的唯一标识做对比.

如果一样,说明资源没改过,响应304状态码,告诉浏览器用缓存数据

如果不一样,说明资源改动过,响应200并且发最新的资源主体过去.

小结

HTTP 缓存就类似于浏览器跟服务器中间的一个缓存数据库,至于用不用这个缓存库就需要服务器来指定.

服务器有两种方式:强制缓存和对比缓存.强制缓存比对比缓存优先级高

强制缓存不会发请求给服务器,它就是设定一个时间,这个时间可以分为 Expires 和 Cache-Control,Expires就是定一个具体的时间节点,但是有 bug,会因为服务器时间跟本地时间不同而有误差.而Cache-Control就是指定多少秒内用强制缓存.当强制缓存成功后返回200.

对比缓存就是发送一段请求给服务器,请求信息可以有两种:修改时间和唯一标识.

两种都是需要跟服务器的信息进行对比,对比成功就返回304,告诉浏览器用缓存数据,对比失败就返回200,然后把用户需要请求的数据作为资源主体发回去.

五.服务器返回报文

不管怎么用,服务器总算返回数据了,这里主要讲一下响应报文

http 响应报文

响应报文由响应行(request line)、响应头部(header)、响应主体三个部分组成

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

状态码规则如下:

  • 1xx:指示信息--表示请求已接收,继续处理。
  • 2xx:成功--表示请求已被成功接收、理解、接受。
  • 3xx:重定向--要完成请求必须进行更进一步的操作。
  • 4xx:客户端错误--请求有语法错误或请求无法实现。
  • 5xx:服务器端错误--服务器未能实现合法的请求。

(2) 响应头部包含响应报文的附加信息,由 名/值 对组成

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

六.浏览器解析渲染页面

浏览器解析渲染页面分为一下六个步骤:

1、构建html树 (dom)

2、构建css树(cssom)

3、将两棵树合并成一棵树 (render tree)

4、Layout布局(文档流、盒模型、计算大小、位置)【主要动html或有关的css】

5、paint绘制(绘制颜色、阴影等)【主要动css】

6、compsite合并 根据层叠关系展示画面

另一个面试题

这又引出另外一个回流跟重绘的问题

回流就是触发布局阶段,重绘就是触发绘制阶段.

通过上面的阶段,我们可以得知layout阶段在 paint 阶段之前, 所以得出一句话结论:

回流必将引起重绘,重绘不一定会引起回流。

回流 (Reflow)

当Render Tree中部分或全部元素的尺寸、结构、或某些属性发生改变时,浏览器重新渲染部分或全部文档的过程称为回流。 会导致回流的操作:

  • 页面首次渲染
  • 浏览器窗口大小发生改变
  • 元素尺寸或位置发生改变
  • 元素内容变化(文字数量或图片大小等等)
  • 元素字体大小变化
  • 添加或者删除可见的DOM元素
  • 激活CSS伪类(例如::hover)
  • 查询某些属性或调用某些方法

重绘 (Repaint)

当页面中元素样式的改变并不影响它在文档流中的位置时(例如:color、background-color、visibility等),浏览器会将新样式赋予给元素并重新绘制它,这个过程称为重绘。

七.TCP 连接:四次挥手

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

TCP连接是双向传输的对等的模式,就是说双方都可以同时向对方发送或接收数据。当有一方要关闭连接时,会发送指令告知对方,我要关闭连接了。这时对方会回一个ACK,此时一个方向的连接关闭。

但是另一个方向仍然可以继续传输数据,等到发送完了所有的数据后,会发送一个FIN段来关闭此方向上的连接。接收方发送ACK确认关闭连接。注意,接收到FIN报文的一方只能回复一个ACK, 它是无法马上返回对方一个FIN报文段的。

这就是需要四次挥手的原因。

扩展:Javascript函数:encodeURIComponent()和encodeURI()

URL 的编码方式被浏览器搞出了很多种,为了保证客户端只用一种编码方法向服务器发出请求,这里就需要用到这两个函数来进行 JavaScript手动编码,

两者的不同之处在于encodeURI()更着眼于整个URL 的编码,而encodeURIComponent()适合给参数编码,就范围而言encodeURIComponent更宽广.

最后

文章写到这里就算讲完了,上面的答案可能不全,但我尽量把我所学到的都归纳上来了。

如果还需要补充,后续我会再来更新这篇博客。

最后的最后

非常感谢你花费时间来阅读我的博客,下期再见!

参考文章

google-Render-tree Construction, Layout, and Paint

google-Rendering Performance

阿里面试官的”说一下从url输入到返回请求的过程“问的难度就是不一样!

从URL输入到页面展现到底发生什么?

关于URL编码-阮一峰

TCP 为什么三次握手而不是两次握手(正解版)

TCP为什么是三次握手,而不是两次或四次?

HTTP请求和响应(二)

CSS 渲染性能

浏览器的回流与重绘 (Reflow & Repaint)

两张动图-彻底明白TCP的三次握手与四次挥手

escape,encodeURI,encodeURIComponent有什么区别?

github

最后推广一下本人长期维护的 github 博客

1.从学习到总结,记录前端重要知识点,涉及 Javascript 深入、HTTP 协议、数据结构和算法、浏览器原理、ES6、前端技巧等内容。

2.在 README 中有目录可对应查找相关知识点。

如果对您有帮助,欢迎star、follow。

地址在这里

分类:
前端
标签:
收藏成功!
已添加到「」, 点击更改