我们开始学习了应用层的协议,从HTTP协议开始

我们首先拿一个网站门户来看

假如登录了http://www.163.com,前面整体是一个URL,统一资源定位符,HTTP称为协议,www.163.com 是一个域名,表明了互联网的一个地址,我们URL有着非常详细的位置标注,例如http://www.163.com/index.html ,因为整体是统一的,有协议的,浏览器才知道如何去处理

HTTP请求的准备过程

浏览器会将www.163.com 这个域名发给DNS服务器,让其解释为IP地址,具体的流程就先不管,接下来我们HTTP基于了TCP协议,建立了TCP连接,怎么建立呢?就是所谓的三次握手,然后当前HTTP协议大部分都是1.1的,在1.1之中,开启了keep-alive的,建立了TCP连接,可以在多次请求中复用

HTTP请求的构建

我们要通过浏览器发送HTTP的请求

图片

我们的报文分为了三个部分,第一部分是请求行,然后是首部,最后是正文部分

(1).请求行,我们的请求的HTTP版本为1.1,这里说的第一个,就是方法,方法能有的类型有

GET,去服务器获取一些资源,对于访问网页来说,可能获取的是一个网页,对于很多API,获取的可能是一个JSON字符串,比如我们有一个HTTP的API,我们在获取的时候,就会使用GET方法获取这个API,这个API可能返回JSON字符串,里面是一个列表,然后遍历获取对应数据

(2).然后是POST,这就是主动告知服务器的一些信息,而不是获取一些数据,告知的信息可以多种多样,一般是JSON,比如我们创建一个云服务器,就是通过了POST方法,将需要创建的信息放在一个JSON中,然后通过POST方法告诉服务器端

(3).或者使用PUT,指向指定的资源位置上传最新的内容,但是一般很少HTTP服务器可以上传文件,于是和POST变成了给服务器给予数据的方法

实际使用过程中,两者是有些区别的,POST是创建一个资源,PUT是修改一个资源

(4).还有就是DELETE,就是删除资源的

2.首部

首部是key:value的信息,保存一些HTTP相关的字段

例如Accept-Charset,表示客户端可以接受的字符集

Content-Type,表示正文的格式,比如我们使用POST请求,如果正文是JSON,就应该将这个值设置为json

然后首部里面还有缓存相关的信息,缓存常用于服务我们页面的静态资源

比如我们去浏览一个商品,商品的介绍一般不会改变,但是库存需要实时刷新,于是我们的更新就要选择性的去更新数据

图片

在nginx中,对于静态资源,有Vanish缓存层,当缓存过期的时候,才去请求动态资源

在首部,我们的Cache-control是控制缓存的,当客户端发送的请求中包含了max-age指令的时,如果判定缓存层中资源的缓存时间比指定时间小,就可以接受缓存的资源,当指定max-age值为0,缓存层就要将请求转发给应用集群

If-Modified-Since也是关于缓存的,也就是,如果服务器的资源在某个时间之后更新了,客户端应该更新资源,但是如果在时间后没有更新,服务端返回一个304 Not Modified的响应

那么,我们就拼凑了Http的请求报文,那么浏览器会将其交给传输层,然后发送,其中的Socket的调用,会由浏览器管理,而非自己管理

HTTP请求的发送

我们进行了HTTP请求的发送,而HTTP是基于TCP的,于是面向连接去发送,通过了stream流的方式发送给了对方,到了TCP层,将二进制变为了一个个报文段给了服务器

然后每发送一个报文段,都需要对方一个ACK回应,保证报文可靠的到达了对方,如果没有回应,那么TCP会在这一层多次重试,这是TCP做的,然后TCP加上了目标地址和本地址,交给IP层传输,IP层利用ARP协议,找到MAC地址,然后发送出去

网关收到了包发现了MAC符合,取出了IP地址,根据路由协议找到了下一跳的路由器,获取下一跳的MAC地址,将包发给下一跳路由,这样包到了目标的服务器,然后解析TCP的头,发现序列号是不是我要的,如果是,就放入缓存中,然后返回一个ACK,不是就丢弃

TCP头里面有对应的端口号,然后会知道HTTP的服务器的应用监控的也是这个端口号,于是利用这个端口号将包发给了HTTP服务器,HTTP服务器的进城解析后,返回了对应的数据

HTTP返回包的构建

返回的报文是有一定格式,如果基于HTTP1.1的

图片

主要就是状态码,这个会表示HTTP返回的结果,200代表着成功,404代表着服务器无法响应这个请求,短语中表明了原因

然后是返回首部的key:value

其中有一个key:Retry-After,表明客户端应该多久后尝试

Content-Type表示返回的类型,是否是HTML还是JSON

构造好了返回的HTTP报文,原路返回回去

然后客户端接受到了,从MAC层到IP层到TCP层,到HTTP层,发现返回的是200,就是正常的,然后将HTML拿出来,解析成一个网页

HTTP 2.0的问题

当然HTTP协议在不断的进化,现在已经有了HTTP2.0

HTTP1.0的通信是纯文本的,每次通信都要用完整的HTTP的头,而且不考虑pipeline的时候,就是每次发包收包都是一来一回的,在实时性和并发性都有问题的

现在的HTTP2.0会对HTTP头会进行一定的压缩,每次传递都会传输的大量的头,都变成了索引表中的索引

HTTP2.0将一个TCP的连接中请求,切分为多个流,每个流都有自己的ID,而且流可以是客户端发往服务端,也可以是服务端发往客户端

并且将传输的信息分为更小的消息和帧,并且进行二进制的编码,常见的有Header帧,传输Header内容,并且开启一个新的流,就是Data帧,传输正文实体,多个Data帧属于一个流

这样我们,可以将多个请求分到不同的流中,然后将请求内容拆成帧,进行二进制传输,这些帧可以打散乱序发送,然后根据每个帧的首部的流标识符重新组装

我们发送三个独立的请求,一个获取CSS,一个获取JS,一个获取图片,使用1,1就是串行的,使用2.0就是在一个连接中,同时发送多个

图片

2.0将三个请求变成了三个流,将数据分成了帧,乱序的发送到一个TCP连接中

图片

HTTP2.0成功解决了HTTP1.1的队首阻塞问题,同时不需要通过HTTP1.0的pipeline机制,来同步发送,而是将多个数据通过一个数据连接同时传输,加快了传输效率

然后就是QUIC协议

可能是未来的发展趋势,因为TCP本身的数据传输就是有顺序的,这使得即使使用了HTTP2.0,通过多个stream,让逻辑上一个TCP能够并行发送,但是中间并没有关联的数据,一前一后的发送,也会因为一个阻塞而导致后面的阻塞

于是我们可以采用更为简单的方式,UDP的连接方式发送,就是Google的QUIC协议

这个QUIC的协议,是具有自己的独特机制的

一:连接机制

因为TCP是由四元标识符标识的,分别是源IP,源端口,目的IP,目的端口,当一个元素发生了变化的时候,就需要断开重连,这样肯定不适合手机端的连接,因为手机端的连接不稳定,而且会出现WIFI和移动网络的切换,于是基于UDP可以解决这些问题,使用一个64位的随机数作为ID,这样IP或者端口变化的时候,只要ID不变,就可以不重新连接

二:重传机制

TCP为了保证可靠性,通过序号和应答机制,来解决了顺序问题和丢包问题

任何一个序号的包发过去,都需要在一定的时间内获得应答,不然就重发,这就是自适应重传算法,这个超市是通过RTT采样往返时间调整的

那么,TCP的计算时间是否准确呢?

加入我们发送了一个包,序号为100,发现过期后没有返回,只能在发送一个,然后知道返回了一个包,但是多次发送了100这个包,我们如何知道具体的RTT呢?

如果是按照返回的包是第一次发送的,就会把时间算长了,如果按照第二个发送的,就会算短了

于是QUIC进行了改进,在QUIC中,有一个序号同样的,但是每个序号只会被使用一次,一个包的序号是100,如果发现超时,再次发送就是101的序号,这样返回的包可以直接对应到了,RTT的计算爷更加准确

但是有一个问题,如何知道包100和包101发送的内容保证一样的呢?QUIC有一个offset的概念,QUIC是面向连接的,就像是TCP一样,是一个数据流,可以查看数据发送到了那个部分了,只要这个offset的包没有发过,就重发一次

图片

机制三,无阻塞的多路复用

我们利用自定义的连接和重传机制,来解决上面的多路复用,而且一个QUIC可以创建多个straem流,发送多个HTTP请求,但是QUIC是基于UDP的,一个连接上面的多个stream之间没有依赖,加入stream2丢了一个udp的包,也不会影响到后面的stream3

机制四:自定义流量控制

TCP的流量控制是通过滑动窗口协议,QUIC也是通过window_update,告诉对端可以接收的字节数,但是QUIC的窗口是适应自己的多路复用机制的,不但在一个连接上控制窗口,还在一个连接中的每个stream控制了窗口

TCP协议中,接收端的窗口的起始点是下一个要接受并发送ACK的包,后面的包都到了,放在了缓存中,窗口也不能右移,因为TCP的ACK是基于序列号的累计应答,一旦ACK了一个序列号,就说明前面的都到了,但是前面的没到,后面的到了也不能直接ACK,因为要等一批,导致可能出现的超时重传问题

QUIC的ACK基于了offset,每个offset的包来了,进了缓存,就可以进行应答,应答后发送方就不会重发了,中间的空档没来,会自然超时后重发的,窗口的起始位置就是当前收到的最大offset,从这个offset到当前stream能够容纳的最大缓存,就是真正的窗口大小

图片

本章小结

Http常用的几种方法,比如GET POST PUT DELETE的几个方法,以及重要的首部字段

HTTP2.0的改进,增加了多路复用和头压缩和分帧和二进制编码来提升了性能

QUIC协议通过基于UDP的方式,来定义了类似TCP的连接,重试,多路复用,流量控制,来进一步提升性能

课后思考

1.QUIC是一个精巧的协议,所以包含的机制肯定很多,能说一下嘛?

2.这一节讲了如何基于HTTP浏览网页,那么传输像是支付的信息,怎么办呢?

1.虽然QUIC很厉害,但是碍于设备的发展和老基站的不支持,仍然不能普及

具体点来说,QUIC还有其他的属性,一个是快速建立连接,一个是拥塞控制,QUIC协议默认使用了TCP协议的CUBIC 拥塞控制算法,

TCP的拥塞控制算法,是每当收到一个ACK的时候,都需要调整拥塞窗口的大小,对于RTT比较小的,窗口生长速度快

对于很多跨地区长的传输,RTT比较长,因为RTT的窗口调整策略,不太公平,因为,窗口增长慢,导致很多时候没涨到带宽的上限,数据就发完了,因而巨大的带宽就已经浪费掉了

CUBIC进行了不同的设计,窗口的增长函数取决于两次拥塞事件的时间间隔值,窗口增长独立于网络的RTT

CUBIC的窗口大小和变化的过程如图所示

图片

丢包事件发生的时候,CUBIC会记录此时的拥塞窗口的大小,作为Wmax,接着,CUBIC会通过某个因子执行拥塞窗口的乘法减小,然后,沿着立方函数进行窗口的恢复

所以,恢复的速度是很快的,后来便从快速灰度的阶段进入了避免拥塞的阶段,当接近Wmax的时候,增长的速度变慢,直到在Wmax的时候达到了稳定点,增长速度为0,之后平稳的慢慢增长,探索新的最大窗口

keepalive模式如何理解,什么时候断开的,如何判断的呢?

在没有keepalive的模式下,每个HTTP都需要建立一个TCP的连接,并且使用一次就断开这个连接,使用了keepalive之后,再一次TCP的连接中,可以维护多份数据而不断开连接,减少TCP的连接建立次数,从而减少TIME_WAIT状态

长时间的TCP连接容易导致系统的资源无效占用,因为需要设置合理的keepalive timeout的时间,当HTTP产生的TCP连接在传输完最后一个响应后,需要等待keepalive timeout秒后,才能关闭这个连接

http1.0的队首阻塞,对于一个tcp连接,所有的1,0都放在队列中,进行串行化的发送

1.1的队首阻塞,允许一次性的发送多个http1.1的请求,不必等到前一个相应的收到,就能发送一下个,但是对于服务端来说,先接受的请求要先发送,导致请求处理时间越长,响应速度越慢

1.2利用一个tcp上,有多个stream,每个stream各自发送接受http请求,互不阻塞来解决了阻塞问题

图片

发表评论

邮箱地址不会被公开。 必填项已用*标注