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开始,定位世界|从我们输入 URL 并按下回车键到看到网页结果之间发生了什么? #12

Closed
geekyouth opened this issue Sep 13, 2018 · 1 comment

Comments

@geekyouth
Copy link
Owner

原文地址 http://www.wangtianyi.top/blog/2017/10/22/cong-urlkai-shi-,ding-wei-shi-jie/

从我们输入 URL 并按下回车键到看到网页结果之间发生了什么?换句话说,一张网页,要经历怎样的过程,才能抵达用户面前?下面来从一些细节上面尝试一下探寻里面的秘密。

前言:键盘与硬件中断

说到输入 URL,当然是从手敲键盘开始。对于键盘,生活中用到的最常见的键盘有两种:薄膜键盘、机械键盘。

  • 薄膜键盘:由面板、上电路、隔离层、下电路构成。有外观优美、寿命较长、成本低廉的特点,是最为流行的键盘种类。键盘中有一整张双层胶膜,通过胶膜提供按键的回弹力,利用薄膜被按下时按键处碳心于线路的接触来控制按键触发。
  • 机械键盘:由键帽、机械轴组成。键盘打击感较强,常见于游戏发烧友与打字爱好者。每一个按键都有一个独立的机械触点开关,利用柱型弹簧提供按键的回弹力,用金属接触触点来控制按键的触发。

键盘传输信号到操作系统后便会触发硬件中断处理程序硬件中断是操作系统中提高系统效率、满足实时需求的非常重要的信号处理机制,它是一个异步信号,并提供相关中断的注册表(IDT)与请求线(IRQ)。键盘被按压时,将通过请求线将信号输入给操作系统,CPU 在当前指令结束之后,根据注册表与信号响应该中断并利用段寄存器装入中断程序入口地址。具体可参看操作系统与汇编相关书籍。

当然本文主要不是介绍硬件与操作系统中的细节,前言只是想说明,从输入 URL 到浏览器展现结果页面之间有太多底层相关的知识,怀着一颗敬畏的心并且在有限的篇幅中是无法详细阐述的,所以本文会将关注点放在在一个稍高的角度上来看,从浏览器替我们发送请求之后到看到页面显示完成这中间中发生了什么事情,比如 DNS 解析、浏览器渲染。

浏览器解析 URL

按下回车键之前

比如我按下一个‘b’键,会出现很多待选 URL 给我,第一个便是百度。那么其实是在浏览器接收到这个消息之后,会触发浏览器的自动完成机制,会在你之前访问过的搜索最匹配的相关 URL,会根据特定的算法显示出来供用户选择。

按下回车键之后

依据上述键盘触发原理,一个专用于回车键的电流回路通过不同的方式闭合了。然后触发硬件中断,随之操作系统内核去处理对应中断。省略其中的过程,最后交给了浏览器这样一个 “回车” 信号。那么浏览器(本文涉及到的浏览器版本都为 Chrome 61)会进行以下但不仅限于以下炫酷(乱七八糟)的步骤:

  1. 解析 URL:您输入的是 http 还是 https 开头的网络资源 / file 开头的文件资源 / 待搜索的关键字?然后浏览器进行对应的资源加载进程
  2. URL 转码:RFC 标准中规定部分字符可以不经过转码直接用于 URL,但是汉字不在范围内。所以如果在网址路径中包含汉字将会被转码,比如 https://zh.wikipedia.org/wiki/HTTP%E4%B8%A5%E6%A0%BC%E4%BC%A0%E8%BE%93%E5%AE%89%E5%85%A8 转换成 https://zh.wikipedia.org/wiki/HTTP%E4%B8%A5%E6%A0%BC%E4%BC%A0%E8%BE%93%E5%AE%89%E5%85%A8
  3. HSTS:鉴于 HTTPS 遗留的安全隐患,大部分现代浏览器已经支持 HSTS。对于浏览器来说,浏览器会检测是否该网络资源存在于预设定的只使用 HTTPS 的网站列表,或者是否保存过以前访问过的只能使用 HTTPS 的网站记录,如果是,浏览器将强行使用 HTTPS 方式访问该网站。

DNS 解析

不查 DNS,读取缓存

  • 浏览器中的缓存:对于 Chrome,缓存查看地址为:chrome://net-internals/#dns
  • 本地 hosts 文件:以 Mac 与 Linux 为例,hosts 文件所在路径为:/etc/hosts。所以有一种翻墙的方式就是修改 hosts 文件避免 GFW 对 DNS 解析的干扰,直接访问真正 IP 地址,但已经不能完全生效因为 GFW 还有根据 IP 过滤的机制。

发送 DNS 查找请求

DNS 的查询方式是:按根域名 -> 顶级域名 -> 次级域名 -> 主机名这样的方式来查找的,对于某个 URL,如下所示

主机名.次级域名.顶级域名.根域名
--------------------------
host.sld.tld.root

查询步骤为:

  1. 查询本地 DNS 服务器。本地 DNS 服务器地址为连接网络时路由器指定的 DNS 地址,一般为 DHCP 自动分配的路由器地址,保存在 / etc/resolv.conf,而路由器的 DNS 转发器将请求转发到上层 ISP 的 DNS,所以此处本地 DNS 服务器是局域网或者运营商的。
  2. 从 "根域名服务器" 查到 "顶级域名服务器" 的 NS 记录和 A 记录(IP 地址)。世界上一共有十三组根域名服务器,从 A.ROOT-SERVERS.NET 一直到 M.ROOT-SERVERS.NET,由于已经将这些根域名服务器的 IP 地址存放在本地 DNS 服务器中。
  3. 从 "顶级域名服务器" 查到 "次级域名服务器" 的 NS 记录和 A 记录(IP 地址)
  4. 从 "次级域名服务器" 查出 "主机名" 的 IP 地址

www.google.com 为例,下面是一整个 DNS 查询过程:

  1. 由于本次测试是在阿里云上的实例进行测试,所以首先从 100.100.2.138 这个阿里内网 DNS 服务器查找到所有根域名服务器的映射关系。
  2. 访问根域名服务器(f.root-servers.net),拿到 com 顶级域名服务器的 NS 记录与 IP 地址。
  3. 访问顶级域名服务器(e.gtld-servers.net),拿到 google.com 次级域名服务器的 NS 记录与 IP 地址。
  4. 访问次级域名服务器(ns2.google.com),拿到 www.google.com 的 IP 地址
.         388687  IN  NS  m.root-servers.net.
.         388687  IN  NS  d.root-servers.net.
.         388687  IN  NS  h.root-servers.net.
.         388687  IN  NS  l.root-servers.net.
.         388687  IN  NS  c.root-servers.net.
.         388687  IN  NS  g.root-servers.net.
.         388687  IN  NS  a.root-servers.net.
.         388687  IN  NS  k.root-servers.net.
.         388687  IN  NS  i.root-servers.net.
.         388687  IN  NS  e.root-servers.net.
.         388687  IN  NS  f.root-servers.net.
.         388687  IN  NS  j.root-servers.net.
.         388687  IN  NS  b.root-servers.net.
;; Received 1097 bytes from 100.100.2.138#53(100.100.2.138) in 1072 ms

com.          172800  IN  NS  l.gtld-servers.net.
com.          172800  IN  NS  a.gtld-servers.net.
com.          172800  IN  NS  i.gtld-servers.net.
com.          172800  IN  NS  j.gtld-servers.net.
com.          172800  IN  NS  b.gtld-servers.net.
com.          172800  IN  NS  f.gtld-servers.net.
com.          172800  IN  NS  m.gtld-servers.net.
com.          172800  IN  NS  h.gtld-servers.net.
com.          172800  IN  NS  e.gtld-servers.net.
com.          172800  IN  NS  g.gtld-servers.net.
com.          172800  IN  NS  c.gtld-servers.net.
com.          172800  IN  NS  k.gtld-servers.net.
com.          172800  IN  NS  d.gtld-servers.net.
;; Received 1174 bytes from 192.5.5.241#53(f.root-servers.net) in 1841 ms

google.com.       172800  IN  NS  ns2.google.com.
google.com.       172800  IN  NS  ns1.google.com.
google.com.       172800  IN  NS  ns3.google.com.
google.com.       172800  IN  NS  ns4.google.com.
;; Received 664 bytes from 192.12.94.30#53(e.gtld-servers.net) in 185 ms

www.google.com.       300 IN  A   216.58.200.228
;; Received 48 bytes from 216.239.34.10#53(ns2.google.com) in 2 ms

所以总的来说,DNS 的解析是一个逐步缩小范围的查找过程。

建立 HTTPS、TCP 连接

确定发送目标

拿到 IP 之后,还需要拿到那台服务器的 MAC 地址才行,在以太网协议中规定,同一局域网中的一台主机要和另一台主机进行直接通信,必须要知道目标主机的 MAC 地址。所以根据ARP(根据 IP 地址获取物理地址的一个 TCP/IP 协议)获取到 MAC 地址之后保存到本地 ARP 缓存之后与目标主机准备开始通信。具体细节参见维基百科 DHCH/ARP。

建立 TCP 连接

为什么握手一定要是三次?

  • 第一次与第二次握手完成意味着:A 能发送请求到 B,并且 B 能解析 A 的请求
  • 第二次与第三次握手完成意味着:A 能解析 B 的请求,并且 B 能发送请求到 A

这样就保证了A 与 B 之间既能相互发送请求也能相互接收解析请求。同时避免了因为网络延迟产生的重复连接问题,比如 A 发送一次连接请求但网络延迟导致这次请求是在 A 重发连接请求并完成与 B 通信之后的,有三次握手的话,B 返回的建立请求 A 就不会理睬了。

短连接与长连接?

上图是一个短连接的过程演示,对于长连接,A 与 B 完成一次读写之后,它们之间的连接并不会主动关闭,后续的读写操作会继续使用这个连接。另外,由于长连接的实现比较困难,需要要求长连接在没有数据通信时,定时发送数据包 (心跳),以维持连接状态,并且长连接对于服务器的压力也会很大,所以推送服务对于一般的开发者是非常难以实现的,这样的话就出现了很多不同的大型厂商提供的消息推送服务。

进行 TLS 加密过程

  • Hello - 握手开始于客户端发送 Hello 消息。包含服务端为了通过 SSL 连接到客户端的所有信息,包括客户端支持的各种密码套件和最大 SSL 版本。服务器也返回一个 Hello 消息,包含客户端需要的类似信息,包括到底使用哪一个加密算法和 SSL 版本。
  • 证书交换 - 现在连接建立起来了,服务器必须证明他的身份。这个由 SSL 证书实现,像护照一样。SSL 证书包含各种数据,包含所有者名称,相关属性(域名),证书上的公钥,数字签名和关于证书有效期的信息。客户端检查它是不是被 CA 验证过的。注意服务器被允许需求一个证书去证明客户端的身份,但是这个只发生在敏感应用。
  • 密钥交换 - 先使用 RSA 非对称公钥加密算法(客户端生成一个对称密钥,然后用 SSL 证书里带的服务器公钥将改对称密钥加密。随后发送到服务端,服务端用服务器私钥解密,到此,握手阶段完成。)或者 DH 交换算法在客户端与服务端双方确定一将要使用的密钥,这个密钥是双方都同意的一个简单,对称的密钥,这个过程是基于非对称加密方式和服务器的公钥 / 私钥的。
  • 加密通信 - 在服务器和客户端加密实际信息是用到对称加密算法,用哪个算法在 Hello 阶段已经确定。对称加密算法用对于加密和解密都很简单的密钥,这个密钥是基于第三步在客户端与服务端已经商议好的。与需要公钥 / 私钥的非对称加密算法相反。

服务端的处理

静态缓存、CDN

为了优化网站访问速度并减少服务器压力,通常将 html、js、css、文件这样的静态文件放在独立的缓存服务器或者部署在类似 Amazon CloudFront 的 CDN 云服务上,然后根据缓存过期配置确定本次访问是否会请求源服务器来更新缓存。

负载均衡

负载均衡具体实现有多种,有直接基于硬件的 F5,有操作系统传输层 (TCP) 上的 LVS,也有在应用层 (HTTP) 实现的反向代理(也叫七层代理),下面简单介绍一下最后者。

在请求发送到真正处理请求的服务器之前,还需要将请求路由到适合的服务器上,一个请求被负载均衡器拿到之后,需要做一些处理,比如压缩请求(在 nginx 中 gzip 压缩格式是默认配置在 nginx.conf 内的,所以默认开启,如果不对数据量要求特别精细的话,默认配置完全可以满足基本需求)、接收请求(接收完毕后才发给 Server,提高 Server 处理效率),然后根据预定的路由算法,将此次请求发送到某个后台服务器上。

其中需要提到的一点是反向代理,先回顾一下反向代理的原理: 正向代理是将自己要访问的资源告诉 Proxy,让 Proxy 帮你拿到数据返回给你,Proxy 服务于 Client,常用于翻墙和跨权限操作; 反向代理也是将自己要访问的资源告诉 Proxy,让 Proxy 帮你拿到数据返回给你,但是 Proxy 服务于 Server,它会将请求接受完毕之后发送给某一合适的 Server,这个时候 Client 是不知道是根据什么规则并且也不知道最后是哪一个 Server 服务于它的,所以叫反向代理,常用于负载均衡、安全控制.

服务器的处理

对于 HTTPD(HTTP Daemon) 在服务器上部署,最常见的 HTTPD 有 Linux 上常用的 Apache 和 Nginx。对于 Java 平台来说,Tomcat 是 Spring Boot 也会默认选用的 Servlet 容器实现,Tomcat 对于请求的处理如下:

  1. 请求到达 Tomcat 启动时监听的 TCP 端口。
  2. 解析请求中的各种信息之后创建一个 Request 对象并填充那些有可能被所引用的 Servlet 使用的信息,如参数,头部、cookies、查询字符串等。
  3. 创建一个 Response 对象,所引用的 Servlet 使用它来给客户端发送响应。
  4. 调用 Servlet 的 service 方法,并传入 Request 和 Response 对象。这里 Servlet 会从 Request 对象取值,给 Response 写值。
  5. 根据我们自己写的 Servlet 程序或者框架携带的 Servlet 类做进一步的处理(业务处理、请求的进一步处理)
  6. 最后根据 Servlet 返回的 Response 生成相应的 HTTP 响应报文。

浏览器的渲染

浏览器的功能是从服务器上取回你想要的资源,然后展示在浏览器窗口当中。资源通常是 HTML 文件,也可能是 PDF,图片,或者其他类型的内容。也可以显示其他类型的插件 (浏览器扩展)。例如显示 PDF 使用 PDF 浏览器插件。资源的位置通过用户提供的 URI(Uniform Resource Identifier) 来确定。

浏览器解释和展示 HTML 文件的方法,在 HTML 和 CSS 的标准中有详细介绍。这些标准由 Web 标准组织 W3C(World Wide Web Consortium) 维护。

下面会以 Chrome 中使用的浏览器引擎 Webkit 为例,根据上图来简单介绍浏览器的渲染。具体解析、渲染会涉及到非常多的细节,请参考 HTML5 渲染规范和对应的页面 GPU 渲染实现。

HTML 解析

浏览器拿到具体的 HTML 文档之后,需要调用浏览器中使用的浏览器引擎中处理 HTML 的工具(HTML Parser)来将 HTML 文档解析成为DOM 树,将以便外部接口(JS)调用。

  • 文档内容解析:将一大串字符串解析为 DOM 之前需要从中分析出结构化的信息让 HTML 解析器可以很方便地提取数据进行其他操作,所以对于文档内容的解析是第一步。解析器有两个处理过程——词法分析(将字符串切分成符合特定语法规范的符号)与语法分析(根据符合语法规范的符号构建对应该文档的语法树)。
  • HTML 解析:根据 HTML 语法,将 HTML 标记到语法树上构建成 DOM(Document Object Model)。

CSS 解析

  • 根据 CSS 词法和句法分析 CSS 文件和 <style> 标签包含的内容以及 style 属性的值
  • 每个 CSS 文件都被解析成一个样式表对象(StyleSheet object),这个对象里包含了带有选择器的 CSS 规则,和对应 CSS 语法的对象

页面渲染

解析完成后,浏览器引擎会通过 DOM 树和 CSS Rule 树来构造渲染树。渲染树的构建会产生 Layout,Layout 是定位坐标和大小,是否换行,各种 position, overflow, z-index 属性的集合,也就是对各个元素进行位置计算、样式计算之后的结果。

接下来,根据渲染树对页面进行渲染(可以理解为 “画” 元素)。

当然,将这个渲染的过程完成并显示到屏幕上会涉及到显卡的绘制,显存的修改,有兴趣的读者可以深入了解。

<iframe src="https://www.icloud.com/keynote/0Mu0DJnu0kyfoZwSZUmCYKSWQ?embed=true" width="640" height="500" frameborder="0" allowfullscreen="1" referrer="no-referrer"></iframe>

参考

https://github.com/skyline75489/what-happens-when-zh_CN

http://achuan.me/2017/03/01/20170301how-browser-works/

https://zh.wikipedia.org/wiki/HTTP%E4%B8%A5%E6%A0%BC%E4%BC%A0%E8%BE%93%E5%AE%89%E5%85%A8

http://www.ruanyifeng.com/blog/2010/02/url_encoding.html

https://technet.microsoft.com/en-us/library/cc772774(v=ws.10).aspx

http://www.ruanyifeng.com/blog/2016/06/dns.html

jawil/blog#14

http://robertheaton.com/2014/03/27/how-does-https-actually-work/

共计:8575 个字

@geekyouth
Copy link
Owner Author

精髓

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
09-综合系列
Awaiting triage
Development

No branches or pull requests

1 participant