什么是HTTP请求过程?
这是一个直到现在也隐隐约约威胁着小白鼠的问题,发送get,post请求,服务端就直接解析了吗?
No!
在你真正去了解和挖掘之后,你会发现,在这个过程中,请求发生了很多不可思议的故事
Start:
HTTP请求过程大致分为以下四步:
- 读取浏览器缓存
- CDN解析域名获取IP地址
- TCP协议(握手,传输,挥手)
- HTTP发送请求和响应
为什么浏览器缓存放在第一位?
因为浏览器中如果存在缓存数据,并且没有过期,那么浏览器会优先读取缓存数据而不会去发送请求,但如果没有缓存,我们继续往下看
浏览器缓存机制
如果想知道如何读取浏览器缓存,我们必须要知道浏览器缓存的机制
- 强制缓存(本地缓存) Expires/Cache-control
- 协商缓存 Last-Modifed/Etag
由于浏览器和服务器的通信基于TCP,属于应答的请求方式,所以在客户端第一次向服务端发送请求并得到结果时,会根据响应报文中的HTTP头的缓存标识来绝对是否进行缓存
- 每次发起请求,都会现在浏览器缓存中查询该请求结果和缓存标识
- 浏览器每次拿到返回结果都会将该结果和缓存标识存入浏览器缓存
强制缓存(本地缓存)
强制缓存就是向浏览器缓存查找缓存结果和缓存标识,并根据返回结果的缓存表示和规则来决定是否使用该缓存结果
- 如果该请求结果和缓存标识不存在,则强制缓存失败,需要再向服务器解析域名获取IP地址
- 如果该请求结果和缓存表示存在,但该请求结果过期,则强制缓存失败,需要使用协商缓存
- 如果该请求结果和缓存表示存在,但该请求结果未过期,则直接返回缓存结果
强制缓存的规则
浏览器向服务器发送请求时,服务器会将缓存规则存放在HTTP相应报文的请求头中和请求结果一起响应给浏览器。
控制强制缓存的字段分别是Expires和Cache-Chntrol,其中Cache-Control比Expire优先级高,但在新的HTTP协议中,Expire已经被Cache-Control取代
Cache-Control控制网页缓存的等级划分:
- public:所有内容都被缓存
- private:只有客户端可以缓存,Cache-Control的默认值
- no-cache:客户端缓存内容,决定权在协商缓存
- no-store:所有内容都不会被缓存
- max-age = xxx (xxx is numberic):缓存内容将在xxx秒后失效
协商缓存
在强制缓存失效后,浏览器携带缓存标识向服务器发送请求,由服务器根据缓存标识决定是否继续使用缓存
- 协商缓存生效,返回304
- 协商缓存失效,返回200和请求结果
导致缓存失效的原因
协商缓存的控制字段有 Last-Modified /If-Modified-Since 和 Etag/If-None-Match。 Etag/If-None-Match的优先级比Last-Modified /If-Modified-Since 高
Last-Modified
表示一个服务器上的资源的最后修改时间,资源可以是静态的或者动态的内容。一般在第一次请求时服务器发给客户端的缓存标识
If-Modified-Since
客户端再次发送该请求时,携带上次请求返回的Last-Modified值,服务器收到该请求,发现 If-Modified-Since字段,如果服务器资源的最后修改时间大于If-Modified-Since的值,则重新返回新资源,状态码为200,此时则表明协商缓存失效,之前的缓存不再使用。否则,返回304,资源没有被更新,协商缓存生效,继续使用浏览器缓存。
Etag
Etag是服务器响应请求时,返回当前资源文件的唯一的编号
If-None-Match
客户端再次发起该请求时,携带上次请求返回的唯一编号,服务器收到请求后,将If-None-Match的字段值与该资源在服务器上的Etag值做对比,一致则返回304,说明资源无更新,协商缓存生效,继续使用缓存文件,不一致说明资源已更新,重新返回资源文件,状态码为200
现在我们已经了解了浏览器缓存的机制,那么浏览器的缓存到底是什么呢?
浏览器缓存分为两种,内存缓存和磁盘缓存
内存缓存(from memory cache):具有两个特点,分别是快速读取和时效性:
- 快速读取: 内存缓存会将编译解析后的文件,直接存入该进程的内存中,占据该进程一定的内部资源,以方便下次运行使用时的快速读取
- 时效性:一旦进程关闭,则该进程的内存则会清空。
硬盘缓存(from disk cache): 硬盘缓存则是直接将缓存写入硬盘文件中,读取缓存需要对该缓存存放的硬盘文件进行I/O操作,然后重新解析该缓存内容,读取复杂,速度比内存缓存慢。
用户的操作和缓存的关系表
用户操作 | Expires/Cache-Control | Last-Modifed/Etag |
---|---|---|
地址栏回车 | true | true |
页面连接跳转 | true | true |
新开窗口 | true | true |
前进后退 | true | true |
F5刷新 | false | true |
Ctrl+F5强制刷新 | false | false |
浏览器缓存总结图
注意:第一次访问一个请求时,此时浏览器不存在缓存,需要去服务器中获取,去服务器中获取就需要解析域名获取IP,这就设计到了第二个步骤DNS域名解析
DNS域名解析
互联网都是通过URL地址来发布请求资源的,而URL需要DNS解析成IP才能够与远程主机建立连接
什么是DNS?
它是一种组织成域层次结构的计算机和网络服务命名空间系统,应用于TCP/IP网络,它提供的服务是将主机名和域名转换为IP地址的工作
分层式DNS域名服务器的划分:
- 根域名服务器
最高层次的域名服务器,也是最重要的域名服务器,所有的根域名服务器都知道所有的顶级域名服务器的域名和IP地址
- 顶级域名服务器(TLD服务器)
管理在该顶级域名服务器注册的二级域名
- 权威域名服务器
如阿里和腾讯云,华为云自己申请搭建的域名,.net .tech等
还有一个本地域名服务器,它对域名系统非常重要,当一台主机发出DNS请求时,这个请求报文首先发送给本地域名服务器
DNS域名解析过程
- 检查浏览器缓存中是否存在该域名对应解析过的IP地址
如果缓存中存在该域名解析过得IP地址,则解析结束,发送请求,否则继续向下解析。
需要注意的是:浏览器缓存有大小和时间限制
- 如果浏览器缓存中没有
用户的浏览器缓存中不存在这个域名对应的解析结果,则浏览器会查找操作系统缓存中是否有这个域名对应的DNS解析结果,windows和linux都在host文件中可以查看
- 向本地服务器查看(LDNS)
如果前两个过程都无法完成解析,则操作系统会把这个域名发送给LDNS,也就是本地的域名服务器
LDNS 即用户本地DNS, CDN 调度系统会通过这个ip来给用户分配CDN 节点
- LDNS没有命中,直接到Root Server域名服务器请求解析
Root Server就是最高等级的根域名服务器,所有的根域名服务器都知道所有的顶级域名服务器的域名和IP地址
- 根域名服务器返回给本地域名服务器一个所查询与的主域名服务器(gTLD Server)地址
gTLD就是国际顶级域名服务器,com cn等等
- 接受请求的顶级域名服务器查询并返回此域名对应的权威服务器
权威服务器就是注册的域名服务器,比如阿里,华为,腾讯的域名服务器
- 权威服务器查询存储的域名和IP的映射关系表并返回
在得到目标IP后,联通TTL值一起返回给DNS Server域名服务器
- 将解析的IP地址返回给用户,用户根据TTL值缓存在本地系统缓存中,域名解析就结束了
DNS域名解析过程中的查找方式
- 递归查询
主机向域名服务器的查询大多都采用递归查询,什么是递归查询?
如果主机访问的本地域名服务器不知道被查询域名的IP地址,那么本地域名就求的顶级域名服务器查询并会以DNS客户的身份代替主机向其他根域名服务器继续发出查询请求报文,而不是主机本身
- 迭代查询
本地域名服务器向根域名服务器的查询通常是采用迭代查询,什么是迭代查询?
根据明服务器收到本地域名服务器发送的迭代查询请求报文时,要么给出IP地址,要么告诉本地域名服务器下一步应当向哪一个域名服务器查询,然后让本地域名服务器进行后续的查询操作
DNS中的缓存
DNS域名解析后会缓存解析结果,其中主要在两个地方缓存结果,本地域名服务器和用户的本地及其,这两个缓存都是由TTL值和本机缓存大小控制
DNS图解
2019.9.4
因为昨天产品发版上线,我待执行刷库脚本,所以没有写完,今天接着写。
了解了浏览器缓存,也了解了DNS解析,那么接下来,就是了解如何建立IP与服务器之间的连接关系,只有这个连接关系存在的时候,才能够与服务器进行通信
首先,什么是TCP连接?
在浏览器发送HTTP请求到服务器之间,需要建立一条TCP/IP的连接,每一条的TCP连接都被通信两端的端点确定,这种连接的端点也叫作套接字。根据FC 793的定义,端口号拼接 到IP地址就构成了套接字。而一次TCP连接主要分为建立连接,数据传输,断开连接
TCP连接三步骤:
- 建立连接三次握手
- 传输数据
- 断开连接四次挥手
TCP报文段首部的概念:
在了解TCP的三次握手和四次挥手之前,我们务必先了解和TCP连接有关的报文段首部的几个控制位和初始序列号
- 确认号
确认号是期望收到对方下一个报文段的第一个数据字节的序号,假如B正确收到A发送过来的一个报文段,其序列号字段值为501,而数据长度是200字节(序列号501~700),这表明B正确收到了A发送的序列号为700为止的数据。所以B期望收到A的下一个数据序列号为701,于是B将确认是置为701,表示ACK = 701。所以,如果确认号=n,则n-1之前的所有数据都已正确收到
- 确认ACK
ACK标志位用于确认收到
- 同步SYN
用来初始化一个连接的同步序列号,当建立一个新连接时,从客户端发送到服务器的第一个报文段的SYN位字段将被启用
- 终止FIN
用户释放连接(也就是断开连接),当FIN = 1时,表明此报名文的发送方数据已经发送完毕,并要求释放/断开运输链接
- 序列号
由于TCP是面向字节流的,在一个TCP连接传输的字节流的每一个字节都按照顺序编号,整个传输要传输的字节流的起始序列号必须在连接建立的时候设置,那么,这里的序列号就是TCP三次握手过程中的初始序列号。在<TCP/IP详解 卷1:协议>中提到,在一个连接中,TCP报文段在经过网络路后可能会存在延迟抵达与排序混乱的情况,所以为了解决这个问题,需要仔细选择初始序列号,所以现代系统通常采用半随机的方法选择初始序列号,保障一定的安全性
注意: 确认号是针对接收方而言的,接收方希望收到发送方下一个报文段的第一个数据字节的序列号,而SYN所初始化的序列号是针对发送方,在建立连接阶段,发送方和接收方都要发送自己的初始序列号。
TCP三次握手:
- 第一次握手(进入同步已发送状态)
客户端在建立TCP连接时,向服务器发送连接请求报文段(也就是SYN报文段)设置首部微中的同部位SYN = 1,同时随机选择一个初始序列号 seq = x 。TCP规定,SYN报文段(SYN = 1)不能携带数据,但要消耗掉一个序列号,这是TCP客户进程进入SYN - SENT(同步已发送)状态
- 第二次握手(进入同步收到状态)
服务器收到连接请求报文后,即统一建立连接,则向客户端发送确认,在确认报文段(SYN + ACK段)中把SYN位和ACK为都设置为1,确认号 ack = X + 1,标识服务器希望从客户端这边接收到数据的序列号。同时也为自己随机选择一个初始序列号 seq = y。
**注意:**这个报文段也不能袖带数据,但同样要消耗一个序列号,这是TCP服务器进程进行SYN(同步收到)状态
- 第三次握手(已建议连接状态)
客户端收到服务端发送的确认ACK后,还要再次向服务器给出确认,确认报文段的ACK设置为1,确认号ack = y + 1,而自己的序列号 seq = x + 1,TCP的标准规定,ACK报文段可以携带数据,但如果不懈怠数据则不消失序列号,在这种情况下,下一个数据报文段的序列号仍然是seq = x + 1。这时,TCP连接已经建立,客户端进入已建立连接状态,服务端收到确认后,同样也会进入已连接状态
TCP为什么是三次呢?
A:喂
B:啊,你说
A:bibibiiibibibib
两次
A:喂
B:bibibibibibibibib
一次
A:喂…
**注意:**三次握手的目的不仅在于通信双方了解一个连接正在建立,还在于利用数据包的选项来称在特殊的信息,确保通信双方是可靠的
TCP四次挥手:
- 第一次挥手(终止等待状态)
客户端向服务器发出连接释放的报文段(就是FIN标志为1的报文段),并停止再发送数据,客户端把连接释放的报文段首部的终止控制位FIN设置为1,其序列号 seq = u,它等于前面已传送过的数据的最后一个字节的序列号+1,这时客户端进入FIN-WAIT-1(终止等待1)状态
- 第二次挥手(终止等待状态2)
服务区收到连接释放报文段后立即发出确认,确认号 ack = u + 1,而这个报文段自己的序列号是v,等于服务器前面已传送过的数据的最后一个字节的序列号+1,然后服务器进入关闭等待状态。此时的TCP连接处于半关闭状态,即客户端已经没有数据要发送了,但服务器可能还有数据要发送,客户端是仍然要接受的。
**注意:**服务器到客户端这个方向的连接并没有关闭,并会持续一段时间。客户端收到来自服务器的确认后,就进入终止等待2的状态,等待服务器发出释放连接报文段
- 第三次挥手(最后确认)
如果服务器已经没有向客户端发送数据,其应用的进程就通知服务器释放连接。此时服务器发出的释放连接报文段必须是FIN=1,并进入最后确认状态
- 第四次挥手(时间等待)
客户端收到服务器的连接释放报文段后,立即对此发出确认。在确认报文段中把ACK设置为1,确认号ack = w + 1。而自己的序列号是 seq = u + 1,然后进入时间等待状态,此时,TCP连接还没有释放,需要等待时间计数器设置的时间2MSL过后,客户端才会进入关闭状态
什么是2MSL? https://blog.csdn.net/zxx2096/article/details/80286577
TCP的半关闭
TCP的半关闭就是当一方已经完成了数据的发送工作,并发送给了一个FIN给对方,但是它仍然希望接受来自对方的数据,直到对方发送一个FIN给它,这种情况下,它就处于TCP的半关闭状态
为什么客户端要在TIME-WAIT状态必须等待2MSL的状态?
TIME_WAIT状态也成为2MSL等待状态,在这种状态下,TCP将会等待两倍于最大生存周期MSL的时间,也成为加倍等待,这样就能够让TCP重新发送最终的ACK以避免出现丢失的情况,重新发送最终的ACK并不是因为TCP重新传递了ACK,而是因为通信的另一方重新传递了它的FIN序列号
小总结:
客户端先向服务器发送关闭请求,表示客户端不再发送请求数据了,服务器确认,但是此时客户端和服务器处于"半关闭"状态,服务器还可以接着向客户端发送消息,待服务器没有数据传输时,服务器再向客户端发送关闭请求,然后客户端确认,关闭连接
HTTP请求和响应
浏览器与服务器建立TCP连接后,就可以发起HTTP请求了,客户端按照指定的格式开始向服务器发送HTTP请求,服务器接受请求后,解析HTTP请求,返回HTTP响应给客户端
HTTP的请求报文和响应报文
HTTP的请求报文与响应报文结构一样,都是分为报文头部和报文主体部分,请求报文中的主体部分是主要发送给服务器的数据(比如post,put,delete请求),而响应报文总的主体也是返回给客户端的响应数据
请求报文和响应报文的首部内容组成:
- 请求行:包含用于请求的方法,URL,HTTP等
- 状态行:包含表明响应结果的状态码,原因短句和HTTP版本
- 首部字段:包含表示请求和响应的各种条件和属性的各类首部
- 其他:包含HTTP的RFC里未定义的首部(Cookie)等
现在相信大家已经了解的差不多了吧,综上所述,浏览器的URL请求过程就是
浏览器缓存=》DNS域名解析=》TCP连接=》HTTP请求