在客户端输入 url,到显示页面经历了什么?
总结:
1. 在浏览器地址栏中输入 url 按下回车键;
2. 浏览器对 url 进行解析,并查找是否对当前的 url 有缓存 且是否过期;
- 强缓存
- 协商缓存
3. 不存在缓存,就进行 DNS 解析,拿到对应的 ip 地址;
- DNS 存在缓存,可以使用 <link> 标签对 DNS 预获取,对 DNS 进行优化
4. 根据 ip地址 和 服务器建立连接(三次握手);
- 第三次握手是为了 通知服务器接收到了对方的数据,并开始数据的传输,让服务器知道连接是否顺利
5. 建立连接后,浏览器发起数据的请求;
6. 服务器接收来自 浏览器的请求,并做出响应;(存储缓存)
7. 浏览器接收请求的数据,并渲染页面;
8. 浏览器发起关闭 TCP 连接请求,和 服务器进行 四次挥手;
1. URL 解析
1,了解 url 的组成部分:
http://user:pass@www.baidu.com:80/index.html?name=zyf&age=12#video
http
: 协议名,有 http,https, ftp 等等协议;
user:pass
:登录信息,使用比较少;
www.baidu.com
:域名,主机号(通过DNS解析,可以得到ip地址);
80
: 端口号,范围: 0 ~ 65535
- http 默认为 80;
- https 默认为 443;
index.html
: 资源路径;
?name=zyf&age=12
: 查询字符串,传递的参数;
#video
:哈希值,如 vue 和 react 路由;
2,编码
如果在 url 中遇到 空格,中文,http链接;需要进行编码,编码成我们看不出来的字符串;
对整个 url 进行编码: 针对空格 和 字符串;
encodeURI (加码) / decodeURI (解码)
例子:
let url = `http://user:pass @www.baidu.com:80/index.html?name=穆土&age=12&$url=https://www.bilibili.com/#vide`
console.log(encodeURI(url)) // => http://user:pass%20@www.baidu.com:80/index.html?name=%E7%A9%86%E5%9C%9F&age=12&$url=https://www.bilibili.com/#vide
把 空格 和 字符串编码成 一串字符串了。
对参数进行编码: 针对参数中的 url
encodeURIComponent / decodeURIComponent
例子:
let url = 'http://user:pass@www.baidu.com:80/index.html?name=穆土&age=12&http://www.blibli.com#video'
/* 一般不是这么写的,因为这样会编码到我们主要的 url
encodeURIComponent(url) =>
http%3A%2F%2Fuser%3Apass%20%40www.baidu.com%3A80%2Findex.html%3Fname%3D%E7%A9%86%E5%9C%9F%26age%3D12%26http%3A%2F%2Fwww.blibli.com%23video
而是这样 */
// 直接写在 url 中,使用 模板字符串
let url = `http://user:pass@www.baidu.com:80/index.html?name=穆土&age=12&$url=${encodeURIComponent("https://www.bilibili.com/")}#vide`
// => http://user:pass@www.baidu.com:80/index.html?name=穆土&age=12&$url=https%3A%2F%2Fwww.bilibili.com%2F#vide
2. 缓存检查
概念:
产品性能优化的重点,他可以分为 强缓存 和 协商缓存 :
html 页面一般不设置强缓存。
缓存机制 针对于 静态资源文件,而且 不是经常更新的;我们的 接口就不是;
检测缓存的顺序:
1,先检测是否存在 强缓存;
2,有,并且还没有失效,走强缓存;
3,如果没有 或 失效,检测是否有 协商缓存;
4,有,就使用 协商缓存;
5,没有,获取最新的数据;
缓存的位置:
- Memory Cache: 内存缓存(内存条;
- Disk Cache: 硬盘缓存(C盘;
打开网页: 查找的是 硬盘中是否有缓存,没有则发送请求获取数据 。
普通刷新(F5): 因为页面还没有关闭,因此会在 内存中检查是否存在缓存;然后在是 硬盘缓存 。
强制刷新(Ctrl + F5):浏览器不使用缓存,直接发送请求,获取数据 。
强缓存 和 协商缓存 是 后台设置的,浏览器会自动携带信息,前端不需要自己写代码!
1. 强缓存
概念:
浏览器对 强缓存 的处理,根据第一次请求资源时,返回的响应头来确定;
Expires
: 缓存过期事件,用来指定资源到期的事件(HTTP/1.0);Cache-Control: cache-control: max-age = 123231
是秒为单位,第一次拿到资源后,123231 秒后失效,就发起请求( HTTP/1.1 );- 俩者同时存在的话,
Cache-Control
优先级高于Expires
强缓存的问题:
如果浏览器缓存存在,但服务器的资源更新了,那我们获取到的数据不就是旧的吗?
解决手段:
服务器更新完资源后,可以重命名 更新文件, 和 之前的名称不一样了,html 页面引入的资源名称 和 缓存中的文件名称不一样,就会发起请求,获取资源;
1,如果是使用 webpack 打包的资源,他会给 资源名称添加一个 哈希值,让每次更新资源的名称不一致 。
index.dfs123.js
index.aad132.js
2,如果没有使用 webpack,我们可以在资源后部添加一个 时间戳)
<a href="www.baidu.com/index.css?date=时间戳"></a>
**这就是 html 页面,不走缓存的原因;**有资源更新了,重命名修改的资源,然后代替之前的 资源链接;
强制缓存图:
2. 协商缓存
概念:
协商缓存就是 强制缓存失效后,浏览器携带 缓存标识 向 服务器发起请求,由 服务器 根据 缓存标识,决定是否使用缓存的过程。(由服务器校验)
- Last-Modified:HTTP/1.0(只精确到秒
- ETag: HTTP / 1.1
过程:
第一次向服务器发送请求:
协商缓存没有
向服务器发送请求(没有携带任何 缓存标识)
+ 服务器收到 请求,准备返回内容;
+ Last-Modified: 资源最后更新的时间;
+ ETag: 记录的是一个标识(根据资源文件更新生成的,每一个 资源更新 都会重新生成一个 ETag)
-------
客户端接收到数据后进行渲染,
并把数据 和 缓存标识 存储到本地;
浏览器再次发送请求:
携带
If-Modified-Since = Last-Modified
If-None-Match = ETag
发送到 服务器;
服务器根据 缓存标识 判断数据是否 过期了。
图:
数据缓存(无关)
数据缓存,我们在存储数据时,首先存储我们所请求到的数据,再添加一个 当前时间;
然后在获取数据时,到本地缓存中拿,然后利用 当前时间 减 存储数据时的时间,但超过设定的时间,就重新发送请求 。
3. DNS 解析
概念:
因为 服务器的 ip 地址是一串数字,比较不好记忆;使用 域名就比较好记忆,域名有逻辑性,比较好记忆,比如 百度的域名 是吧!https://www.baidu.com/
,但通过域名是找不到 服务器的,所以就使用 DNS 解析,把 域名 解析成 id ,然后去寻找 服务器 。
DNS 解析方法:
1,递归解析
2,迭代查询
DNS 缓存:
DNS 存在缓存;之前解析过,再次访问时,就不用进行 DNS 解析了 。
服务器问题:
不同资源存放到不同的服务器上,web服务器,数据服务器,图片视频音乐服务器,这样 可以减少服务器的压力 和 根据不同功能购买不同性能的服务器 ,可以减少成本 ,提供 http 并发性(一个浏览器可以一起发送 4 个同源的 http请求,5个服务器,我们就可以 一下子发送 20个 http 请求 。
DNS 优化:
-
减少 DNS 的请求次数,就意味着把资源存放到同一个服务器上,就能减少很多 DNS 的请求;但是这个并不好,因为万一服务器坏了 或是 资源都放在一个服务器上,多人访问时,服务器的压力过大。实际上,公司的 服务器都有多个服务器,存放不同的资源。
-
DNS 预获取
就是在 html 中,利用
<link>
标签访问 我们网站所用到的 服务器,这样在 html 页面渲染时,同时访问我们的服务器,提前 进行 DNS 解析,之后访问服务器时,可以 到 DNS 缓存中拿取;这样能提高 请求的速度;因为写在<body>
上面,页面还没有实际的 http 请求获取资源,我们就执行到了<link>
了,这样就可以在 获取资源的请求 之前,做到 DNS 预获取了 。
4. TCP 的三次握手
seq序列:标记 发送方 向 目的方 的字节流;
ack确认序号: seq + 1;ACK 为 1 时,确认序号才有效;
标记位:
- ACK: 确认序号有效;
- RST: 重置连接;
- SYN: 发起一个新连接;
- FIN: 释放一个连接;
客户端: SYN = 1(发起新连接), seq = x(字节流)
服务器: SYN = 1(发起新连接), seq = y, ACK = 1(确认有效), ack = x + 1(确认序号,收到刚才的 x + 1 返回)
客户端: ACK = 1, seq = x + 1, ack = y + 1
形容式说法:
浏览器: 服务器你在吗?
服务器: 在啊!
浏览器: 好,连接成功!那我开始发送数据了!
为什么要3次呢,不能 2次吗?
比如说是 2 次握手:
-
我们客户端发送连接的请求;
-
服务器接收到请求后,这样就确立了连接;开始发送数据给 客户端;
-
但在发送的过程中,如果出现错误了,数据发送不到 客户端那里,
时间一长,客户端接收不到数据,客户端就关闭了请求的接口;再次重新发送 连接请求,但是 服务器不知道 数据掉失了,服务器会想,咦,刚刚的请求不是发给你了吗? 怎么又请求一次呢?
所以如果没有第三次握手通知 服务器 连接完成了,服务器 是不知道这个状态的 。
所以我们需要“第三次握手”来确认这个过程,让客户端和服务器端能够及时地察觉到因为网络等一些问题导致的连接创建失败,这样服务器端的端口就可以关闭了不用一直等待。
可以这样形容:
半连接队列:
服务器在第一次接收到 客户端的 SYN 后!就会处于SYN_RCVD 状态,此时 双方还没有建立连接,服务器会把这样的状态的请求,存放到 一个队列中;这个队列就称为 “ 半连接队列 ” 。
还有一个 全连接队列,就是完成了 第三次握手的请求队列,如果队列满了就会出现 丢包的现象 。
服务器发送 SYN-ACK 包后,如果 客户端没有返会 确认包,服务器会进行首次重传,等待一段时间后,还没有接收到 确认包,会进行第二次重传。如果超出了 系统规定的重传次数, 服务器会将 当前的连接信息,从 半连接队列 中删除 。
三次握手过程中,可以发送数据吗?
答: 第三次我手是可以的,但 1,2次握手不可以携带数据;
为什么?
答: 如果第一次我手时,可以携带数据的话,如果有人恶意攻击服务器,那服务器在 第一次握手中 SYN 报文中存在大量的数据, 服务器要会时间处理这些没有作用的数据,会浪费很多时间 和 内存;
那第三次握手时,客户端 和 服务器 已经处于 半连接的状态了,知道 服务器可以正常的发送 和 接收数据了,所以发送数据是可以的。
SYN 攻击是什么?
SYN 攻击就是在 短时间内 伪造大量不存在的 IP 地址,并向 服务器不断的发送 SYN 包; 服务器会对每个 SYN 请求 回复一个 确认包,然后等待 对方回应,(这时候,会将这些 半连接请求 存储到 半连接队列中,但是 队列的存储数量是有限的,当满了之后;正常的 SYN 请求,会因为 队列满了,导致被 丢弃了,从而导致网络的堵塞 。
5. 四次挥手
概念:
四次挥手即TCP连接的释放(解除)。连接的释放必须是一方主动释放,另一方被动释放。
客户端发送断开信号, 服务器接收到立即返回一个回馈,告诉 客户端 收到信号,稍后传递确定断开的信息; 客户端接收到第二个断开信息,就断开了连接 。
图解:
长连接:保证 TCP 通道建立后,可以不用关闭;HTTP1.1 规范;
参考博客:
百度文章