GET
的重定向,但是不能确保 POST
会重定向为 POST
GET
的重定向307
和 302
一样,除了不允许 POST
到 GET
的重定向HTTP
协议始于三十年前蒂姆·伯纳斯 - 李的一篇论文HTTP/0.9
是个简单的文本协议,只能获取文本资源;HTTP/1.0
确立了大部分现在使用的技术,但它不是正式标准;HTTP/1.1
是目前互联网上使用最广泛的协议,功能也非常完善;HTTP/2
基于 Google 的 SPDY
协议,注重性能改善,但还未普及;HTTP/3
基于 Google 的 QUIC
协议,是将来的发展方向HTTP
协议传输;User Agent
;Apache
和 Nginx
;CDN
位于浏览器和服务器之间,主要起到缓存加速的作用;User Agent
,是自动访问网络资源的程序。TCP/IP
是网络世界最常用的协议,HTTP
通常运行在 TCP/IP
提供的可靠传输基础上DNS
域名是 IP
地址的等价替代,需要用域名解析实现到 IP
地址的映射;URI
是用来标记互联网上资源的一个名字,由“协议名 + 主机名 + 路径”构成,俗称 URL;HTTPS
相当于“HTTP+SSL/TLS+TCP/IP
”,为 HTTP
套了一个安全的外壳;HTTP
传输过程中的“中转站”,可以实现缓存加速、负载均衡等功能
TCP/IP
里无对应;TCP/IP
的链接层;TCP/IP
的网际层;TCP/IP
的传输层;TCP/IP
的应用层总结
TCP/IP
分为四层,核心是二层的 IP
和三层的 TCP
,HTTP
在第四层;OSI
分为七层,基本对应 TCP/IP
,TCP
在第四层,HTTP
在第七层;OSI
可以映射到 TCP/IP
,但这期间一、五、六层消失了;OSI
模型,用四层、七层等术语;HTTP
利用 TCP/IP
协议栈逐层打包再拆包,实现了数据传输,但下面的细节并不可见有一个辨别四层和七层比较好的(但不是绝对的)小窍门,“两个凡是”:凡是由操作系统负责处理的就是四层或四层以下,否则,凡是需要由应用程序(也就是你自己写代码)负责处理的就是七层
HTTP 协议的请求报文和响应报文的结构基本相同,由三大部分组成
key-value
形式更详细地说明报文;这其中前两部分起始行和头部字段经常又合称为“请求头”或“响应头”,消息正文又称为“实体”,但与“header”对应,很多时候就直接称为“body”。
一个完整的 HTTP 报文就像是下图的这个样子,注意在 header 和 body 之间有一个“空行”
URI
是用来唯一标记服务器上资源的一个字符串,通常也称为 URL;URI
通常由 scheme
、host:port
、path
和 query
四个部分组成,有的可以省略;scheme
叫“方案名”或者“协议名”,表示资源应该使用哪种协议来访问;host:port
”表示资源所在的主机名和端口号;path
标记资源所在的位置;query
表示对资源附加的额外要求;URI
里对“@&/
”等特殊字符和汉字必须要做编码,否则服务器收到 HTTP
报文后会无法正确处理1. 数据类型与编码
text/html
了,表示超文本文档,此外还有纯文本 text/plain
、样式表 text/css
等。image
:即图像文件,有 image/gif
、image/jpeg
、image/png
等。audio/video
:音频和视频数据,例如 audio/mpeg
、video/mp4
等。application
:数据格式不固定,可能是文本也可能是二进制,必须由上层应用程序来解释。常见的有 application/json
,application/javascript
、application/pdf
等,另外,如果实在是不知道数据是什么类型,像刚才说的“黑盒”,就会是 application/octet-stream
,即不透明的二进制数据但仅有
MIME type
还不够,因为HTTP
在传输时为了节约带宽,有时候还会压缩数据,为了不要让浏览器继续“猜”,还需要有一个“Encoding type”,告诉数据是用的什么编码格式,这样对方才能正确解压缩,还原出原始的数据。
比起 MIME type
来说,Encoding type
就少了很多,常用的只有下面三种
gzip
:GNU zip
压缩格式,也是互联网上最流行的压缩格式;deflate
:zlib
(deflate
)压缩格式,流行程度仅次于 gzip
;br
:一种专门为 HTTP
优化的新压缩算法(Brotli
)2. 数据类型使用的头字段
有了 MIME type
和 Encoding type
,无论是浏览器还是服务器就都可以轻松识别出 body
的类型,也就能够正确处理数据了。
HTTP
协议为此定义了两个 Accept
请求头字段和两个 Content
实体头字段,用于客户端和服务器进行“内容协商”。也就是说,客户端用 Accept
头告诉服务器希望接收什么样的数据,而服务器用 Content
头告诉客户端实际发送了什么样的数据
Accept
字段标记的是客户端可理解的MIME
type,可以用“,”做分隔符列出多个类型,让服务器有更多的选择余地,例如下面的这个头:
Accept: text/html,application/xml,image/webp,image/png
这就是告诉服务器:“我能够看懂 HTML、XML 的文本,还有 webp
和 png
的图片,请给我这四类格式的数据”。
相应的,服务器会在响应报文里用头字段Content-Type
告诉实体数据的真实类型:
Content-Type: text/html
Content-Type: image/png
这样浏览器看到报文里的类型是“text/html”就知道是 HTML 文件,会调用排版引擎渲染出页面,看到“image/png”就知道是一个 PNG 文件,就会在页面上显示出图像。
Accept-Encoding字段标记的是客户端支持的压缩格式,例如上面说的 gzip、deflate 等,同样也可以用“,”列出多个,服务器可以选择其中一种来压缩数据,实际使用的压缩格式放在响应头字段Content-Encoding
里
Accept-Encoding: gzip, deflate, br
Content-Encoding: gzip
不过这两个字段是可以省略的,如果请求报文里没有 Accept-Encoding
字段,就表示客户端不支持压缩数据;如果响应报文里没有 Content-Encoding
字段,就表示响应数据没有被压缩
3. 语言类型使用的头字段
同样的,HTTP 协议也使用 Accept
请求头字段和 Content
实体头字段,用于客户端和服务器就语言与编码进行“内容协商”。
Accept-Language
字段标记了客户端可理解的自然语言,也允许用“,”做分隔符列出多个类型,例如:
Accept-Language: zh-CN, zh, en
这个请求头会告诉服务器:“最好给我 zh-CN
的汉语文字,如果没有就用其他的汉语方言,如果还没有就给英文”。
相应的,服务器应该在响应报文里用头字段Content-Language
告诉客户端实体数据使用的实际语言类型
Content-Language: zh-CN
HTTP
里使用的请求头字段是Accept-Charset
,但响应头里却没有对应的 Content-Charset
,而是在Content-Type
字段的数据类型后面用“charset=xxx
”来表示,这点需要特别注意。GBK
或 UTF-8
的字符集,然后服务器返回的是 UTF-8
编码,就是下面这样Accept-Charset: gbk, utf-8
Content-Type: text/html; charset=utf-8
不过现在的浏览器都支持多种字符集,通常不会发送 Accept-Charset
,而服务器也不会发送 Content-Language
,因为使用的语言完全可以由字符集推断出来,所以在请求头里一般只会有 Accept-Language
字段,响应头里只会有 Content-Type
字段
4. 内容协商的质量值
在 HTTP 协议里用 Accept
、Accept-Encoding
、Accept-Language
等请求头字段进行内容协商的时候,还可以用一种特殊的“q”参数表示权重来设定优先级,这里的“q”是“quality factor”的意思。
权重的最大值是 1,最小值是 0.01,默认值是 1,如果值是 0 就表示拒绝。具体的形式是在数据类型或语言代码后面加一个“;”,然后是“q=value”。
这里要提醒的是“;”的用法,在大多数编程语言里“;”的断句语气要强于“,”,而在 HTTP 的内容协商里却恰好反了过来,“;”的意义是小于“,”的。
例如下面的 Accept 字段:
Accept: text/html,application/xml;q=0.9,*/*;q=0.8
它表示浏览器最希望使用的是 HTML 文件,权重是 1,其次是 XML 文件,权重是 0.9,最后是任意数据类型,权重是0.8。服务器收到请求头后,就会计算权重,再根据自己的实际情况优先输出 HTML 或者 XML
5. 内容协商的结果
内容协商的过程是不透明的,每个 Web 服务器使用的算法都不一样。但有的时候,服务器会在响应头里多加一个Vary字段,记录服务器在内容协商时参考的请求头字段,给出一点信息,例如:
Vary: Accept-Encoding,User-Agent,Accept
这个 Vary
字段表示服务器依据了 Accept-Encoding
、User-Agent
和 Accept
这三个头字段,然后决定了发回的响应报文。
Vary
字段可以认为是响应报文的一个特殊的“版本标记”。每当 Accept
等请求头变化时,Vary
也会随着响应报文一起变化。也就是说,同一个 URI
可能会有多个不同的“版本”,主要用在传输链路中间的代理服务器实现缓存服务,这个之后讲“HTTP 缓存”时还会再提到
6. 小结
MIME type
,相关的头字段是 Accept
和 Content-Type
;Accept-Encoding
和 Content-Encoding
;Accept-Language
和 Content-Language
;Accept-Charset
和 Content-Type;Accept
等头字段与服务器进行“内容协商”,要求服务器返回最合适的数据; Accept
等头字段可以用“,”顺序列出多个可能的选项,还可以用“;q=
”参数来精确指定权重超文本传输协议,HTTP 是一个在计算机世界里专门在两点之间传输文字、图片、音频、视频等超文本数据的约定和规范。
TCP
连接,当某个请求时间过长时,其他的请求只能处于阻塞状态,这就是队头阻塞问题。http 无状态无连接
http
协议对于事务处理没有记忆能力url
请求没有上下文关系http协议无状态中的 状态 到底指的是什么?!
cookie
和session
机制,现在的网络请求其实是有状态的http
协议下,服务器也一定会保留你每次网络请求对数据的修改,但这跟保留每次访问的数据是不一样的,保留的只是会话产生的结果,而没有保留会话http/1.1
规定了以下请求方法(注意,都是大写):
从应用场景角度来看,Get 多用于无副作用,幂等的场景,例如搜索关键字。Post 多用于副作用,不幂等的场景,例如注册。
options 方法有什么用
本质上,只是语义上的区别,GET 用于获取资源,POST 用于提交资源。
具体差别👇
什么是队头阻塞?
对于每一个HTTP请求而言,这些任务是会被放入一个任务队列中串行执行的,一旦队首任务请求太慢时,就会阻塞后面的请求处理,这就是HTTP队头阻塞
问题。
有什么解决办法吗👇
并发连接
我们知道对于一个域名而言,是允许分配多个长连接的,那么可以理解成增加了任务队列,也就是说不会导致一个任务阻塞了该任务队列的其他任务,在
RFC规范
中规定客户端最多并发2个连接,不过实际情况就是要比这个还要多,举个例子,Chrome中是6个。
域名分片
TianTian.com
,可以分出很多二级域名,比如Day1.TianTian.com
,Day2.TianTian.com
,Day3.TianTian.com
,这样子就可以有效解决队头阻塞问题。大概遇到的情况就分为定长数据 与 不定长数据的处理吧。
定长数据
对于定长的数据包而言,发送端在发送数据的过程中,需要设置Content-Length
,来指明发送数据的长度。
当然了如果采用了Gzip压缩的话,Content-Length设置的就是压缩后的传输长度。
我们还需要知道的是👇
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("第一次传输数据给您
");
}, 1000);
res.write("骚等一下");
setTimeout(() => {
res.write("第一次传输数据给您");
res.end()
}, 3000);
}
})
server.listen(3000, () => {
console.log("成功启动--TinaTian");
})
上面使用的是nodejs中
http
模块,有兴趣的小伙伴可以去试一试,以上就是HTTP对定长数据和不定长数据传输过程中的处理手段。
session
: 是一个抽象概念,开发者为了实现中断和继续等操作,将 user agent
和 server
之间一对一的交互,抽象为“会话”,进而衍生出“会话状态”,也就是 session
的概念cookie
:它是一个世纪存在的东西,http
协议中定义在 header
中的字段,可以认为是 session
的一种后端无状态实现现在我们常说的
session
,是为了绕开cookie
的各种限制,通常借助cookie
本身和后端存储实现的,一种更高级的会话状态实现
session
的常见实现要借助cookie
来发送 sessionID
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理解成HTTPS = HTTP + SSL/TLS
TLS/SSL 的功能实现主要依赖于三类基本算法:
散列函数
、对称加密
和非对称加密
,其利用非对称加密实现身份认证和密钥协商,对称加密算法采用协商的密钥对数据加密,基于散列函数验证信息的完整性。
1. 对称加密
加密和解密用同一个秘钥的加密方式叫做对称加密。Client客户端和Server端共用一套密钥,这样子的加密过程似乎很让人理解,但是随之会产生一些问题。
问题一: WWW万维网有许许多多的客户端,不可能都用秘钥A进行信息加密,这样子很不合理,所以解决办法就是使用一个客户端使用一个密钥进行加密。
问题二:既然不同的客户端使用不同的密钥,那么对称加密的密钥如何传输? 那么解决的办法只能是一端生成一个秘钥,然后通过HTTP传输给另一端,那么这样子又会产生新的问题。
问题三: 这个传输密钥的过程,又如何保证加密?如果被中间人拦截,密钥也会被获取, 那么你会说对密钥再进行加密,那又怎么保存对密钥加密的过程,是加密的过程?
到这里,我们似乎想明白了,使用对称加密的方式,行不通,所以我们需要采用非对称加密👇
2. 非对称加密
通过上面的分析,对称加密的方式行不通,那么我们来梳理一下非对称加密。采用的算法是RSA,所以在一些文章中也会看见传统RSA握手,基于现在TLS主流版本是1.2,所以接下来梳理的是TLS/1.2握手过程。
非对称加密中,我们需要明确的点是👇
3. 主要工作流程
梳理起来,可以把TLS 1.2 握手过程分为主要的五步👇
接下来,就可以通过该对称密钥对传输的信息加密/解密啦,从上面图举个例子👇
接下来考虑一个问题,如果公钥被中间人拿到纂改怎么办呢?
客户端可能拿到的公钥是假的,解决办法是什么呢?
3. 第三方认证
客户端无法识别传回公钥是中间人的,还是服务器的,这是问题的根本,我们是不是可以通过某种规范可以让客户端和服务器都遵循某种约定呢?那就是通过第三方认证的方式
在HTTPS中,通过 证书 + 数字签名来解决这个问题。
这里唯一不同的是,假设对网站信息加密的算法是MD5,通过MD5加密后,然后通过第三方机构的私钥再次对其加密,生成数字签名。
这样子的话,数字证书包含有两个特别重要的信息👉某网站公钥+数字签名
我们再次假设中间人截取到服务器的公钥后,去替换成自己的公钥,因为有数字签名的存在,这样子客户端验证发现数字签名不匹配,这样子就防止中间人替换公钥的问题。
那么客户端是如何去对比两者数字签名的呢?
4. 数字签名作用
数字签名:将网站的信息,通过特定的算法加密,比如MD5,加密之后,再通过服务器的私钥进行加密,形成加密后的数字签名。
第三方认证机构是一个公开的平台,中间人可以去获取。
如果没有数字签名的话,这样子可以就会有下面情况👇
从上面我们知道,如果只是对网站信息进行第三方机构私钥加密的话,还是会受到欺骗。
因为没有认证,所以中间人也向第三方认证机构进行申请,然后拦截后把所有的信息都替换成自己的,客户端仍然可以解密,并且无法判断这是服务器的还是中间人的,最后造成数据泄露。
5. 总结
一共有两种方法来恢复断开的 SSL 连接,一种是使用 session ID,一种是 session ticket。
通过session ID
使用 session ID 的方式,每一次的会话都有一个编号,当对话中断后,下一次重新连接时,只要客户端给出这个编号,服务器如果有这个编号的记录,那么双方就可以继续使用以前的秘钥,而不用重新生成一把。目前所有的浏览器都支持这一种方法。但是这种方法有一个缺点是,session ID 只能够存在一台服务器上,如果我们的请求通过负载平衡被转移到了其他的服务器上,那么就无法恢复对话。
通过session ticket
另一种方式是 session ticket 的方式,session ticket 是服务器在上一次对话中发送给客户的,这个 ticket 是加密的,只有服务器能够解密,里面包含了本次会话的信息,比如对话秘钥和加密方法等。这样不管我们的请求是否转移到其他的服务器上,当服务器将 ticket 解密以后,就能够获取上次对话的信息,就不用重新生成对话秘钥了。
首先补充一下,http 和 https 的区别,相比于 http,https 是基于 ssl 加密的 http 协议
简要概括:http2.0
是基于 1999 年发布的 http1.0
之后的首次更新
http1.1
中,浏览器客户端在同一时间,针对同一域名下的请求有一定数量限 制(连接数量),超过限制会被阻塞头部压缩
HTTP 1.1版本会出现 User-Agent、Cookie、Accept、Server、Range 等字段可能会占用几百甚至几千字节,而 Body 却经常只有几十字节,所以导致头部偏重。
HTTP 2.0 使用 HPACK
算法进行压缩。
多路复用
HTTP2中:
Stream ID
,流标识符,有了它,接收方就能从乱序的二进制帧中选择ID相同的帧,按照顺序组装成请求/响应报文。服务器推送
浏览器发送一个请求,服务器主动向浏览器推送与这个请求相关的资源,这样浏览器就不用发起后续请求。
相比较http/1.1的优势👇
二进制分帧
之前是明文传输,不方便计算机解析,对于回车换行符来说到底是内容还是分隔符,都需要内部状态机去识别,这样子效率低,HTTP/2采用二进制格式,全部传输01串,便于机器解码。
这样子一个报文格式就被拆分为一个个二进制帧,用Headers帧存放头部字段,Data帧存放请求体数据。这样子的话,就是一堆乱序的二进制帧,它们不存在先后关系,因此不需要排队等待,解决了HTTP队头阻塞问题。
在客户端与服务器之间,双方都可以互相发送二进制帧,这样子双向传输的序列,称为流
,所以HTTP/2中以流来表示一个TCP连接上进行多个数据帧的通信,这就是多路复用概念。
那乱序的二进制帧,是如何组装成对于的报文呢?
优先级
和流量控制
等功能,这样子的话,就可以设置数据帧的优先级,让服务器处理重要资源,优化用户体验。HTTP2的缺点
Google 在推SPDY的时候就已经意识到了这些问题,于是就另起炉灶搞了一个基于 UDP 协议的“QUIC”协议,让HTTP跑在QUIC上而不是TCP上。主要特性如下:
keep-alive
来设置Range
来实现。二进制分帧
这是一次彻底的二进制协议,头信息和数据体都是二进制,并且统称为"帧":头信息帧和数据帧。头部压缩
HTTP 1.1版本会出现 User-Agent、Cookie、Accept、Server、Range 等字段可能会占用几百甚至几千字节,而 Body 却经常只有几十字节,所以导致头部偏重。HTTP 2.0 使用 HPACK
算法进行压缩。多路复用
复用TCP连接,在一个连接里,客户端和浏览器都可以同时发送多个请求或回应,且不用按顺序一一对应,这样子解决了队头阻塞的问题。服务器推送
允许服务器未经请求,主动向客户端发送资源,即服务器推送。请求优先级
可以设置数据帧的优先级,让服务端先处理重要资源,优化用户体验。DNS 的作用就是通过域名查询到具体的 IP。DNS 协议提供的是一种主机名到 IP 地址的转换服务,就是我们常说的域名系统。是应用层协议,通常该协议运行在UDP协议之上,使用的是53端口号。
因为 IP 存在数字和英文的组合(IPv6),很不利于人类记忆,所以就出现了域名。你可以把域名看成是某个 IP 的别名,DNS 就是去查询这个别名的真正名称是什么。
当你在浏览器中想访问 www.google.com
时,会通过进行以下操作:
com
这个一级域名的服务器google.com
这个二级域名www.google.com
这个三级域名的地址[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-De8BCewP-1658290547545)(https://s.poetries.work/images/20210503210054.png)]
我们通过一张图来看看它的查询过程吧👇
这张图很生动的展示了DNS在本地DNS服务器是如何查询的,一般向本地DNS服务器发送请求是递归查询的
本地 DNS 服务器向其他域名服务器请求的过程是迭代查询的过程👇
递归查询和迭代查询
所以一般而言,本地服务器查询是递归查询,而本地 DNS 服务器向其他域名服务器请求的过程是迭代查询的过程
DNS缓存
缓存也很好理解,在一个请求中,当某个DNS服务器收到一个DNS回答后,它能够回答中的信息缓存在本地存储器中。返回的资源记录中的 TTL 代表了该条记录的缓存的时间。
DNS实现负载平衡
它是如何实现负载均衡的呢?首先我们得清楚DNS 是可以用于在冗余的服务器上实现负载平衡。
原因: 这是因为一般的大型网站使用多台服务器提供服务,因此一个域名可能会对应 多个服务器地址。
举个例子来说👇
DNS 为什么使用 UDP 协议作为传输层协议?
DNS 使用 UDP 协议作为传输层协议的主要原因是为了避免使用 TCP 协议时造成的连接时延
总结
—>>
本地hosts文件 —>>
本地DNS解析器 —>>
本地DNS服务器 —>>
其他域名服务器请求。 接下来的过程就是迭代过程。1. 短轮询
短轮询的基本思路:
优缺点👇
2. 长轮询
长轮询的基本思路:
优缺点👇
3. WebSocket
正向代理
我们常说的代理也就是指正向代理,正向代理的过程,它隐藏了真实的请求客户端,服务端不知道真实的客户端是谁,客户端请求的服务都被代理服务器代替来请求。
反向代理
这种代理模式下,它隐藏了真实的服务端,当我们向一个网站发起请求的时候,背后可能有成千上万台服务器为我们服务,具体是哪一台,我们不清楚,我们只需要知道反向代理服务器是谁就行,而且反向代理服务器会帮我们把请求转发到真实的服务器那里去,一般而言反向代理服务器一般用来实现负载平衡。
负载平衡的两种实现方式?
什么是keep-alive
我们知道HTTP协议采用“请求-应答”模式,当使用普通模式,即非KeepAlive模式时,每个请求/应答客户和服务器都要新建一个连接,完成 之后立即断开连接(HTTP协议为无连接的协议);
当使用Keep-Alive模式(又称持久连接、连接重用)时,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连接就看服务器设置情况。
1.0 协议缺陷:
TCP 3
次握手head of line blocking
: 线头阻塞,导致请求之间互相影响1.1 改进:
keep-alive
),复用host
字段指定对应的虚拟站点cache
缓存
Cache-Control
Expires
Last-Modified
Etag
2.0:
https: 较为安全的网络传输协议
SSL
加密443
TCP:
缓存策略: 可分为 强缓存 和 协商缓存
Cache-Control/Expires
: 浏览器判断缓存是否过期,未过期时,直接使用强缓存,Cache-Control
的 max-age
优先级高于 Expires
Etag
(response
携带) & If-None-Match
(request
携带,上一次返回的 Etag
): 服务器判断资源是否被修改Last-Modified(response) & If-Modified-Since
(request
,上一次返回的Last-Modified
)
Last-Modified
缺点:
s
, s
以内的改动无法检测到Etag
的优先级高于Last-Modified
客户端和服务端都需要直到各自可收发,因此需要三次握手
你可以能会问,2 次握手就足够了?。但其实不是,因为服务端还没有确定客户端是否准备好了。比如步骤 3 之后,服务端马上给客户端发送数据,这个时候客户端可能还没有准备好接收数据。因此还需要增加一个过程
TCP有6种标示:SYN(建立联机) ACK(确认) PSH(传送) FIN(结束) RST(重置) URG(紧急)
举例:已失效的连接请求报文段
client
发送了第一个连接的请求报文,但是由于网络不好,这个请求没有立即到达服务端,而是在某个网络节点中滞留了,直到某个时间才到达server
server
端接收到这个请求报文后,还是会想client
发出确认的报文,表示同意连接。server
发出确认,新的建立就连接了,但其实这个请求是失效的请求,client
是不会理睬server
的确认信息,也不会向服务端发送确认的请求server
认为新的连接已经建立起来了,并一直等待client
发来数据,这样,server的很多资源就没白白浪费掉了client
并没有建立连接。这就是三次握手的作用三次握手过程中可以携带数据吗
ESTABLISHED (已建立连接状态)
,对于客户端来说,已经建立起连接了,并且也已经知道服务器的接收、发送能力是正常的了,所以能携带数据也是没问题的。为什么建立连接只通信了三次,而断开连接却用了四次?
服务端经过一个等待,确定可以关闭连接了,再发一条 FIN 给客户端
。为了确保数据能够完成传输。因为当服务端收到客户端的 FIN 报文后,发送的 ACK 报文只是用来应答的,并不表示服务端也希望立即关闭连接。
当只有服务端把所有的报文都发送完了,才会发送 FIN 报文,告诉客户端可以断开连接了,因此在断开连接时需要四次挥手。
SOCKET
,也即你可能还需要发送一些数据给对方之后,再发送FIN报文给对方来表示你同意现在可以关闭连接了,所以它这里的ACK
报文和FIN报文多数情况下都是分开发送的。已经有了被广泛应用的 HTTP 协议,为什么要再出一个 WebSocket 呢?它有哪些好处呢?
其实 WebSocket 与 HTTP/2 一样,都是为了解决 HTTP 某方面的缺陷而诞生的。HTTP/2 针对的是“队头阻塞”,而
WebSocket 针对的是“请求 - 应答”通信模式
。
那么,“请求 - 应答”有什么不好的地方呢?
WebSocket 的特点
采用了二进制帧结构
,语法、语义与 HTTP 完全不兼容,但因为它的主要运行环境是浏览器,为了便于推广和应用,就不得不“搭便车”,在使用习惯上尽量向 HTTP 靠拢,这就是它名字里“Web”的含义。WebSocket 的默认端口也选择了 80 和 443
,因为现在互联网上的防火墙屏蔽了绝大多数的端口,只对 HTTP 的 80、443 端口“放行”,所以 WebSocket 就可以“伪装”成 HTTP 协议,比较容易地“穿透”防火墙,与服务器建立连接ws://www.chrono.com
ws://www.chrono.com:8080/srv
wss://www.chrono.com:445/im?user_id=xxx
WebSocket 的握手
和 TCP、TLS 一样,WebSocket 也要有一个握手过程,然后才能正式收发数据。
这里它还是搭上了 HTTP 的“便车”,利用了 HTTP 本身的“协议升级”特性,“伪装”成 HTTP,这样就能绕过浏览器沙盒、网络防火墙等等限制,这也是 WebSocket 与 HTTP 的另一个重要关联点。
WebSocket 的握手是一个标准的 HTTP GET 请求,但要带上两个协议升级的专用头字段:
另外,为了防止普通的 HTTP 消息被“意外”识别成 WebSocket,握手消息还增加了两个额外的认证用头字段(所谓的“挑战”,Challenge):
Sec-WebSocket-Key
:一个 Base64 编码的 16 字节随机数,作为简单的认证密钥;Sec-WebSocket-Version
:协议的版本号,当前必须是 13。服务器收到 HTTP 请求报文,看到上面的四个字段,就知道这不是一个普通的 GET 请求,而是 WebSocket 的升级请求,于是就不走普通的 HTTP 处理流程,而是构造一个特殊的“101 Switching Protocols”响应报文,通知客户端,接下来就不用 HTTP 了,全改用 WebSocket 协议通信
小结
浏览器是一个“沙盒”环境,有很多的限制,不允许建立 TCP 连接收发数据,而有了 WebSocket,我们就可以在浏览器里与服务器直接建立“TCP 连接”,获得更多的自由。
不过自由也是有代价的,WebSocket 虽然是在应用层,但使用方式却与“TCP Socket”差不多,过于“原始”,用户必须自己管理连接、缓存、状态,开发上比 HTTP 复杂的多,所以是否要在项目中引入 WebSocket 必须慎重考虑。
HTTP
的“请求 - 应答”模式不适合开发“实时通信”应用,效率低,难以实现动态页面,所以出现了 WebSocket;WebSocket
是一个“全双工”的通信协议,相当于对 TCP 做了一层“薄薄的包装”,让它运行在浏览器环境里;WebSocket
使用兼容 HTTP 的 URI 来发现服务,但定义了新的协议名“ws”和“wss”,端口号也沿用了 80 和 443
;WebSocket
使用二进制帧,结构比较简单,特殊的地方是有个“掩码”操作,客户端发数据必须掩码,服务器则不用;WebSocket
利用 HTTP 协议实现连接握手,发送 GET 请求要求“协议升级”,握手过程中有个非常简单的认证机制,目的是防止误连接。