大致流程
- 解析URL
- DNS域名解析
- TCP连接(三次握手)
- 发送HTTP请求
- 服务器处理请求,返回响应结果
- 浏览器解析文件并渲染页面
- 断开TCP连接(四次挥手)
一、URL解析
URL解析就是当在浏览器中输入URL后,浏览器首先对拿到的URL进行识别,检验URL地址是否合法,抽取出域名字段。
例如http://www.baidu.com,这个URL主要由三部分组成:协议名、域名、端口号。通常端口号不见是因为大部分的是使用默认端口,如HTTP默认80,HTTPS默认443。
更详细的说,URL包含以下几个部分:
传输协议-服务器-域名-端口-虚拟目录-文件名-参数-锚
http-www-aspxfans.com-8080-news-index.asp-boardID=5&ID=246&page=1-name
连接起来就是:
http://www.aspxfans.com:8080/news/index.asp?boardID=5&ID=246&page=1#name
二、DNS域名解析
例如:拿www.baidu.com举例,www.baidu.com就是域名。
www.baidu.com (域名) - DNS解析 -> 11.22.33.44 (IP地址)
查询URL对应的IP地址。DNS实际上是一个域名和IP地址对应的数据库。域名和IP地址之间的转换称为域名解析。域名解析需要由专门的域名解析服务器来完成,整个过程是自动进行的。具体步骤为:
1、先到各种缓存信息中查找
① 浏览器缓存
浏览器会先检查自己的DNS缓存(缓存时间比较短,TTL默认是1000且只能容纳1000条缓存),看自己的缓存中是否有www.baidu.com对应的条目,而且没有过期,如果有就返回其对应IP地址,没有则进行下一步。
② 操作系统缓存
搜索操作系统的DNS缓存,如果没有找到,接着检查域名是否存在本地的Hosts(位于C:WindowSystem32driversetc)文件中,Hosts文件保存了一些以前访问过的网站的域名和IP的数据,它就像是一个本地的数据库,如果找到则返回对应的IP地址,没有则向DNS服务器发送查询请求。
③ 路由器缓存
路由器查找其缓存,如果找到则返回对应IP地址,没有则下一步
④本地DNS缓存
浏览器会向本地配置的首选DNS服务器ISP DNS发起域名解析请求(ISP DNS就是在客户端电脑上设置的首选DNS服务器,它们在大多数情况下都会有缓存),通过UDP协议向DNS的53端口发起请求,这个请求是递归的请求,也就是运营商的DNS服务器必须得提供给我们该域名的IP地址。如果找到则返回对应IP地址,没有则下一步。
⑤ LDNS向根域名服务器查询、递归查询
递归查找的顺序是(根域名服务器,一级域名服务器,二级域名服务器,三级域名服务器)。
在前面所有步骤没有缓存的情况下,LNDS(本地域名服务器)会向Root Name Server(根域名服务器)发起请求获得根域的IP地址,然后再像com域发起请求获得com域的IP地址,最后向www.baidu.com这个域名的DNS地址发起请求获得对应的IP地址。
事实上,真正的网址是www.google.com.,并不是我多打了一个.,这个.对应的就是根域名服务器,默认情况下所有的网址的最后一位都是.,既然是默认情况下,为了方便用户,通常都会省略,浏览器在请求DNS的时候会自动加上,所有网址真正的解析过程为: . -> .com -> google.com. -> www.google.com.
下面这个图很好的诠释了整个流程:
2、LDNS查找成功将IP地址返回给操作系统缓存起来,操作系统将IP地址返回给浏览器缓存起来,浏览器获得IP地址,发起建立连接的请求。
三、TCP连接(三次握手)
浏览器主机根据IP地址与服务器建立TCP连接。浏览器向服务器发送SYN连接请求,经过服务器与浏览器三次报文的交互连接建立完成。就可以发送数据了。
TCP三次握手:
第一次握手:客户端向服务端发送请求(SYN=1),并进入SYN_SENT状态,等待服务器确认;(你在家吗?我要来你家吃饭)
第二次握手:服务器收到请求并确认,回复一个指令(SYN=1,ACK=1),此时服务器进入SYN_RECV状态;(在的,来吧)
第三次握手:客户端收到服务器的回复指令并返回确认(ACK=1),客户端和服务端进入ESTABLISHED状态,完成三次握手。(好嘞)
注:SYN(synchronous建立连接)、ACK(acknowledgement 表示响应、确认)、PSH(push表示有DATA数据传输)
四、发送HTTP请求
握手过程中传送的包里不包含数据,三次握手完毕后,客户端与服务器才正式开始传送数据。根据HTTP协议的要求,组织一个HTTP数据包,向服务器发送HTTP请求。
HTTP的请求报文由请求行、请求头、空行和请求数据4部分组成。
请求行:包括请求方法、URL、协议版本。如GET/HTTP/1.0
请求头:包括一系列key:value成对格式的数据。比如Content-type:application/x-www-form-urlencoded。
空行:这个也是必须的。
请求数据:真正请求所需要的数据,比如登录时的账号和密码。
注:HTTP请求方法详见文章---《HTTP请求方法详解》
五、服务器处理请求,浏览器接受响应
请求到达服务器,服务器会将收到的HTTP请求报文封装成HTTP的Request对象,并通过不同的Web服务器进行处理,处理完的结果以HTTP的Response对象返回,即HTTP响应报文,最终返回给浏览器HTML文件。
HTTP响应报文由状态行(status line)、响应头(headers)、空行(blank line)和响应数据(response body)四部分组成。
1、状态行由3部分组成,分别为:协议版本、状态码、状态码描述。其中协议版本与请求报文一致,状态码描述是对状态码的简单描述。
响应的状态码
- 1xx:指示信息-表示请求已接收,继续处理
- 2xx:成功-表示请求已被成功接收
- 3xx:重定向-要完成请求必须进行更进一步的操作
- 4xx:客户端错误-请求有语法错误或请求无法实现
- 5xx:服务器错误-服务器未能实现合法的请求
注:平时遇到比较常见的状态码有:200, 204, 301, 302, 304, 400, 401, 403, 404, 422, 500。
2、响应报头主要由Cache-Control、Server、Connection、Date等组成。
3、响应数据为服务器返回给浏览器的信息,主要由HTML、css、js、图片等文件组成。
六、浏览器解析文件并渲染页面
6.1 整体流程
1、解析html
2、构建dom树
3、处理CSS标记,构成层叠样式表模型CSSOM(CSS Object Model)。
4、dom树结合cssOM,构建渲染树
5、布局
6、绘制
7、JavaScript 编译执行
6.2 过程解析
浏览器是一个边解析边渲染的过程。首先浏览器解析HTML文件构建DOM树,然后解析CSS文件构建渲染树,等渲染树构建完成,浏览器开始布局渲染树并将其绘制到屏幕上,这个过程比较复杂,涉及到两个概念:回流(reflow)和重绘(repaint)。DOM节点的各个元素都是以盒模型存在的,这些都需要浏览器去计算其位置和大小,这个过程称为回流。当盒模型的位置、大小以及属性如颜色、字体等确定之后,浏览器便开始绘制内容,这个过程称为重绘。页面在首次加载时必然会经历回流和重绘,这是非常消耗性能的,尤其在移动设备上,它会破坏用户体验,有时会造成页面卡顿,所以应该尽可能减少回流和重绘。
JS的解析是由浏览器中的JS解析引擎实现的。JS是单线程运行,即在同一个时间内只能做一件事,所有的任务都需要排队,前一个任务结束,后一个任务才开始。但是又存在某些任务比较耗时,所以需要一种机制可以先执行排在后面的任务,这就是:同步任务(synchronous)和异步任务(asynchronous)。JS的执行机制就可以看做是一个主线程加上一个任务队列(task queue)。同步任务就是放在主线程上执行的任务,异步任务就是放在任务队列中的任务,所有的同步任务在主线程上执行,形成一个执行栈;异步任务有了运行结果就会在任务队列中放置一个事件;脚本运行时先依次运行执行栈,然后从任务队列中提取事件,运行任务队列中的任务,这个过程是不断重复地,所有又叫事件循环。
浏览器在解析过程中,如果遇到请求外部资源时,浏览器将重复下载该资源。请求过程是异步的,并不会影响HTML文档进行加载,但是当文档加载过程中遇到JS文件,HTML文档会挂起渲染过程,不仅要等到文档中JS文件加载完毕还要等到解析执行完毕,才会继续HTML渲染。原因是因为JS有可能修改DOM结构,这就意味着JS执行完成前,后续所有资源的下载是没有必要的,这就是JS阻塞后续资源下载的根本原因。所以js我们大都建议放在body元素之后。CSS文件的加载不影响JS文件的加载,但是却影响JS文件的执行,JS代码执行前浏览器必须保证CSS文件已经下载并执行完毕。
注:浏览器对同一域名的并发连接数是有限的,通常为 6 个。
宏任务
- 同步任务:按照顺序执行,只有前一个任务完成后,才能执行后一个任务
- 异步任务:不直接执行,只有满足触发条件时,相关的线程将该异步任务推进任务队列中,等待JS引擎主线程上的任务执行完毕之后才开始执行,例如异步ajax、DOM事件、setTimeOut等
微任务
微任务是ES6和Node环境下的,主要API有:Promise、process、nextTick
微任务的执行在宏任务的同步任务之后,在异步任务之前。
具体执行方式详见文章-《JS事件循环机制(event loop)》
七、断开TCP连接
四次挥手:
首先,需要明确的是,四次挥手不知道哪一方是主动方,哪一方是被动方。
第一次挥手:主动方发送一个FIN(finish),告诉被动方:我不会再给你发数据了(当然,在FIN包之前发出去的数据,如果没有收到对应的ack确认报文,主动方依然会重新发送这些数据),但此时,主动方依然可以接收数据。(我这边没数据了,我要走啦)
第二次挥手:被动方收到FIN包后,发送一个ACK给对方,确认序号为收到序号+1。(我知道了,我看看我这边有没有数据)
第三次挥手:主动方进入Fin_Wait状态,等待被动方的FIN报文,被动方发送完毕后,向主动方发送FIN,告诉主动方,我的数据也发送完了,不会再给你发送数据了。(我这边也没数据了,咱可以断开连接了)
第四次挥手:主动方收到FIN后,发送一个ACK给被动方,确认序号为收到序号+1,并关闭连接。至此完成四次挥手。(好的,拜拜)
断开连接为什么必须这么繁琐的四次挥手?
主要是因为客户端告诉服务器想断开连接的时候,服务器的数据不一定已经处理完毕,所以服务器是先告诉客户端已经收到了它想断开连接的请求,然后当服务器中数据处理完毕后,便断开请求通知客户端,客户端收到后也断开请求并通知服务器。