首先,已知网络定义的标准为OSI七层模型
: 物理层,数据链路层,网络层,传输层,会话层,表示层,应用层.
但实际的标准为TCP/IP 五层模型
HTTP的发展史
:
- 1996年,1.0规范:RFC 1945诞生;
- 1999年,1.1规范:RFC 2616诞生;
- 2015年,2.0规范:RFC 7540/7541诞生,
实际目前主流还是http1.1版本.
HTTP报文结构: 起始行 + header + body
- 起始行:标记 这是请求(
http request
)还是响应(http response
) - header :KV键值结构,可存储多个KV.
- body:一个字符串
get类型的请求,请求体是空的;
而对于网页来说,响应体就是一个html串,可以让浏览器去解析显示
HTTP的1.0版本
HTTP:超文本传输协议,作为明文传输协议,不是2进制的协议.
客户端发出TCP连接后,在连接上发出http request
到服务器,服务器返回一个http response
,然后再关闭连接.
(1)HTTP的性能问题
: 连接的建立、关闭比较耗时,对网页来说,除页面本身的html请求,页面中的js、css、javaScript、img…这些静态资源,都是一个一个的http请求, 就目前的互联网页面,一个网页至少有几十个资源文件,要是每个请求都开出一个TCP连接
就非常耗时了,虽然说可以同时开多个连接,然后并发
形式发出请求,但连接数是有限的.
(2)HTTP的服务器推送问题
:客户端不发出请求的时候, 服务器不会主动给客户端推送消息
.
为解决第一个性能问题:
- 客户端就在 http请求头加了字段
Connection:keep-Alive
服务器收到该配置的请求后,处理这个请求之后就不会关闭连接,也在http的响应头中加入该字段,等待客户端在此连接线路中发送下一个请求. - 但是此时带给服务器的问题就是:
连接数有限
,若每个连接都设置了Connection:keep-Alive
,都不及时关闭,越来越多…服务器的可用连接数不足. 所以说服务器就设置了keep-Alive timeout
参数,在设定的时间之后,此连接上没有新的请求发来时,这个连接就会关闭. 连接复用的新问题
,在之前的版本中一个连接仅发送一个请求得到一个响应,服务器处理后就把连接关闭; 此时客户端就会明白连接的请求处理结束, 但此时即使一个请求处理结束后也不关闭连接,那么客户端如何得知http请求处理结束了呢? / (如何确认接收的是完整数据包呢?) 实际上:在http 的响应头中就会返回一个Content-Length:xxx
, 该属性可告诉客户端的响应体一共多少字节,客户端收取完成后就知道自己的收据接收完毕了.
HTTP的1.1版本
缼省连接复用
- HTTP的1.1版本时,这个连接复用 就作为一个缼省的属性,[即使不在请求头中加属性
Connection:Keep-Alive
,服务器处理完请求后也不关闭连接]. - 当然不想用这个连接复用属性,也可以使用
Connection:Close
进行关闭.
数据分块chunk
在之前版本中的Content-Length
有这样的问题: 若服务器返回的数据作为动态语言生成的内容,那么就得自己计算Content-Length, 对对服务器来说比较耗时.
所以HTTP1.1使用了Chunk
机制, 即在响应头中加入Transfer-Encoding:chunked属性
,目的是通知客户端–>响应体被分为一块一块的,每个数据块之间有间隔符
,这些块的结尾具有标记,那么即使不设置Content-Length
时,客户端也可以根据该标记判断到响应体的数据是否已经接收完毕.
比如这个非常经典的案例:
响应中包含了4个chunk
,数字25(16进制)表示第一个chunk的字节数,
1C(16进制)表示第二个chunk的字节数;数字3 … 8…,
最终数字0表示整个响应的数据完毕
HTTP/1.1 200 OK
Content-Type: text/plain
Transfer-Encoding: chunked
25
This is the data in the first chunk
1C
and this is the second one
3
con
8
sequence
0
管道传输Pipeline机制
虽然连接复用机制好用一点,但是在同一个连接之上,请求是串行交互的,客户端发送一个请求后,收到响应,然后发出下一个请求,再进行回应.这就是传统的串行化方式交流,并发度过低.
所以在HTTP1.1时,优化使用了PipeLine管道机制
,在同一个TCP上面,即使这个请求的响应还没回来,当然还可以发送其他的请求,提高了请求的效率.
但是这样的机制又产生 队头阻塞问题[Head-of-line Blocking]
-
比如客户端发请求时顺序是1,2,3 发出去的, 虽然说咱们服务器可以并发处理请求, 但是客户端接收响应的顺序也必须是 1, 2, 3;这样才能保证请求得到的是自己对应的响应数据;.
-
可是一旦某个请求的响应出现了延迟阻塞,那么后面的响应也会被阻塞.
性能优化
(1)由于队头阻塞问题,管道通信PipeLine不好用,
(2)对于同一个域名,浏览器限制只可以开6-8个连接,但是一个网页获取要发几十个请求,怎么提升性能;
此时就需要来优化性能了;
(1)spriting技术: 主要用于小图标.
假设某个网页,需要从服务器加载很多的小图标,那就可以在服务器中将这些小图标拼成一个大图,再通过JS/CSS,从大图中截取需要的小图标进行使用.
(2)内联:也是主要用于小图标.
将图片的原始数据存入CSS文件,
比如:.iconw{ background: url(data:image/png;base64,<data>) no-repeat; }
(3)JS拼接
将大量比较小的JS文件合并为一个文件且压缩打包,让浏览器在一个请求中就可以下载结束;
(4)请求分片技术
由于一个域名,浏览器就限制它开6-8个连接,对于网站开发者来说,想要提升页面加载速度,一个方法就是多提供几个域名,逃避浏览器的限制问题; 比如目前的CDN技术, (img,js,css)都可以存到CDN上面,可做一批CDN的域名,提升页面加载并发度.
服务器如何做到主动推送消息
对于web流程来看,http1.0和http1.1版本 都无法达到 服务器主动推送消息
; 但是在实际开发中又存在这样的需求.可以试试这几种方法:
(1)采用客户端定时的轮询请求
每隔一段时间,客户端发出请求, 服务端有了新消息就响应回去, 但是这种方式会增加服务器的压力.
(2)HTTP长轮询
客户端发出HTTP请求,若服务端恰好有新消息则响应即可, 若没有,服务器就会保持这个连接,客户端那边一直等待.
如果超出某个设定的时间,服务器还没有消息,那么服务器就响应空消息,客户端停手=>关闭连接.
(3)HTTP stream流
服务端采用分块发送
Transfer-Encoding: chunked机制
,不断发送数据块,即响应不停止.
断点下载
当客户端从服务器下载文件时,若下载到一半时连接中断,再次新建连接之后,客户端就可以从上次断开的位置继续下载.
客户端在下载时就顺便记录下载的数据量大小, 一旦连接中断,那么重新连接之后,在请求头处 就可以加上
Range:first offset - last offset
,指定从指定的offset下载到指定的offset ,
服务器仅需要响应first->last
区间的数据即可.
HTTP的2版本
由于1.1的pipeline管道传输机制有队头阻塞问题
,那么协议层面的SPDY协议就出现了,进而优化出现了HTTP2版本.
HTTP2也是兼容了HTTP1.1, 在它的上面套了一个HTTP1.1协议, 可以这么看:HTTP2处于HTTP1.1和TCP协议之间.
HTTP的优化: 2进制分帧,避免了队头阻塞问题
具体:
(1) 对每个域名, 客户端与服务器之间仅维护一条TCP连接;
(2)在将HTTP1.1的字符格式报文发给TCP之前,中间的 HTTP2就会把它转为2进制
,分为多个帧(数据块)发送到TCP.
请求响应过程中进行拆包组包即可
二进制分帧机制采用之后,在TCP层面,看起来是串行的,但是在HTTP通信层面来看实际就是并发地发放出去,并发接收响应
,没有http1.1那种pepeline的限制 ;
比如请求 A,B,C 虽然按顺序发出去的,但响应的消息可以不按顺序返回.
并没有彻底解决队头阻塞问题,只是从细化了为帧, 降低了队头阻塞发生的频率;
由于TCP协议是先进先出的,若队头的第一个帧 在网络上被阻塞(或丢包),那么服务器就会一直等待这个数据帧,若它不来,则后面的包都不会被成功接收.
请求1的响应1为什么迟迟不来呢?
- (1)服务器对于请求1处理很慢;
- (2)服务器对于请求1 处理及时,但是网络传输过慢;
(1)的问题:当前的请求2与请求3响应分帧后,先于请求1的响应发出;那么请求2/请求3的响应就不会被请求1阻塞,避免了队头阻塞问题.
(2)的问题:若请求1的第一个帧处于队头,则及时二进制分帧也无法解决队头阻塞问题.