从输入URL到浏览器显示页面发生了什么?
前言
我们每天上网都使用http协议,我们每天编程也使用http协议,Spring Cloud的微服务使用Http template实现RPC调用,到处都是HTTP这四个字……
作为一名天天接触HTTP协议的程序员,不管是前端还是后端,怎能不了解HTTP协议呢?就连校招面试,HTTP协议也作为考官必点的一道开胃菜进行提问!
不管是面试还是开发,学好http协议都是必要的~
今天我们就来好好深入钻研,死磕HTTP协议!现在让我们泡杯咖啡,好好梳理一下HTTP协议!
输入网址
当你输入网址时,通常会输入一个域名而非一个IP地址,因为IP地址是很难记忆的,一般需要特定对象告知才能访问。比如云服务器商提供的IP,自建服务器搞到的公网IP,公司学校的特定公开局域网IP等等。但是大部分时候,我们往往会输入www.baidu.com这样的东西来访问网站。这种东西叫做域名。
域名系统(Domain Name System) 是互联网使用的命名系统,用来把人们使用的及其名字转换为IP地址。当你输入URL的时候,你就间接的使用了DNS。
DNS是应用层协议,事实上他是为其他应用层协议工作的,包括不限于HTTP和SMTP以及FTP,用于将用户提供的主机名解析为ip地址。
我们的电脑上运行着DNS服务的客户端,负责接收域名并执行解析工作,最终得到IP地址。
所以,浏览器抽取域名,并请求本机的DNS客户端,进行域名解析。
DNS解析
1. 本地域名解析
在早期的时候,网络上只有数百台计算机,因此我们只使用一个叫做hosts的文件,记录域名和对应的IP地址,每当我们进行访问的时候,计算机就根据URL到这个文件中查表,自动解析域名至IP,这样就可以进行访问了。这个文件现在还在我们的电脑上存在,每当我们输入URL的时候,首先DNS解析器客户端查询本地的缓存和hosts文件看是否能直接本地解析域名,若不能,解析器会将待解析的域名放在DNS请求报文中,以UDP方式发给本地域名服务器,本地服务器代理执行域名解析操作,能则返回,DNS解析步骤成功。
本地域名服务器不是指在自己的机器上的服务器,而是一个设置好的DNS服务器IP地址,比如WINDOWS系统的网络设置中的DNS服务器和备用DNS服务器地址。他们距离用户较近,一般不超过几个路由器的距离。
2.递归和迭代解析域名
当本地域名服务器接收到请求之后,就由本地域名服务器进行域名查询。本地域名服务器可以到互联网请求其他服务器帮助查询域名。
互联网的域名系统被设计为一个联机分布式数据库系统,采用C/S方式进行连接,因此互联网上存在许多域名服务器在专设的节点上运行。
本地域名服务器在查找域名后,就会把对应的IP放在回答报文中返回,进程即可获得IP地址后进行通信了。DNS 查询以各种不同的方式进行解析。
- DNS 服务器可使用其自身的资源记录信息缓存来应答查询,也可代表请求客户机来查询或联系其他 DNS 服务器,以完全解析该名称,并随后将应答返回至客户机。这个过程称为递归。
- 另外,客户机自己也可尝试联系其他的 DNS 服务器来解析名称。如果客户机这么做,它会使用基于服务器应答的独立和附加的查询,该过程称作迭代,即DNS服务器之间的交互查询就是迭代查询。
采用递归和迭代取决于最初报文设定的哪种方式,一般都是递归查询。
递归和迭代具体的判断方法如下,结合图片进行理解。
1.DNS客户端和本地名称服务器是递归,而本地名称服务器和其他名称服务器之间是迭代。
2.DNS客户端和本地名称服务器是递归,而本地名称服务器和其他名称服务器之间也是递归。
建立TCP链接
- 链接建立
获取到IP之后,需要向HTTP服务器获取网站资源了。要使用HTTP协议,首先需要建立和网站的链接,HTTP协议是基于TCP链接的应用层协议,因此首先要和目标站点建立TCP链接。
TCP链接建立的过程,通常被称为三次握手。
报文格式
在进行讲解之前,先了解一下报文格式。
在进行握手的时候,我们主要会用到序列号seq、确认号ack、SYN、ACK、FIN这几个报文段。
序列号(Sequence Number):该字段用来标识TCP源端设备向目的端设备发送的字节流,它表示在这个报文段中的第几个数据字节。序列号是一个32位的数。
确认号(Acknowledge Number):TCP使用32位的确认号字段标识期望收到的下一个段的第一个字节,并声明此前的所有数据已经正确无误地收到,因此,确认号应该是上次已成功收到的数据字节序列号加1。收到确认号的源计算机会知道特定的段已经被收到。确认号的字段只在ACK标志被设置时才有效。
ACK(Acknowledgement field significant,确认字段标志): 取1时表示应答字段有效,也即TCP应答号将包含在TCP段中,为0则反之。
SYN(Synchronize sequence numbers,同步序列号):表示同步序号,用来建立连接,为1时,表示处于同步阶段,为0则反之。
FIN(No more data from sender):表示发送端已经发送到数据末尾,数据传送完成,发送FIN标志位的TCP段,连接将被断开。此时进入四报文挥手阶段。
三报文握手
三报文握手即为三次握手。叫他三报文握手的原因是,我们其实是通过三次报文交换达成了一次握手,从而建立链接,而不是握手了三次,这听起来就跟建立三次连接一样,有一定的语义模糊。
握手的目标
- 让每一方都确定对方的存在
- 允许双方协调一些参数
- 对运输实体资源进行分配。
TCP协议的握手需要满足可靠性,因为TCP协议保证了信息传输必须可靠。
握手的过程
第一次报文发送 客户端向服务端发送连接请求报文段。
该报文段的头部中SYN=1,ACK=0,seq=x。
请求发送后,客户端便进入SYN-SENT状态。
第二次报文发送 服务端收到连接请求报文段后,如果同意连接,则会发送一个应答:
SYN=1,ACK=1,seq=y,ack=x+1。 该应答发送完成后便进入SYN-RCVD状态。
第三次报文发送 当客户端收到连接同意的应答后,还要向服务端发送一个确认报文段,表示:服务端发来的连接同意应答已经成功收到。 该报文段的头部为:
ACK=1,seq=x+1,ack=y+1。
客户端发完这个报文段后便进入ESTABLISHED状态,服务端收到这个应答后也进入ESTABLISHED状态,此时连接的建立完成!
规律总结:
SYN是同步的意思,在第一次请求的时候 SYN=1,ACK=0,表示第一次请求连接。
服务器回应请求的时候,使用SYN=1,ACK=1,ACK表示对客户端的握手请求进行回应。
- ACK规律:在连接建立后所有传送的报文段都必须把ACK置为1.
第三次请求,SYN=0,说明不是第一次请求了。此时链接已经建立了,SYN=0,ACK在之后永远为1. - seq规律:seq在起始的时候随机一个数进行产生,范围是 [ 0 , 2 32 − 1 ] [0,2^{32}-1] [0,232−1],即32位bit的长度。他的含义是本报文段所发送的数据的第一个字节的序号。虽然握手三报文没有数据,只有报文头,协议还是规定占1长度,因此是x+1这样的表示。
- ack规律:ack表示的是期望收到对方下一个报文段的第一个数据字节的序号。比如服务器回应的时候ack=x+1,就是期望下次收到一个seq=x+1的序号。在握手的时候,数据长度为1,因此是x+1。但是在传输状态的时候,因为存在要传输的数据体,那么期望序号应该就是上次的收到的客户端的序号x+数据体的长度了,比如x=501,数据体长度=200,那么501+200就可以得出701,此为下次期望收到的seq序号,
为什么不是两次报文交换?
我们可以看到,前两次之后SYN已经置为0,说明建立了链接,那么为什么还需要第三次报文发送?这是基于可靠性进行考虑的做法。
设想以下情况:
原因: 已失效的连接请求报文段
当A发送一个握手请求到B,但是由于网络抖动、网络时延,过了一段时间B没有向A发回应,那么A就会重传一个握手包过去,此时有2次握手的报文向B发送了。则B很可能会接收到两次握手请求,那么此时如果是二报文握手,B就会认为这是两个链接建立请求,会认为应该建立2次链接,那么B就会先建立2个链接资源,切换状态,并发送2次回应,这就导致资源浪费了,因为其中一个链接永远不会有数据发来。若是三报文握手,就能防止这样的现象的发生,因为A只会进行一次回应。这样B就知道A并没有2次要求建立链接了。
HTTP请求
发送HTTP请求
在建立连接之后,就可以发送请求了,此时浏览器依据HTTP协议,向服务器发送请求,具体的请求内容可以F12查看。一个HTTP请求报文就发送出去了。
如下图所示,我们可以从右边看到请求的报文头。具体的内容,请参考HTTP协议语法规定等等,这些字符将作为TCP流中的内容发送到服务器,然后服务器再通过TCP将内容进行回应。
可以看到左边密密麻麻的多个回应,这就是多次浏览器和服务器进行HTTP协议通讯的过程。根据HTTP协议版本和头信息,这些内容有可能每次都需要TCP握手,也有可能握手之后保活(keep-alive),从而不需要再建立TCP通道,直接多次进行HTTP协议的通信。
HTTP协议中,规定可以从浏览器本地缓存网页,因此如果HTTP请求有缓存字段,有可能直接在本地就获取资源,不需要发送请求通过互联网获取资源了。
良好的缓存策略可以降低资源的重复加载,提高网页的整体加载速度。
具体的缓存策略如下。
强缓存与协商缓存
此处参考缓存(二)——浏览器缓存机制:强缓存、协商缓存
想要详细学习,可以进入进一步了解。
通常浏览器缓存策略分为两种:强缓存和协商缓存
1、基本原理
1)浏览器在加载资源时,根据请求头的expires和cache-control判断是否命中强缓存,是则直接从缓存读取资源,不会发请求到服务器。
2)如果没有命中强缓存,浏览器一定会发送一个请求到服务器,通过last-modified和etag验证资源是否命中协商缓存,如果命中,服务器会将这个请求返回,但是不会返回这个资源的数据,依然是从缓存中读取资源
3)如果前面两者都没有命中,直接从服务器加载资源
2、相同点
如果命中,都是从客户端缓存中加载资源,而不是从服务器加载资源数据;
3、不同点
强缓存不发请求到服务器,协商缓存会发请求到服务器。
当缓存命中,可以观察F12进行判断,可以看到从磁盘或内存缓存中直接拿出了网页资源。
具体流程图如下,可以看到,先进行强缓存命中判断,再进行协商缓存判断,还有过期、版本判断等等,最终得到资源。
转发HTTP请求
一个HTTP请求报文在互联网中并不是点对点传输到对方服务器的,这其中要经过很多的中转站进行转发。比如路由器,又或者特殊的缓存节点,又或者公司的网关等等,进行层层转发后,才会最终到达目标服务器。这其中有几种比较特殊的节点。
代理服务器节点
代理服务器把最近的一些请求和响应暂存在本地磁盘中,当请求与缓存的相同,就可以直接返回,不需要去继续转发了。这样就减少了给服务器的压力,服务器处理的请求更少,响应速度快,请求时延也会降低,性能就可以得到一定的优化。
反向代理节点
反向代理服务器,如Nginx,对请求进行反向代理,转发请求到其他IP端口,从而进行负载均衡等功能。
HTTP服务器
Nginx可以充当简单的静态服务器,返回http进行应答。Tomcat、Apache等软件也可以当Http服务器,进行应答。在复杂的架构当中,HTTP服务器可以充当业务网关,进行复杂的权限管理、灰度测试转发等安全和运维方面的配置,然后在公司的业务集群中进行多次网络通信,最后给出结果。
处理HTTP请求
对HTTP进行处理,根据网站的复杂程度,动态网站需要运行脚本,根据模板和业务逻辑处理返回静态的HTTP信息,也可以直接返回HTTP和各种资源,再通过Ajax等异步手段,发起请求,异步获取网页信息。
通过HTTP服务器,可以将请求头解析,程序员通过API获取请求信息,并作出业务处理,产生相应的动作和数据,然后使用模板或者JSON方式返回信息。
回应HTTP请求
返回的时候,HTTP服务器会组装HTTP响应头,程序员也可通过配置HTTP服务器或者使用编程语言修改头部部分字段,最终通过TCP方式,将HTTP响应报文发送到浏览器客户端。
关闭TCP链接
发送完HTTP请求之后,需要关闭TCP的连接通道,这样的时候仅需要进行TCP的连接释放。
这时候就需要进行四次挥手,其实挥手也是一种握手,表示端口之间进行协商的过程。
四报文挥手
- 步骤一
客户端向服务端发送报文,报文中,FIN=1,seq=u,其中seq是前面已传送过的数据的最后一个字节的序号加一,A进入FIN-WAIT-1状态 - 步骤二
服务端接收到信息,进行回应,此时报文内容是ACK=1,seq=v,ack=u+1,v的大小与u的规律是一样的。然后服务端进入CLOSE-WAIT状态。并且此时TCP服务器进程会对高层应用进行通知。在此时,链接已经处于半关闭状态,如果服务端向客户端发送数据,客户端仍要接收。 - 步骤三
客户端收到服务端的通知后,进入FIN-WAIT-2状态。 - 步骤四
如果服务端已经没有向客户端发送的数据,就会通知TCP释放服务端到客户端的连接,这个时候发送的报文内容是FIN=1,ACK=1,seq=w,因为服务端可能又发送了一些数据,w表示上次发送末尾的seq序号的+1,ack为u+1这里的u+1就是上次客户端终结握手的时候发送的u+1,这个时候B进入了LAST-ACK状态。 - 步骤五
客户端在收到服务端的通知之后,发出确认报文,ACK=1,seq=u+1,ack=w+1,进入到TIME-WAIT状态,在这个状态等待2MSL之后,客户端进入到了CLOSE状态。
具体图解如下:
TIME-WAIT状态的意义
为什么在TIME-WAIT状态要等待2MSL的时间呢?这也是基于可靠性来考虑的。
场景一
如果在最后的ACK没有发送到服务端,服务端就会一直在LAST-ACK状态进行等待,无法顺利进入CLOSE状态,这必然导致资源的浪费,为了解决这个问题,服务器就会发起超时重传,重新发送一个FIN=1的ACK消息,这个时候,处于TIME-WAIT的客户端就可以再次发送确认的报文,并重置TIME-WAIT的计时。这样服务端就能顺利关闭了。
场景二
协议认为,在2MSL的时间后,本连接持续时间内产生的所有报文都会从网络上消失。不会有“已失效的连接报文字段”这种现象的发生,因此需要等待2MSL,等待报文失效。这样就能确保下次再次进行连接是新的连接,而不是由于重传等导致的无效连接请求了。
浏览器解析资源
DOM解析
当HTTP传输到浏览器后,浏览器就会启动资源解析流程。
浏览器对HTTP文档进行词法分析,从而把HTML文档化为一颗文档树的数据结构。
HTTP的本质是XML,XML可以被解析为树的数据结构,而HTTP解析之后的树叫做DOM Tree
此时浏览器也会接收CSS、图片资源、各种资源合并到DOM的解析当中,遇到JS资源的时候,也会对JS进行语法解析等一系列步骤,并执行js。
浏览器布局渲染
显示Render树
浏览器对CSS规则、DOM树、图片、JS文档等资源进行复杂的处理之后,会形成一颗渲染树,此时浏览器就会对渲染树进行重绘和显示了。当JS操作DOM或者有其他情况发生的时候,会触发回流(reflow)现象,导致浏览器的重绘,这可能是一项耗时的工作,现代前端框架Vue、React等使用Virtual DOM来合并对DOM的修改操作,尽可能减少多次重绘带来的性能损失。