Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

浏览器输入URL发生了什么 #3

Open
impeiran opened this issue Mar 27, 2020 · 0 comments
Open

浏览器输入URL发生了什么 #3

impeiran opened this issue Mar 27, 2020 · 0 comments

Comments

@impeiran
Copy link
Owner

比较考验理念知识积累,后续笔者会随着经验增加再依次补充。

分析协议

首先第一步,如果URL是File://协议开头,浏览器首先会查找机子上的本地文件。如果是FTP就会按照其对应规则建立连接。如果是http/https则会按照下面步骤进行。

域名查找解析

如果地址是直接使用ip时,则跳过域名解析阶段。

地址是使用域名型的,就会进行此步骤,涉及到的概念:

  • 根 DNS 服务器: 返回顶级域DNS服务器的IP地址
  • 顶级域DNS服务器:返回权威DNS服务器的IP地址
  • 权威DNS服务器: 返回相应主机的IP地址

整个查找解析是一个递归过程:首先从

1 客户端/浏览器缓存中查找 ->
2 本地的hosts文件查找 ->
3 本地DNS解析器(此时可能命中缓存) ->
4 本地DNS服务器 ->

当上述这个过程都未命中时,根据本地DNS服务器设置的转发器进行查询。若未用转发模式,则迭代查找过程如下:

5 根域名服务器 ->
6 顶级域名服务器 ->
7 权威域名服务器

缓存与优化

  • DNS存在着多级缓存,从离浏览器的距离排序的话,有以下几种: 浏览器缓存,系统缓存,路由器缓存,IPS服务器缓存,根域名服务器缓存,顶级域名服务器缓存,主域名服务器缓存。
  • 拓展httpDNS,服务商可自定义匹配IP的规则,适合做分布式缓存与负载均衡。
  • 在域名和 IP 的映射过程中,给了应用基于域名做负载均衡的机会,可以是简单的负载均衡,也可以根据地址和运营商做全局的负载均衡。

建立tcp链接

如果是http(s)的链接,还是基于tcp的,需要建立起链接。涉及到TCP的三次握手,主要作用是为了确认双方的接收能力和发送能力是否都正确,指定自己的初始化序列号为后面的可靠性传送做准备。

涉及概念:

  • SYN(SYNchronization):在连接建立时用来同步序号。当SYN=1而ACK=0时,表明这是一个连接请求报文。对方若同意建立连接,则应在响应报文中使SYN=1和ACK=1. 因此, SYN置1就表示这是一个连接请求或连接接受报文。
  • ACK:此标志表示应答域有效,就是说前面所说的TCP应答号将会包含在TCP数据包中;有两个取值:0和1,为1的时候表示应答域有效,反之为0。TCP协议规定,只有ACK=1时有效,也规定连接建立后所有发送的报文的ACK必须为1。
  • FIN:即完,终结的意思, 用来释放一个连接。当 FIN = 1 时,表明此报文段的发送方的数据已经发送完毕,并要求释放连接。
  • seq:序号,本报文段发送的数据的第一个字节的序号。
  • ack:期望收到对方下一个报文段的第一个数据字节的序号。

一开始:客户端处于Closed状态,服务端处于Listen状态。

第一次握手

发起方A向B发起连接请求报文段,tcp首部的控制位SYN设为1,序号seq=x(某个开始的传输字节数)。此时SYN=1时不携带数据,但要消耗一个序号位。此时发起方进入SYN-SENT(同步已发送)阶段。

第二次握手

接收方B收到请求连接报文,若同意建立连接,则发送报文段:设控制位ACK=1SYN继续为1,确认序号ack设为x+1,同时为自己的序号位seq=y。此时该报文依然不携带数据,TCP的服务器进程进入SYN-RCVN(同步收到状态)。

第三次握手

发送方A接收到确认报文后,还要再发送一次确认:控制位ACK=1,序号seq仍然为x+1,确认号ack为y+1。A发送后进入ESTABLISHED(已建立连接)状态,B收到确认后也进入ESTABLISH状态。

PS:如果不携带数据就不消耗序号位,即上次序号位为a且不带数据,下一次仍可继续发送序号位为a的报文。

延伸:为什么需要第三次握手,而不是两次握手?

因为是为了防止A第一次握手时发送的报文,被某些因素导致延误了,而此时A因为超时,重新建立了连接,当延误的报文到了之后,B继续确认进行第二次握手,重复建立链接。

有了第三次握手,则会确保第一次延误时不会进行多次重复的连接。

TSL/SSL握手过程(若有)

TLS以SSL3.0为基准,后又制定了TLS1.0、TLS1.1和TLS1.2。当前主流的版本是SSL3.0和TLS1.0。

整个握手过程采用非对称加密与对称加密混合的方式。因为全部使用非对称加密的方式,算法实现会很耗时间,整个传输过程就会变得效率低。全部采用对称加密的方式,一开始若密钥被监听到,则就不安全。

所以正常数据传输时采用对称加密,而对称加密的密钥,采用安全性更好的非对称加密进行传输。

第一次握手 - Client Hello

客户端首先要告诉服务端,自己可以支持哪些加密算法。于是客户端把本地支持的加密套件(cipher suites),以及产生一个随机数(Client Random),两者一并传输给服务端。而且,客户端也要保存这个随机数。

第二次握手 - Server Hello

服务端收到信息后,保存第一次的随机数。然后把证书(包含公钥、数字签名、过期信息、其余网站信息),以及生成并保存第二个随机数(Server Random),并确定使用的加密方法,三个信息一并传输回给客户端(server hello done)。

若此次发送的证书信息不齐全时,中间还会发送一个Server Key Exchange信息,补充回给客户端。

第三次 - Client Key Exchange

接着,客户端收到证书及信息后。首先验证证书的完整性、正确性、以及证书上的域名与服务端域名是否一致。

拓展数字签名:证书的数字签名由CA相关组织私钥加密而成,而浏览器一般内置了知名的机构的信息与公钥,使用机构提供的公钥解密签名:得到哈希摘要算法,以及一段摘要。接着再对证书上的服务端公钥进行哈希摘要并验证。确保中间过程的服务端公钥没被篡改。

验证完证书的正确性后,客户端会使用一些加密算法(例如:RSA, Diffie-Hellman)产生一个48个字节的Key,这个Key叫PreMaster Secret。该Key将会连同前两个随机数,使用共同协商的加密套件,加密生成“对话密钥(Master Key / Session Key)”。

但客户端只用接收到的公钥加密PreMaster Key,把该加密结果发送给服务端。

此处握手可拓展DH算法生成PreMaster Key,那么就不再需要传递Key,只传递需要的参数即可生成。

第四次 - Server Finish

服务端收到信息后,使用自己的私钥解密,获得PreMaster Key,同样的,因为服务端也有存之前的两个随机数,所以此处也可以根据约定的加密套件生成同样的Master Key

为了验证之前搭建的加密通道是否成功,服务端会用该Master Key加密一段Finish信息给客户端。如果客户端能对Finish信息进行正常加解密且消息正确的被验证,则说明加密通道已经建立成功,可以正常传输数据。

参考资料:
http://www.ruanyifeng.com/blog/2014/09/illustration-ssl.html
https://blog.csdn.net/u010285974/article/details/85320788

浏览器渲染

此处权作通过HTTP请求,浏览器已得到HTML数据:

  1. 对HTML文本词法分析和语法分析,自上而下加载,遇到<script src=""><style href="">此类标签,若资源指向外链,则会额外发起请求加载。

    script标签:若指明defer,则是并行加载,但会延迟到整个页面解析完再执行。async也是会并行加载,加载完后再执行脚本。

  2. 渲染进程将标签内容转换为DOM树

  3. 渲染引擎将CSS样式表转化为浏览器可以理解的styleSheets,计算出DOM节点的样式。

    • 首先进行属性值标准化,例如:将color: blue转化为color: rgb(0,0,255)
    • 其次处理样式的继承与层叠 => 从右往左开始匹配
  4. 创建renderTree布局树,计算元素的布局信息。

    结合dom tree和样式规则,生成一颗只包含可见元素的tree。即display: none的tree并不会出现在此模型上。

  5. 对布局树进行分层,并生成分层树。

    此处考虑到页面中有很多复杂的效果,如一些复杂的 3D 变换、页面滚动,或者使用 z-index 做 z 轴排序等,为了更加方便地实现这些效果,渲染引擎还需要为特定的节点生成专用的图层,并生成一棵对应的图层树(LayerTree)

  6. 为每个图层生成绘制列表,并将其提交到合成线程。合成线程将图层分图块,并栅格化将图块转换成位图。

    栅格化可以理解为按照图层整合出顶层视角上的显示图块。合成线程优先考虑合成视口及其附近的位图。

  7. 合成线程发送绘制图块命令给浏览器进程。浏览器进程根据指令生成页面,并显示到显示器上。

参考:https://mp.weixin.qq.com/s/nMlZWZO6foRUPFK34ouPhg

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant