系列文章传送门:html
网络协议五层通天路,我们从物理层、到链路层、网络层、再到传输层,如今又进一步,来到了应用层。这也是咱们五层协议里最上面的一层,关于应用层,有太多协议要了解。但要说最有名的,那确定就是 HTTP 了。编程
HTTP 协议,几乎是每一个人上网用的第一个协议,同时也是很容易被人忽略的协议。json
就像 http://blog.muzixizao.com/,是个 URL,叫做统一资源定位符。之因此叫统一,是由于它是有规定格式的。HTTP 称为协议,blog.muzixizao.com 是一个域名,表示互联网的一个位置。有的 URL 会有更详细的位置标识,例如浏览器
http://blog.muzixizao.com/?p=140
正是由于格式是统一的,因此当你把这样一个字符串输入到浏览器的框里的时候,浏览器才知道如何进行统一处理。缓存
浏览器会将 blog.muzixizao.com 这个域名发送给 DNS 服务器,让它解析为 IP 地址。关于 DNS 解析的过程,较为复杂,后面会专门介绍。安全
域名解析成 IP 后,下一步是干吗呢?服务器
还记得吗?HTTP 是基于 TCP 协议的,因此接下来就是创建 TCP 链接了。具体的链接过程可点击这里查看。网络
目前使用的 HTTP 协议大部分都是 1.1.在 1.1 协议里面,默认开启了 Keep-Alive 的,这样创建的 TCP 链接,就能够在屡次请求中复用。虽然 HTTP 用了各类方式来解决它存在的问题,但基于TCP 的它,每次创建链接的三次握手以及断开链接的四次挥手,这个过程仍是挺费时的。若是好不容易创建了链接,而后作一点儿事情就结束了,未免太浪费了。架构
创建了链接之后,浏览器就要发送 HTTP 的请求。请求的格式以下图:并发
如图,HTTP 的报文大概分为请求行、首部、正文实体三部分。接下来,我们就来一一认识。
在请求行中,URL 就是 http://blog.muzixizao.com,版本为 HTTP 1.1。这里要说一下的,就是对应的请求方法。有如下几种类型:
1)GET 请求
对于访问网页来说,最经常使用的类型就是 GET。顾名思义,GET 就是去服务器获取一些资源。对于访问网页来说,要获取的资源每每是一个页面。其实也有不少其余的格式,好比返回一个 JSON 字符串。固然,具体要返回什么,是由服务端决定的。
例如,在云计算中,若是咱们的服务端要提供一个基于 HTTP 协议的 API,获取全部云主机的列表,就会使用 GET 方法请求,返回的多是一个 JSON 字符串,字符串里面是一个列表,列表里面会有各个云主机的信息。
2)POST 请求
另外一种类型叫作 POST。它须要主动告诉服务端一些信息,而非获取。而要告诉服务端的信息,通常都放在正文里面。正文里有各类各样的格式,最多见的的就是 JSON了。
例如,咱们平时的支付场景,客户端就须要把 “我是谁?我要支付多少?我要买什么?” 这样信息告诉服务器,这就须要 POST 方法。
再如,在云计算里,若是咱们的服务器,要提供一个基于 HTTP 协议的建立云主机的 API,也会用到 POST 方法。这个时候每每须要将 “我要建立多大的云主机?多少 CPU 和多少内存?多大硬盘?” 这些信息放在 JSON 字符串里面,经过 POST 的方法告诉服务器。
除了上面常见的两种类型,还有一种 PUT 类型,这种类型就是向指定资源位置上传最新内容。可是 HTTP 的服务区每每是不容许上传文件的,因此 PUT 和 POST 就都变成了要传给服务器东西的方法。
在咱们的实际使用过程当中,PUT 和 POST 仍是有区别的。POST 每每是用来建立一个资源,而 PUT 每每是用来更新一个资源。
例如,云主机已经建立好了,想对云主机打一个标签,说明这个云主机是生产环境的,另一个云主机是测试环境的。咱们修改标签的请求每每就是用 PUT 方法。
还有 DELETE 方法。这个是用来删除资源的。
请求行下面就是首部字段。首部是 key-value 格式,经过冒号分割。这里面,每每保存了一些很是重要的字段。
这里重点认识下缓存字段。为何要使用缓存呢?这是由于一个很是大的页面有不少东西。
例如,咱们浏览一个商品的详情,里面有商品的价格、库存、展现图片、使用手册等待。
商品的展现图片会保持较长时间不变,而库存胡一根筋用户购买状况常常改变。若是图片很是大,而库存数很是小,若是咱们每次要更新数据的时候都要刷新整个页面,对于服务器的压力也会很大。
对于这种高并发场景下的系统,在真正的业务逻辑以前,都须要有个接入层,将这些静态资源的请求拦在最外面。架构就像下图:
其中 DNS、CDN 会在后面的章节详细说明。这里我们就先来了解下 Nginx 这一层。它是若是处理 HTTP 协议呢?对于静态资源,有 Vanish 缓存层,当缓存过时的时候,才会访问真正的 Tomcat 应用集群。
在 HTTP 头里面,Cache-Control 是用来控制缓存的。当客户端发送的请求中包含 max-age 指令时,若是断定缓存层中,资源的缓存时间数值比指定时间的数值校,那么客户端能够接受缓存的资源;当指定 max-age 值为 0,那么缓存层一般须要将请求转发给应用集群。
另外,If-Modified-Since 也是关于缓存的字段,这个字段是说,若是服务器的资源在某个时间以后更新了,那么客户端就应该下载最新的资源;若是没有更新,服务端会返回“304 Not Modified” 的响应,那客户端就不用下载了,也会节省带宽。
到此,咱们拼凑起了 HTTP 请求的报文格式,接下来,浏览器会把它交给传输层。
HTTP 协议是基于 TCP 协议的,因此它是以面向链接的方式发送请求,经过 stream 二进制流的方式传给对方。固然,到了 TCP 层,它会把二进制流变成一个个的报文段发送给服务器。
在发送给每一个报文段的时候,都须要对方有一个回应 ACK,来保证报文可靠地到达了地方。若是没有回应,那么 TCP 这一层会从新传输,直到能够到达。同一个包有可能被传了好屡次,可是 HTTP 这一层不须要知道这一点,由于是 TCP 这一层在埋头苦干。
然后续传输过程以下:
HTTP 的返回报文也是有必定格式的,以下图:
状态行包含状态码和短语。状态码反应 HTTP 请求的结果。200 是大吉大利;404 则是咱们最不想见到的,也就是服务端没法响应这个请求。短语中会说明出错缘由。
首部 key-value。这里经常使用的有如下字段:
构造好了返回的 HTTP 报文,接下来就是把这个报文发送出去。固然,仍是交给 Socket 去发送,交给 TCP,让 TCP 返回的 HTML 分红一个个小的数据段,而且保证每一段都安全到达。这些小的数据段会加上 TCP 头,而后交给 IP 层,沿着来时的路反向走一遍。虽然不必定是彻底相同的路径,可是逻辑过程是同样的,一直到达客户端。
客户端取出数据后 ,会根据端口号交给指定的程序,这时候就是咱们的浏览器出马的时候。
浏览器拿到了 HTTP 报文,发现返回 200,一切正常,就从正文中将 HTML 拿出来,展现出一个炫酷吊炸天的网页。
以上就是正常的 HTTP 请求与返回的完整过程。
上面提到了,如今用到 HTTP 大可能是 1.1 版本,而 HTTP 2.0 在 1.1 的基础上进行了一些优化,以期解决一些问题。
HTTP 1.1 在应用层以纯文本的形式进行通讯。每次通讯都要带完整的 HTTP 头,并且不考虑 pipeline 模式的话,每次的过程都要像上面描述的那样一去一回。显然,在效率上会存在问题。
为了解决这些问题,HTTP 2.0 会对 HTTP 头进行必定的压缩,将原来每次都要携带的大量 key-value 对在两端创建一个索引表,对相同的头只发送索引表中的索引。
另外,HTTP 2.0 协议将一个 TCP 链接切分红多个流,每一个流都有本身的 ID,并且流能够是客户端发给服务端,也能够是服务端发给客户端,它其实只是个虚拟的通道,除此以外,它还有优先级。
HTTP 2.0 将全部的传输信息分割成更小的消息和帧,并对它们采用二进制格式编码。常见的帧有 Header 帧,用于传输 Header 内容,而且会开启一个新的流。还有 Data 帧,用来传输正文实体,而且多个 Data 帧属于同个流。
经过这两种机制,HTTP 2.0 的客户端能够将多个请求分到不一样的流中, 而后将请求内容拆分红帧,进行二进制传输。这些帧能够打散乱序发送,而后根据帧首部的流标识符从新组装,而且能够根据优先级,决定先处理哪一个流的数据。
针对 HTTP 2.0,咱们来看一个例子。
假设咱们有一个页面要发送三个独立的请求,一个获取 CSS、一个获取 JS、一个获取图片 jsg。若是使用 HTTP 1.1,这三个请求就是串行的,可是若是使用 HTTP 2.0,就能够在一个链接里,客户端和服务端同时反思多个请求和回应,并且不用按照顺序一对一对应。
如上图。HTTP 2.0 实际上是将三个请求变成三个流,将数据分红帧,乱序发送到一个 TCP 链接中。
HTTP 2.0 成功解决了 HTTP 1.1 的队首阻塞问题。同时,也不须要经过 HTTP 1.x 的 pipeline 机制用多条 TCP 链接来实现并行请求与响应,减小了 TCP 链接数对服务器性能的影响,加快页面组件的传输速度。
HTTP 2.0 虽然大大增长了并发性,但因为 TCP 协议的按序处理的特性,仍是会出现阻塞的问题。
还记得我们以前说过的 QUIC 协议吗?这时候就是它登场的时候了。
它用如下四个机制,解决了 TCP 存在的一些问题。
机制一:自定义链接机制
咱们知道,一条 TCP 链接是由四元组标识的。一旦一个元素发生变化,就须要端口重连。这在移动互联网的状况下,当咱们切换网络或者信号不稳定时,都会致使重连,从而增长时延。
TCP 没办法解决上述问题,可是 QUCI 基于 UDP 协议,就能够在本身的逻辑里面维护链接的机制,再也不以四元组标识,而是以一个 64 位的随机数做为标识 ID,并且 UDP 是无链接的,只要 ID 不变,就不须要从新创建链接。
机制二:自定义重传机制
TCP 为了保证可靠性,经过使用序号和应答机制,来解决顺序问题和丢包问题。
任何一个序号的包发出去,都要在必定时间内获得应答,不然就会超时重发。这个超时时间就是经过采样往返时间 RTT 不断调整的。其实,这个超时时间的采样是不太准确的。
如上图。发送一个包,序号为 100,超时后,再发送一个 100。而后收到了一个 ACK101。这个时候客户端知道服务器已经收到了 100,可是往返时间怎么计算呢?是 ACK 到达时间减去后一个 100 发送的时间,仍是减去前一个 100 发送的时间呢?前者把时间算短了,后者把时间算长了。
QUIC 也有一个序列号,是彻底递增的。任何一个包发送一次后,下一次序列号就要加一。像咱们上面的例子,在 QUIC 协议中,100 的包没有返回,再次发送时,序号就是 101 了,若是返回是 ACK100,就是对第一个包的响应,若是返回 ACK101,就是对第二个包的响应,RTT 时间计算相对准确,过程以下图:
上面的过程当中,有的童鞋可能会问了,两个序号不同的包,服务器怎么知道是一样的内容呢?没错,这确实是个问题。为了解决这个问题,QUIC 协议定义了一个 Offset 的概念。
QUIC 既然是面向链接的,也就像 TCP 同样,是一个数据流。,发送的数据在这个流里面都有个偏移量 Offset,能够经过 Offset 查看数据发送到了那里,这样只要这个 Offset 的包没有来,就要重发。若是来了,就按照 Offset 拼接成一个流。
机制三:无阻塞的多路复用
有了自定义的链接和重传机制,咱们就能够解决上面 HTTP 2.0 的多路复用问题。
同 HTTP 2.0 同样,同一条 QUIC 链接上能够建立多个 stream,来发送多个 HTTP 请求。更棒的是,QUIC 是基于 UDP 的,一个链接上的多个 stream 之间没有依赖。这样,假如 stream2 丢了一个 UDP 包,后面跟着 stream3 的一个 UDP 包,虽然 stream2 的那个包须要重传,可是 stream3 的包无需等待,就能够发给用户。
机制四:自定义流量控制
TCP 的流量控制是经过滑动窗口协议。QUIC 的流量控制也是经过 window_update,来告诉对端它能够接受的字节数。可是 QUIC 的窗口是适应本身的多路复用机制的,不但在一个链接上控制窗口,还在一个链接中的每一个 stream 控制窗口。
还记得吗?在 TCP 协议中,接收端的窗口的起始点是下一个要接收而且 ACK 的包,即使后来的包都到了,放在缓存里面,窗口也不能右移,由于 TCP 的 ACK 机制是基于序列号的累计应答,一旦 ACK 一个序列号,就说明前面的都到了,因此只要前面的没到,后面的即便到了也不能 ACK,就会致使后面的到了,也有可能超时重传,浪费带宽。
QUIC 的 ACK 是基于 offset 的,每一个 offset 的包来了,进了缓存,就能够应答,应答后就不会重发,中间的空档会等待到来或者重发便可,而窗口的起始位置为当前收到的最大 offset,从这个 offset 到当前的 stream 所能容纳的最大缓存,是真正的窗口大小,显然,这样更加准确。
另外,还有整个链接的窗口,须要对于全部的 stream 的窗口作一个统计。
参考: