简单路径线
1. 键盘或触碰输入 URL 并回车确认
2. URL 解析/DNS 解析查找域名 IP 地址
3. 网络连接发起 HTTP 请求
4. HTTP 报文传输过程
5. 服务器接收数据
6. 服务器响应请求/MVC
7. 服务器返回数据
8. 客户端接收数据
9. 浏览器加载/渲染页面
10. 打印绘制输出
详细路径线
1.键盘或触碰输入 URL 并回车确认
实际上,在此之前有准备工作。服务器启动了操作系统,然后开启了 http 服务进程(daemon),可能是(Apache/Nginx/IIS/Lighttpd)中的一个。http 服务开始定位到服务器上的 www 文件夹(网站根目录),一般位于/var/www,然后启动一些附属模块,如 php 或使用 fastcgi 方式连接到 php 的 fpm 管理进程,接着向操作系统申请一个 tcp 连接并绑定在 80 端口,调用 accept 函数,开始默默监听来自地球任何一个地方的请求,随时准备做出响应。
然后,开始键盘或手机触屏输入 URL,通过某种机制传到 CPU,CPU 进行内部处理后,再传到操作系统内核,然后由操作系统 GUI 传到浏览器,再到浏览器内核。
在 GUI 将输入事件传递到浏览器从过程中。浏览器可能会做一些预处理,他会从历史记录,书签等地方,智能匹配所有有可能的 URL。对于 Chrome,他甚至会直接从缓存中把网页渲染处理,再或者在浏览器启动时预先查询 10 个你有可能访问的域名等
2.URL 解析/DNS 查询
完整的 URL 组成:协议、网络地址、资源路径、文件名、动态参数
DNS 解析域名的两种方式:递归查询和迭代查询
递归查询
浏览器缓存 -> 系统缓存(本地 hosts 文件) -> 上层路由器缓存 -> ISP DNS 缓存(负载均衡)-> 本地名称服务器 -> 权威名称服务器 -> 顶级名称服务器 -> 根名称服务器
根名称服务器是互联网域名解析系统 DNS 中最高级别的域名服务器,全球一共 13 组,每组都只有一个主根名称服务器采用同一个 IP(注意不是 13 个,前期是个现在是集群),好多用于负载均衡、备份等,全球有 386 台根物理服务器,被编号 A 到 M 共 13 个标号
迭代查询
DNS 递归查询和迭代查询的区别
- 递归查询:以本地名称服务器为中心,是 DNS 客户端和服务器间的查询活动,查询的递交者一直在更替,其结果是直接告诉 DNS 客户端需查询的网站目标 IP 地址
- 迭代查询:以 DNS 客户端为中心,是各个服务器和服务器间的查询活动,查询的递交者一直没变化,其结果是间接告诉 DNS 客户端另一个 DNS 服务器的地址
3.应用层客户端发送 HTTP 请求
互联网内各网络设备间的通信都遵循 TCP/IP 协议,会通过分层顺序与对方进行通信。分层由高到低:应用层、传输层、网络层、数据链路层。
发送端从应用层往下走,接收端从数据链路层往上走,如图:
得到 IP 地址后,浏览器会开始构造一个 HTTP 请求,应用层客户端向服务端发送 HTTP 请求:请求报头(request header)和请求主体,请求头包括请求的方法,目标 url,遵循的协议,返回的信息是否缓存,以及客户端是否发送 cookie 等信息。因 HTTP 请求是纯文本格式,所以 TCP 的数据段中可以直接分析 HTTP 文本的。
4.传输层 TCP 传输报文
当应用层的 HTTP 请求准备好后,TCP 协议通过三次握手后,将已经分割成报文段的数据包传输
由于 TCP 的 Head-of-line blocking 问题:如客户端发 3 个 TCP 片段,结果 1 包传丢了,因 TCP 协议需保证顺序,所以其它包到达也只能等待,在 HTTP pipelining 下更严重, HTTP pipelining 可以让多个 HTTP 请求通过一个 TCP 发送。为解决此问题,Chrome 团队提出 QUIC 协议,它基于 UDP 实现的可靠传输,能减少很多来回(round trip)时间,还有前向纠错码等功能,可通过chrome://net-internals/#spdy页面来发现。
5.网络层 IP 协议查询 MAC 地址
IP 协议的作用是把 TCP 分割好的各种数据包封装到 IP 包里面传送个接收方,而确保传到和传对还需接收方的 MAC 地址(物理地址)。 一个网络设备的 IP 地址可以更换,但 MAC 地址一般是固定不变的。ARP 协议将 IP 地址解析成对应的 MAC 地址。当通信双方不在同一局域网,需多次中转,中转过程需通过下一个中转站的 MAC 地址搜索下一个中转目标。
6.数据到达数据链路层
在找到对方的 MAC 地址后,被封装好的 IP 包在被封装到数据链路层的数据帧结构中,将数据发送到数据链路层传输,再通过物理层的比特流送出去
7.服务器接收数据
接收端的服务器在链路层接收到数据包,再层层向上到应用层。
8.服务器响应请求并返回响应文件
服务接收到客户端发送的 HTTP 请求后,服务器上的的 http 监听进程会得到这个请求,然后一般情况下会启动一个新的子进程去处理这个请求,同时父进程继续监听。http 服务器首先会查看重写规则,然后如果请求的文件是真实存在,例如一些图片,或 html、css、js 等静态文件,则会直接把这个文件返回,如果是一个动态的请求,那么会根据 url 重写模块的规则,把这个请求重写到一个 rest 风格的 url 上,然后根据动态语言的脚本,来决定调用什么类型的动态文件脚本解释器来处理这个请求。
9.浏览器开始处理数据信息并渲染页面
浏览器会根据返回响应报文里的状态码,做判断。
- 200:请求成功,直接进入渲染流程
- 300:要去相应头里面找 location 域,进行跳转。这里跳转需开启一个跳转计数器,避免多个页面间形成循环跳,当跳转多次后,浏览器会报错
- 301:永久重定向,即请求的资源永久转移到新位置
- 400/500:浏览器会出一错误页面
当浏览得到一个正确的 200 响应之后,接下来面临的一个问题就是多国语言的编码解析了,响应头是一个 ascii 的标准字符集的文本,这个还好办,但是响应的正文本质上就是一个字节流,对于这一坨字节流,浏览器要怎么去处理呢?首先浏览器会去看响应头里面指定的 encoding 域,如果有了这个东西,那么就按照指定的 encoding 去解析字符,如果没有的话,那么浏览器会使用一些比较智能的方式,去猜测和判断这一坨字节流应该使用什么字符集去解码。相关的笔记可以看这里,字符集编码
接下来就是构建 dom 树了,在 html 语言嵌套正常而且规范的情况下,这种 xml 标记的语言是比较容易的能够构建出一棵 dom 树出来的,当然,对于互联网上大量的不规范的页面,不同的浏览器应该有自己不同的容错去处理。构建出来的 dom 本质上还是一棵抽象的逻辑树,构建 dom 树的过程中,如果遇到了由 script 标签包起来的 js 动态脚本代码,那么会把代码送到 js 引擎里面去跑,如果遇到了 style 标签包围起来的 css 代码,也会保存下来,用于稍后的渲染。如果遇到了 img 或 css 和 js 等引用外部文件的标签,那么浏览器会根据指定的 url 再次发起一个新的 http 请求,去把这个文件拉取回来,值得一提的是,对于同一个域名下的下载过程来说,浏览器一般允许的并发请求是有限的,通常控制在两个左右,所以如果有很多的图片的话,一般出于优化的目的,都会把这些图片使用一台静态文件的服务器来保存起来,负责响应,从而减少主服务器的压力。
dom 树构造好了之后,就是根据 dom 树和 css 样式表来构造 render 树了,这个才是真正的用于渲染到页面上的一个一个的矩形框的树,网页渲染是浏览器最复杂、最核心的功能,对于 render 树上每一个框,需要确定他的 x y 坐标,尺寸,边框,字体,形态,等等诸多方面的东西,render 树一旦构建完成,整个页面也就准备好了,可以上菜了。需要说明的是,下载页面,构建 dom 树,构建 render 树这三个步骤,实际上并不是严格的先后顺序的,为了加快速度,提高效率,让用户不要等那么久,现在一般都并行的往前推进的,现代的浏览器都是一边下载,下载到了一点数据就开始构建 dom 树,也一边开始构建 render 树,构建了一点就显示一点出来,这样用户看起来就不用等待那么久了