转自 https://github.com/sunyongjian/blog/issues/34
开始
从浏览器输入一个 url 开始,到页面完成渲染,整个过程的分析,重要知识点的介绍,以及哪些地方前端需要着重注意可以做优化。
以打开 google.com 为例。
第一部分:过程
也就是链路,url 输入开始到页面渲染,发生了什么,以及重要知识点。
浏览器
过程分为几个大的模块,首先是开始在浏览器端。
输入提示
从浏览器地址框输入一个 g ,浏览器会根据你的历史访问,书签等,给出输入建议,假如说我以前打开过 google.com,浏览器就会根据它的算法匹配并给出 4,5 条建议地址。
有时候提示还会根据浏览器默认搜索引擎的搜索记录,去匹配最近的搜索记录。
url 解析
也就是输入完地址,敲回车之后。这里省去键盘电流脉冲对 CPU 等等的流程,不研究硬件。
对关键字的解析。
当协议或主机名不合法时,也就是不符合 URL 格式,比如输入几个单词,中文等。浏览器会将地址栏中输入的文字传给默认的搜索引擎。我就经常用 Chrome 的此特性快速 google 搜索。
什么是合法的 URL 格式?也就是 URL 遵循的语法。大多数 url 都建立在以下 9 部分组成的结构上:
构成的每部分应该都了解,可查阅。另外,这只是格式上的合法,还有字符的问题。
字符
URL 为了在不同协议不同传输机制都可以安全的运送信息,采用的字符都是符合 ASCII 集的。这其中还包含一些保留字符,比如上面的 URL 协议中的分割字符,等特殊含义的字符。
而不安全的字符,非 ASCII 的 Unicode(中文等) 字符,就会通过转义去处理,使用 % 。先不展开,感觉字符、编码的地方都够写一遍文章了。
此部分就是检查 url 的合法性,并对不合法的进行转义。提取出 domain name 之后,就该是 DNS 解析相关的了,不过还要提一下,浏览器还会检查 HSTS 列表。
HSTS 列表
HSTS 就是一种安全策略的机制,是为了让浏览器强制使用 HTTPS 访问的,详细可以去这篇文章看,介绍的比较清楚。当你的网站均采用 HTTPS,并符合它的安全规范,就可以申请加入 HSTS 列表,之后用户不加 HTTPS 协议再去访问你的网站,浏览器都会定向到 HTTPS。无论匹配到没有,都要开始 DNS 查询工作了,提取出的主机名类似于 https://www.google.com/
。
DNS 解析
为什么要 DNS 解析
因为 http 是基于 tcp 连接的,而 tcp 则是通过 ip 地址去识别访问的。DNS 解析就是域名转化成 ip 地址的过程。
DNS 查询过程
域名通过 DNS 转化成 ip 地址的过程。
1. 查看浏览器内部缓存
检测域名是否存在于浏览器缓存中,如果有缓存直接使用,没有则下一步。打开 chrome://net-internals/#dns
即可查看本机浏览器的 dns 缓存。
2. 系统缓存
浏览器会调用一个类似 gethostbyname
的库函数,此函数会先去检测本地 hosts 文件,查看是否有对应 ip。
PS: 这里有一个点,localhost 默认 ip 是 172.0.0.1,这是一个回路段,也叫换回接口。也就是不会发往服务器,是直接在本地打开的。
3. 路由器缓存、ISP 缓存
如果浏览器和系统缓存都没有,系统的 gethostname 函数就会像 DNS 服务器发送请求。而网络服务一般都会先经过路由器以及网络服务商(电信),所以会先查询路由器缓存,然后再查询 ISP 的 DNS 缓存。
4. 本地 DNS 服务器
通常为自己计算机搭建的小型 DNS 服务器,自我使用,属于 DNS 优化的一部分。
5. 域名服务器
到此处的过程为:根域服务器(.) -> 顶级域名服务器(eg: .com,.org)->
主域名服务器(eg: google.com)
如果域名正常,应该就会返回 IP 地址,如果没有浏览器就会提示找不到服务器地址。
DNS 优化
DNS 查询的过程经历了很多的步骤,如果每次都如此,是不是会耗费太多的时间,资源。所以我们应该尽早的返回真实的 IP 地址,减少查询过程,也就是 DNS 缓存。浏览器获取到 IP 地址后,一般都会加到浏览器的缓存中,本地的 DNS 缓存服务器,也可以去记录。另外,每天几亿网名的访问需求,一秒钟几千万的请求域名服务器如何满足?就是 DNS 负载均衡。
通常我们的网站应用各种云服务,或者各种服务商提供类似的服务,由他们去帮我们处理这些问题。DNS 系统根据每台机器的负载量,地理位置的限制
(长距离的传输效率)等等,去提供高效快速的 DNS 解析服务。
TCP 连接
目前大部分的应用层连接都是 HTTP 协议,而 HTTP 是 TCP/IP 承载的,所以这一步先假定是建立的 TCP 连接。
IP 发出
通过上面一系列过程,得到了真实的 IP,再加上端口号,浏览器会调用系统库函数 socket
,请求一个 TCP流套接字,再经过一系列封装成 TCP 封包。然后将封包由计算机发出,通过本地网络,网卡,在经过路由器等调节器把数字信号转化成模拟信号,经网线(电话线、光纤等)传输。这里的分层是这样的:
TCP 三次握手
IP 和端口号可以使 TCP 连接对应的服务器,网络信号经由网线传输到对应服务器的网络调节器后,再转化成数字信号,使计算机可以处理(发出和接收的过程是一样的)。那是不是这样就可以建立 TCP 连接了呢?还不行。TCP 为了建立可靠稳定的比特传输管道,会执行多次。
建立连接是有名的 TCP 协议三次握手,通俗理解就是:1.客户端(我们的浏览器等)向服务器询问是否可连接 2.如果可以,服务器回复我们收到了,可连接我这没问题 3. 客户端收到再确认,OK,可以传输了。但为什么是“三次握手”?不是一次两次四次?
为什么是三次握手
资料里说的是为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误。失效的连接是怎样一种情况呢?我们都经历过网络不好的时候,网络中的延迟现象也是时有发生的。当客户端发送的一个请求在网络的某个地方停滞的时候,服务器端并不会感知到,延迟到一定时间就会发生超时现象,客户端通常会断开连接。而这时候停滞在途中的某个请求,又发送服务器了,假设不是约定“三次握手”,服务器认定客户端此时要建立连接,便会占用程序去监听处理此连接,但是客户端是完全“无感”的,并无连接,就会导致连接“错开”的现象。同样的,服务器像客户端发请求也是一样的,请求滞后也会导致这种现象。所以要彼此确认,再建立连接。
实际上我们生活中,“三次握手”的约定也是有道理的,通常去处理一些存在不确定因素的“约定”。比如之前公司组织跑步的活动,活动前两周就开始报名,每个人就开始像行政小姐姐踊跃报名了,但是由于报名是没有成本的,但是行政小姐姐们按报名人数准备 T恤,补给等。所以活动前一天,行政姐姐再向每个人确认明天是否参加,做最后准备。到活动那天,参与者到行政姐姐那报道登记,才算是真正可以开始比赛。
看了一下知乎上的回答,真的搞笑,但是能说明问题。have fun...
三次握手:“喂,你听得到吗?”“我听得到呀,你听得到我吗?”“我能听到你,今天balabala……”
两次握手:“喂,你听得到吗?”“我听得到呀”“喂喂,你听得到吗?”“草,我听得到呀!!!!”“你TM能不能听到我讲话啊!!喂!”“……”
两次握手:
“喂,你听得到吗?”
“我听得到,你听得到吗?”
‘’今天…………‘’
“……谁在说话?”
四次握手:“喂,你听得到吗?”“我听得到呀,你听得到我吗?”“我能听到你,你能听到我吗?”“……不想跟傻逼说话”
链接:https://www.zhihu.com/question/24853633/answer/114872771
所以说,“三次握手”是很必要的。第一次 client 端发送给 server 端,server 确认了 client 的请求功能正常以及自己的接收监听服务正常。第二次 server 再发送给 client,告诉 client 我已经收到了,client 端就可以确认,我的请求是没问题的,并且确认了 server 的请求功能正常。但是 server 并不知道自己请求是否成功了啊,所以 client 再发送一次请求,如果 server 同样接收到,server 就可以确认自己的请求是没问题的。这样,client,server 的收发才真正确认成功,通信正常,连接可以建立。但是这只是确认了双方的收发通信功能正常,如何确保数据的正确稳定传输呢,“三次握手”中还有别的信息。
TCP 报文段
先查看一下 TCP 头部的信息,如下图:
只介绍几个这里要用到的。序列号、确认号,标识:SYN,ACK,FIN。
-
序列号(sequence number)
TCP 是面向字节流的,是 TCP 收发两端发送数据时所需,表示当然 TCP 段的第一个数据字节在整个数据流中的位置。也可以表示当前发送了多少数据。当前 seq - INS(初始 seq)
-
确认号(acknowledgement number)
发送端发送 TCP 数据包到另一端后,接收端通过确认号来通知发送端接收成功了多少数据。即表示发送端成功发送了多少数据。后面简称为 ackn。
-
SYN
同步标识,通常用来建立连接。在“三次握手”的前两次出现。TCP 协议规定,SYN = 1 时,当前 TCP 段不能携带其他有效数据。
-
ACK
确认标识,接收端确认接收到数据。TCP 协议规定,只有 ACK = 1 时有效,即确认号信息才会包含在 TCP 段内,反正为 0。
-
FIN
结束标识。表示双方数据发送完成,跟 SYN 类似,属于行为标识。也是 FIN = 1 时,TCP 段内不能包含其他主体数据。
通过标识符就可以推断出处于 TCP 连接的所处位置。比如 SYN = 1,ACK = 0,一定就是握手第一阶段,SYN = 1,ACK = 1,就是握手第二阶段,只剩 ACK,就是第三阶段。同样 FIN 就是断开阶段。
以上序号、标识在“三次握手”中的应用
首先是客户端(浏览器)初始一个序列号 seq = x (随机生成),设 SYN = 1, 发送报文段给服务器端,表明自己想要建立连接。
服务器接收到之后,如果可以连接,服务器端同样会初始自己的序列号 seq = y,设值 SYN = 1,ACK = 1,并根据客户端的序列号,设置确认号为 ackn = x + 1,为什么要加 1,我们知道确认号表示发送了多少数据,它是从客户端的初始序列号加上发送数据的结果,而 SYN 恰好是第一次携带的数据(跟主体数据等价),所以就是 x + 1。最后像客户端发送报文段来作为应答。
客户端收到应答后,还会再确认一次,设置序列号为 seq = x + 1,表示发送了 1 bite 数据(SYN),SYN 不需要设置,因为第一次已经标识了要建立连接了。这次最主要的是设置 ACK = 1(需要确认并告诉服务端已接收到数据),ackn = y + 1(服务器携带 SYN = 1),最后发送报文段给服务器端。第三次的包比较特殊,只带seq和确认号,不带数据。
用图的话就是这样:
数据传输中
TCP 数据传输的可靠性是有很多算法去保障的,不过最基本主要的是序列号和确认号。接着上面的继续,“三次握手”建立连接,就是数据传输了。
承接上个图,传输规则如下:
[图片上传失败...(image-5c0e7a-1585301748578)]
假如说,我们客户端的初始序列号为 1000 b,建立连接后,现在要发送 5000b 的数据到服务器端,序号是 1001 - 6000。虽然 5000 b 数据量很小,但是我们还是分五次传输,也就是五个报文段,一次传 1000 b。
第一次序号就是 1001,这个序号表示了当前传输数据的第一个字节的编号。由于握手时期 SYN 不是真实数据,seq + 1 算是一种约定,不过恰好到发送数据的时候,我们计算只需要加当前数据量,就是当前报文段的序列号。
服务器接收到第一次 seq = 1001,并收到数据 1000,所以 ackn = 2001。ackn 返给客户端,确认收到数据,并期待下次报文段序列号是 2001。
2~5 次 seq 为 2001,3001,4001,5001...
最后一次返回 akcn = 6001,也就是之前的 6001 - 1001,5000b 数据都传送完啦。下次传输继续从 6001 开始。
四次挥手
TCP 数据传输完,是要断开连接的。我说的数据传输完,是某一端发送完数据,要断开连接,去发送一个断开的请求,客户端和服务端都可以主动断开连接。为什么断开要四次呢。
- 第一次是连接的某一端 A 请求断开连接,发送报文段给 B,设置 seq = x 和 ackn = y,另外加一个标识位 FIN,表示已经没有数据发送了,请求断开。
- B 收到请求,需要进行确认,即设置 ACK = 1,然后是 ackn = x(A 的 seq) + 1,B 的 seq 仍然是 y,只是确认收到 A 的关闭请求。
- 隔一段时间,B 再向 A 发送 FIN 报文段,请求关闭,FIN = 1,seq = y,ackn = x + 1。此时是最后确认阶段。
- A 收到 B 的 FIN,向 B 发送 ACK,确认关闭,seq = x + 1,ACK = 1,ackn = y + 1。发送完之后,A 会进入 TIME_WAIT 的阶段,如果 B 收到 ACK 关闭连接,A 在 2MSL (报文最大生存时间)收不到 B 的响应就自己默认关闭了。
承接上面,来个图:
[图片上传失败...(image-d9a439-1585301748578)]
为什么断开要四次
对比 TCP 建立连接的时候,区别大概就是第二步拆成了两步。“三次握手”的时候确认 ACK 和同步 SYN 是一块返回的,断开连接则是分开发送,先发送 ACK 确认,再发送 FIN。这里主要是因为 B 端是被动断开的一方,A 发送完数据了,发送 FIN 表示我已经完事了,但是 B 不一定,也能还有数据会发送给 A。所以 B 会先 ACK 确认,然后当它真的没有数据要发送了,才会执行 FIN。
这种情况主要是由于 TCP 全双工传输的特性决定的。什么是全双工?先说一下半双工吧,举个栗子,有一条很窄的道路,只有单通道,但是却两个方向的车都可以走。当有一个方向的车进入,另一个方向的车就只能等待它通过才能进入。而全双工就是互不影响,你走你的,我走我的。所以 TCP 的数据传输也是这样,两端同时可以向对方发送数据,所以当 A 要断开连接的时候,B 接收到 FIN 表示没有数据会发来了,但是我还可以继续发送数据,可能还有数据要发,为了数据不丢失,即采用先确定后断开的方式。
tcp 优化
只了解三次握手就谈优化显然是不可能的,必然还需要了解 TCP 的其他特点,TCP 作为传输层的协议,在网络通信中也是重要的一环,所以如果 TCP 事务发生了延时,网络通信也会延时。了解几个可能造成延时的地方:
延迟确认
nagle算法延时,缺陷
slow start
端口耗尽
其他底层协议
udp
HTTP请求
- http 协议
- 通过报文传输,包括起始行,各种 method,信息,重要的首部,实体
- http 缓存
服务器
- server 处理
资源
- html
页面渲染
- html, dom
- css
- js
第二部分:优化
对于前端来说,页面访问优化大概就是基于页面的打开并展示速度去做的。但是其实会有很多维度,首屏打开速度,请求资源速度,交互操作,页面渲染速度,等等
资源加载
普通静态资源
- 网络要好
- tcp
- http 优化
- 资源请求合并
- 缓存
- 压缩文件
图片
- 图片合并,文件压缩,base 64,icon
页面展示
- 首屏加载
- 交互
渲染
- 页面渲染
- 重绘重流
- 框架使用优化