此次梳理的篇幅主要是涉及网络部分,包括HTTP等,对巩固本身的网络知识体系也是颇有帮助的,进一步的对性能优化而言也是帮助很大的。html
但更多的是抛砖引玉,但愿对大家有所帮助。node
感谢掘友的鼓励与支持🌹🌹🌹,往期文章都在最后梳理出来了(●'◡'●)git
接下来就以问题的形式展开梳理👇github
超文本传输协议,「HTTP 是一个在计算机世界里专门在两点之间传输文字、图片、音频、视频等超文本数据的约定和规范」。web
http1.1是目前最为主流的http协议版本,从1999年发布至今,还是主流的http协议版本。面试
keep-alive
来设置Range
来实现。二进制分帧
这是一次完全的二进制协议,头信息和数据体都是二进制,而且统称为"帧":头信息帧和数据帧。头部压缩
HTTP 1.1版本会出现 「User-Agent、Cookie、Accept、Server、Range」 等字段可能会占用几百甚至几千字节,而 Body 却常常只有几十字节,因此致使头部偏重。HTTP 2.0 使用 HPACK
算法进行压缩。多路复用
复用TCP链接,在一个链接里,客户端和浏览器均可以同时发送多个请求或回应,且不用按顺序一一对应,这样子解决了队头阻塞的问题。服务器推送
容许服务器未经请求,主动向客户端发送资源,即服务器推送。请求优先级
能够设置数据帧的优先级,让服务端先处理重要资源,优化用户体验。HTTP 1.1版本会出现 「User-Agent、Cookie、Accept、Server、Range」 等字段可能会占用几百甚至几千字节,而 Body 却常常只有几十字节,因此致使头部偏重。算法
HTTP 2.0 使用 HPACK
算法进行压缩。跨域
那咱们看看HPACK
算法吧👇数组
从上面看,咱们能够看到相似于索引表,每一个索引表对应一个值,好比索引为2对应头部中的method头部信息,这样子的话,在传输的时候,不在是传输对应的头部信息了,而是传递索引,对于以前出现过的头部信息,只须要把「索引」(好比1,2,...)传给对方便可,对方拿到索引查表就好了。浏览器
这种「传索引」的方式,能够说让请求头字段获得极大程度的精简和复用。
其次是对于整数和字符串进行「哈夫曼编码」,哈夫曼编码的原理就是先将全部出现的字符创建一张索引表,而后让出现次数多的字符对应的索引尽量短,传输的时候也是传输这样的「索引序列」,能够达到很是高的压缩率。
HTTP 1.x 中,若是想并发多个请求,必须使用多个 TCP 连接,且浏览器为了控制资源,还会对单个域名有 6-8个的TCP连接请求限制。
HTTP2中:
Stream ID
,流标识符,有了它,接收方就能从乱序的二进制帧中选择ID相同的帧,按照顺序组装成请求/响应报文。浏览器发送一个请求,服务器主动向浏览器推送与这个请求相关的资源,这样浏览器就不用发起后续请求。
相比较http/1.1的优点👇
以前是明文传输,不方便计算机解析,对于回车换行符来讲究竟是内容仍是分隔符,都须要内部状态机去识别,这样子效率低,HTTP/2采用二进制格式,所有传输01串,便于机器解码。
这样子一个报文格式就被拆分为一个个二进制帧,用「Headers帧」存放头部字段,「Data帧」存放请求体数据。这样子的话,就是一堆乱序的二进制帧,它们不存在前后关系,所以不须要排队等待,解决了HTTP队头阻塞问题。
在客户端与服务器之间,双方均可以互相发送二进制帧,这样子「双向传输的序列」,称为流
,因此HTTP/2中以流来表示一个TCP链接上进行多个数据帧的通讯,这就是多路复用概念。
那乱序的二进制帧,是如何组装成对于的报文呢?
优先级
和流量控制
等功能,这样子的话,就能够设置数据帧的优先级,让服务器处理重要资源,优化用户体验。RFC 规定 HTTP 的状态码为「三位数」,第一个数字定义了响应的类别,被分为五类:
接受的请求正在处理,信息类状态码。
当 30一、30二、303 响应状态码返回时,几乎全部的浏览器都会把 POST 改为 GET,并删除请求报文内的主体,以后请求会自动再次发送 30一、302 标准是禁止将 POST 方法改变成 GET 方法的,但实际使用时你们都会这么作
Access-Control-Allow-Methods →GET,HEAD,PUT,PATCH,POST,DELETE
复制代码
DNS 协议提供的是一种主机名到 IP 地址的转换服务,就是咱们常说的域名系统。是应用层协议,一般该协议运行在UDP协议之上,使用的是53端口号。
「咱们经过一张图来看看它的查询过程吧」👇
这张图很生动的展现了DNS在本地DNS服务器是如何查询的,「通常向本地DNS服务器发送请求是递归查询的」
本地 DNS 服务器向其余域名服务器请求的过程是迭代查询的过程👇
递归查询指的是查询请求发出后,域名服务器代为向下一级域名服务器发出请求,最后向用户返回查询的最终结果。使用递归 查询,用户只须要发出一次查询请求。
迭代查询指的是查询请求后,域名服务器返回单次查询的结果。下一级的查询由用户本身请求。使用迭代查询,用户须要发出 屡次的查询请求。
因此通常而言,「本地服务器查询是递归查询」,而「本地 DNS 服务器向其余域名服务器请求的过程是迭代查询的过程」。
缓存也很好理解,在一个请求中,当某个DNS服务器收到一个DNS回答后,它可以回答中的信息缓存在本地存储器中。「返回的资源记录中的 TTL 表明了该条记录的缓存的时间。」
它是如何实现负载均衡的呢?首先咱们得清楚DNS 是能够用于在冗余的服务器上实现负载平衡。
**缘由:**这是由于通常的大型网站使用多台服务器提供服务,所以一个域名可能会对应 多个服务器地址。
举个例子来讲👇
—>>
本地hosts文件 —>>
本地DNS解析器 —>>
本地DNS服务器 —>>
其余域名服务器请求。 接下来的过程就是迭代过程。「DNS 使用 UDP 协议做为传输层协议的主要缘由是为了不使用 TCP 协议时形成的链接时延。」
咱们知道HTTP协议采用“请求-应答”模式,当使用普通模式,即非KeepAlive模式时,每一个请求/应答客户和服务器都要新建一个链接,完成 以后当即断开链接(HTTP协议为无链接的协议);
当使用Keep-Alive模式(又称持久链接、链接重用)时,Keep-Alive功能使客户端到服 务器端的链接持续有效,当出现对服务器的后继请求时,Keep-Alive功能避免了创建或者从新创建链接。
keep-alive技术的建立目的,能在屡次HTTP以前重用同一个TCP链接,从而减小建立/关闭多个 TCP 链接的开销(包括响应时间、CPU 资源、减小拥堵等),参考以下示意图(来源:维基百科):
在HTTP/1.0协议中,默认是关闭的,须要在http头加入"Connection: Keep-Alive”,才能启用Keep-Alive;
Connection: keep-alive
复制代码
http 1.1中默认启用Keep-Alive,若是加入"Connection: close “,才关闭。
Connection: close
复制代码
目前大部分浏览器都是用http1.1协议,也就是说默认都会发起Keep-Alive的链接请求了,因此是否能完成一个完整的Keep- Alive链接就看服务器设置状况。
这个跟以前的浏览器缓存原理同样,我直接拿我以前梳理过的吧。
我在我以前的那一篇中已经详细的说过了,点这里传送门聊一聊浏览器缓存
咱们来梳理一下吧👇
强缓存两个相关字段,「Expires」,「Cache-Control」。
「强缓存分为两种状况,一种是发送HTTP请求,一种不须要发送。」
首先检查强缓存,这个阶段**不须要发送HTTP请求。**经过查找不一样的字段来进行,不一样的HTTP版本因此不一样。
Expires
即过时时间,时间是相对于服务器的时间而言的,存在于服务端返回的响应头中,在这个过时时间以前能够直接从缓存里面获取数据,无需再次请求。好比下面这样:
Expires:Mon, 29 Jun 2020 11:10:23 GMT
复制代码
表示该资源在2020年7月29日11:10:23
过时,过时时就会从新向服务器发起请求。
这个方式有一个问题:「服务器的时间和浏览器的时间可能并不一致」,因此HTTP1.1提出新的字段代替它。
HTTP1.1版本中,使用的就是该字段,这个字段采用的时间是过时时长,对应的是max-age。
Cache-Control:max-age=6000
复制代码
上面表明该资源返回后6000秒,能够直接使用缓存。
固然了,它还有其余不少关键的指令,梳理了几个重要的👇
注意点:
强缓存失效后,浏览器在请求头中携带响应的缓存Tag
来向服务器发送请求,服务器根据对应的tag,来决定是否使用缓存。
缓存分为两种,「Last-Modified」 和 「ETag」。二者各有优点,并不存在谁对谁有绝对的优点
,与上面所讲的强缓存两个Tag所不一样。
这个字段表示的是「最后修改时间」。在浏览器第一次给服务器发送请求后,服务器会在响应头中加上这个字段。
浏览器接收到后,「若是再次请求」,会在请求头中携带If-Modified-Since
字段,这个字段的值也就是服务器传来的最后修改时间。
服务器拿到请求头中的If-Modified-Since
的字段后,其实会和这个服务器中该资源的最后修改时间
对比:
ETag是服务器根据当前文件的内容,对文件生成惟一的标识,好比MD5算法,只要里面的内容有改动,这个值就会修改,服务器经过把响应头把该字段给浏览器。
浏览器接受到ETag值,会在下次请求的时候,将这个值做为「If-None-Match」这个字段的内容,发给服务器。
服务器接收到「If-None-Match」后,会跟服务器上该资源的「ETag」进行比对👇
Last-Modified
优于ETag
,Last-Modified
记录的是时间点,而Etag
须要根据文件的MD5算法生成对应的hash值。ETag
优于Last-Modified
。ETag
按照内容给资源带上标识,能准确感知资源变化,Last-Modified
在某些场景并不能准确感知变化,好比👇
最后,「若是两种方式都支持的话,服务器会优先考虑ETag
」。
接下来咱们考虑使用缓存的话,缓存的位置在哪里呢?
浏览器缓存的位置的话,能够分为四种,优先级从高到低排列分别👇
这个应用场景好比PWA,它借鉴了Web Worker思路,因为它脱离了浏览器的窗体,所以没法直接访问DOM。它能完成的功能好比:离线缓存
、消息推送
和网络代理
,其中离线缓存
就是「Service Worker Cache」。
指的是内存缓存,从效率上讲它是最快的,从存活时间来说又是最短的,当渲染进程结束后,内存缓存也就不存在了。
存储在磁盘中的缓存,从存取效率上讲是比内存缓存慢的,优点在于存储容量和存储时长。
二者对比,主要的策略👇
内容使用率高的话,文件优先进入磁盘
比较大的JS,CSS文件会直接放入磁盘,反之放入内存。
推送缓存,这算是浏览器中最后一道防线吧,它是HTTP/2
的内容。具体我也不是很清楚,有兴趣的能够去了解。
Cache-Control
, 尝鲜,看强缓存是否可用If-Modified-Since
或者If-None-Match
字段检查资源是否更新http/1.1
规定了如下请求方法(注意,都是大写):
本质上,只是语义上的区别,GET 用于获取资源,POST 用于提交资源。
想装逼请参考 https://zhuanlan.zhihu.com/p/22536382
具体差异👇
从应用场景角度来看,Get 多用于无反作用,幂等的场景,例如搜索关键字。Post 多用于反作用,不幂等的场景,例如注册。
OPTIONS 请求与 HEAD 相似,通常也是用于客户端查看服务器的性能。
这个方法会请求服务器返回该资源所支持的全部 HTTP 请求方法,该方法会用'*'来代替资源名称,向服务器发送 OPTIONS 请求,能够测试服务器功能是否正常。
JS 的 XMLHttpRequest对象进行 CORS 跨域资源共享时,对于复杂请求,就是使用 OPTIONS 方法发送嗅探请求,以判断是否有对指定资源的访问权限。
统一资源定位符的简称,Uniform Resource Locator,经常被称为网址,是因特网上标准的资源地址。
通用的格式:scheme://host[:port]/path/…/?query#anchor
名称 | 功能 |
---|---|
scheme | 访问服务器以获取资源时要使用哪一种协议,好比:http,https 和 FTP 等 |
host | HTTP 服务器的 IP 地址或者域名 |
port | HTTP 服务器的默认端口是 80,HTTPS默认端口是443,这种状况下端口号能够省略,若是使用了别的端口,必须指明。不一样的端口,你能够认为是不一样的应用程序。 |
path | 访问资源的路径 |
query-string | 发给 http 服务器的数据 |
anchor | 锚点 |
举个例子👇
https://www.baidu.com/s?tn=baidu&bar=&wd=TianTian
复制代码
这个URL中,https就是协议,www.baidu.com就是域名,默认端口是443,/s就是请求的path,tn=baidu&bar=&wd=TianTian
这个就是query
举个例子👇
每天
转换为有效的ASCII格式就是%CC%EC%CC%EC
对于每个HTTP请求而言,这些任务是会被放入一个任务队列中串行执行的,一旦队首任务请求太慢时,就会阻塞后面的请求处理,这就是HTTP队头阻塞
问题。
有什么解决办法吗👇
咱们知道对于一个域名而言,是容许分配多个长链接的,那么能够理解成增长了任务队列,也就是说不会致使一个任务阻塞了该任务队列的其余任务,在RFC规范
中规定客户端最多并发2个链接,不过实际状况就是要比这个还要多,举个例子,Chrome中是6个。
顾名思义,咱们能够在一个域名下分出多个二级域名出来,而它们最终指向的仍是同一个服务器,这样子的话就能够并发处理的任务队列更多,也更好的解决了队头阻塞的问题。
举个例子,好比TianTian.com
,能够分出不少二级域名,好比Day1.TianTian.com
,Day2.TianTian.com
,Day3.TianTian.com
,这样子就能够有效解决队头阻塞问题。
大概遇到的状况就分为「定长数据」 与 「不定长数据」的处理吧。
对于定长的数据包而言,发送端在发送数据的过程当中,须要设置Content-Length
,来指明发送数据的长度。
固然了若是采用了Gzip压缩的话,Content-Length设置的就是压缩后的传输长度。
咱们还须要知道的是👇
Content-Length
,如果非Keep-alive,跟前面状况同样,Content-Length无关紧要。那怎么来设置Content-Length
举个例子来看看👇
const server = require('http').createServer();
server.on('request', (req, res) => {
if(req.url === '/index') {
// 设置数据类型
res.setHeader('Content-Type', 'text/plain');
res.setHeader('Content-Length', 10);
res.write("你好,使用的是Content-Length设置传输数据形式");
}
})
server.listen(3000, () => {
console.log("成功启动--TinaTian");
})
复制代码
如今采用最多的就是HTTP/1.1版本,来完成传输数据,在保存Keep-alive状态下,当数据是不定长的时候,咱们须要设置新的头部字段👇
Transfer-Encoding: chunked
复制代码
经过chunked机制,能够完成对不定长数据的处理,固然了,你须要知道的是
Transfer-Encoding
,优先采用Transfer-Encoding里面的方法来找到对应的长度。那咱们来模拟一下吧👇
const server = require('http').createServer();
server.on('request', (req, res) => {
if(req.url === '/index') {
// 设置数据类型
res.setHeader('Content-Type', 'text/html; charset=utf8');
res.setHeader('Content-Length', 10);
res.setHeader('Transfer-Encoding', 'chunked');
res.write("你好,使用的是Transfer-Encoding设置传输数据形式");
setTimeout(() => {
res.write("第一次传输数据给您<br/>");
}, 1000);
res.write("骚等一下");
setTimeout(() => {
res.write("第一次传输数据给您");
res.end()
}, 3000);
}
})
server.listen(3000, () => {
console.log("成功启动--TinaTian");
})
复制代码
上面使用的是nodejs中http模块,有兴趣的小伙伴能够去试一试,以上就是HTTP对「定长数据」和「不定长数据」传输过程当中的处理手段。
HTTPS 要比 HTTPS 多了 secure 安全性这个概念,实际上, HTTPS 并非一个新的应用层协议,它其实就是 HTTP + TLS/SSL 协议组合而成,而安全性的保证正是 SSL/TLS 所作的工做。
「SSL」
安全套接层(Secure Sockets Layer)
「TLS」
(传输层安全,Transport Layer Security)
如今主流的版本是 TLS/1.2, 以前的 TLS1.0、TLS1.1 都被认为是不安全的,在不久的未来会被彻底淘汰。
「HTTPS 就是身披了一层 SSL 的 HTTP」。
那么区别有哪些呢👇
我以为记住如下两点HTTPS主要做用就行👇
上一节来看,咱们能够把HTTPS理解成「HTTPS = HTTP + SSL/TLS」
TLS/SSL 的功能实现主要依赖于三类基本算法:
散列函数
、对称加密
和非对称加密
,其利用非对称加密实现身份认证和密钥协商,对称加密算法采用协商的密钥对数据加密,基于散列函数验证信息的完整性。
加密和解密用同一个秘钥的加密方式叫作对称加密。Client客户端和Server端共用一套密钥,这样子的加密过程彷佛很让人理解,可是随之会产生一些问题。
「问题一:」 WWW万维网有许许多多的客户端,不可能都用秘钥A进行信息加密,这样子很不合理,因此解决办法就是使用一个客户端使用一个密钥进行加密。
「问题二:「既然不一样的客户端使用不一样的密钥,那么」对称加密的密钥如何传输?」 那么解决的办法只能是「一端生成一个秘钥,而后经过HTTP传输给另外一端」,那么这样子又会产生新的问题。
「问题三:」 这个传输密钥的过程,又如何保证加密?「若是被中间人拦截,密钥也会被获取,」 那么你会说对密钥再进行加密,那又怎么保存对密钥加密的过程,是加密的过程?
到这里,咱们彷佛想明白了,使用对称加密的方式,行不通,因此咱们须要采用非对称加密👇
经过上面的分析,对称加密的方式行不通,那么咱们来梳理一下非对称加密。采用的算法是RSA,因此在一些文章中也会看见「传统RSA握手」,基于如今TLS主流版本是1.2,因此接下来梳理的是「TLS/1.2握手过程」。
非对称加密中,咱们须要明确的点是👇
梳理起来,能够把「TLS 1.2 握手过程」分为主要的五步👇
图片内容来自浪里行舟
Client发起一个HTTPS请求,链接443端口。这个过程能够理解成是「请求公钥的过程」。
Server端收到请求后,经过第三方机构私钥加密,会把数字证书(也能够认为是公钥证书)发送给Client。
在安全拿到「服务器公钥」后,客户端Client随机生成一个「对称密钥」,使用「服务器公钥」(证书的公钥)加密这个「对称密钥」,发送给Server(服务器)。
Server(服务器)经过本身的私钥,对信息解密,至此获得了「对称密钥」,此时二者都拥有了相同的「对称密钥」。
接下来,就能够经过该对称密钥对传输的信息加密/解密啦,从上面图举个例子👇
接下来考虑一个问题,「若是公钥被中间人拿到纂改怎么办呢?」
如下图片来自leocoder
「客户端可能拿到的公钥是假的,解决办法是什么呢?」
客户端没法识别传回公钥是中间人的,仍是服务器的,这是问题的根本,咱们是否是能够经过某种规范可让客户端和服务器都遵循某种约定呢?那就是经过「第三方认证的方式」
在HTTPS中,经过 「证书」 + 「数字签名」来解决这个问题。
这里惟一不一样的是,假设对网站信息加密的算法是MD5,经过MD5加密后,「而后经过第三方机构的私钥再次对其加密,生成数字签名」。
这样子的话,数字证书包含有两个特别重要的信息👉「某网站公钥+数字签名」
咱们再次假设中间人截取到服务器的公钥后,去替换成本身的公钥,由于有数字签名的存在,这样子客户端验证发现数字签名不匹配,这样子就防止中间人替换公钥的问题。
那么客户端是如何去对比二者数字签名的呢?
数字签名:将网站的信息,经过特定的算法加密,好比MD5,加密以后,再经过服务器的私钥进行加密,造成「加密后的数字签名」。
第三方认证机构是一个公开的平台,中间人能够去获取。
若是没有数字签名的话,这样子能够就会有下面状况👇
从上面咱们知道,若是「只是对网站信息进行第三方机构私钥加密」的话,仍是会受到欺骗。
由于没有认证,因此中间人也向第三方认证机构进行申请,而后拦截后把全部的信息都替换成本身的,客户端仍然能够解密,而且没法判断这是服务器的仍是中间人的,最后形成数据泄露。
一共有两种方法来恢复断开的 SSL 链接,一种是使用 session ID,一种是 session ticket。
使用 session ID 的方式,每一次的会话都有一个编号,当对话中断后,下一次从新链接时,只要客户端给出这个编号,服务器若是有这个编号的记录,那么双方就能够继续使用之前的秘钥,而不用从新生成一把。目前全部的浏览器都支持这一种方法。可是这种方法有一个缺点是,session ID 只可以存在一台服务器上,若是咱们的请求经过负载平衡被转移到了其余的服务器上,那么就没法恢复对话。
另外一种方式是 session ticket 的方式,session ticket 是服务器在上一次对话中发送给客户的,这个 ticket 是加密的,只有服务器可以解密,里面包含了本次会话的信息,好比对话秘钥和加密方法等。这样无论咱们的请求是否转移到其余的服务器上,当服务器将 ticket 解密之后,就可以获取上次对话的信息,就不用从新生成对话秘钥了。
短轮询的基本思路:
优缺点👇
优势是比较简单,易于理解。
缺点是这种方式因为须要不断的创建 http 链接,严重浪费了服务器端和客户端的资源。当用户增长时,服务器端的压力就会变大,这是很不合理的。
长轮询的基本思路:
首先由客户端向服务器发起请求,当服务器收到客户端发来的请求后,服务器端不会直接进行响应,而是先将 这个请求挂起,而后判断服务器端数据是否有更新。
若是有更新,则进行响应,若是一直没有数据,则到达必定的时间限制才返回。客户端 JavaScript 响应处理函数会在处理完服务器返回的信息后,再次发出请求,从新创建链接。
优缺点👇
长轮询和短轮询比起来,它的优势是「明显减小了不少没必要要的 http 请求次数」,相比之下节约了资源。
长轮询的缺点在于,链接挂起也会致使资源的浪费。
咱们常说的代理也就是指正向代理,正向代理的过程,它隐藏了真实的请求客户端,服务端不知道真实的客户端是谁,客户端请求的服务都被代理服务器代替来请求。
这种代理模式下,它隐藏了真实的服务端,当咱们向一个网站发起请求的时候,背后可能有成千上万台服务器为咱们服务,具体是哪一台,咱们不清楚,咱们只须要知道反向代理服务器是谁就行,并且反向代理服务器会帮咱们把请求转发到真实的服务器那里去,通常而言反向代理服务器通常用来实现负载平衡。
一种是使用反向代理的方式,用户的请求都发送到反向代理服务上,而后由反向代理服务器来转发请求到真实的服务器上,以此来实现集群的负载平衡。
另外一种是 DNS 的方式,DNS 能够用于在冗余的服务器上实现负载平衡。由于如今通常的大型网站使用多台服务器提供服务,所以一个域名可能会对应多个服务器地址。当用户向网站域名请求的时候,DNS 服务器返回这个域名所对应的服务器 IP 地址的集合,但在每一个回答中,会循环这些 IP 地址的顺序,用户通常会选择排在前面的地址发送请求。以此将用户的请求均衡的分配到各个不一样的服务器上,这样来实现负载均衡。这种方式有一个缺点就是,因为 DNS 服务器中存在缓存,因此有可能一个服务器出现故障后,域名解析仍然返回的是那个 IP 地址,就会形成访问的问题。
若是你以为这篇内容对你挺有有帮助的话: