系列文章传送门:网络协议1-网络协议概述2-IP是怎么来的,为什么消失了?网络协议3-从物理层到MAC层网络协议4-交换机和VLAN:办公室太复杂,我要回学校了网络协议5-ICMP和ping:问路的童子军网络协议6-路由协议:敢问路在何方?NetworkProtocol7-UDPProtocol:一个好人可以在城市玩NetworkProtocol8-TCPProtocol(Part1):如果一个人是坏人,他必须遵循一个套路DeepNetworkProtocol9-TCPProtocol(Part2):Clear而不是被误解网络协议10-Socket编程(上):实践是检验真理的唯一标准网络协议11-Socket编程(下):眼见为实,耳听为虚传输层,现在更进一步,来到应用层。这也是我们五层协议的顶层。应用层的协议太多,难以理解。但是如果要说最著名的,那一定是HTTP了。HTTP协议是几乎每个人上网的第一个协议,也是一个容易被忽视的协议。像http://blog.muzixizao.com/,就是一个URL,叫做UniformResourceLocator。之所以叫统一,是因为它有规定的格式。HTTP称为协议,blog.muzixizao.com是一个域名,代表互联网上的一个位置。有些网址会有更详细的位置标识,比如http://blog.muzixizao.com/?p=140这是因为格式是统一的,所以当你在浏览器框中输入这样的字符串时,浏览器才会知道如何进行统一处理。HTTP请求的准备浏览器会将域名blog.muzixizao.com发送给DNS服务器解析成IP地址。DNS解析的过程比较复杂,后面会介绍。域名解析成IP后,下一步是什么?你是否记得?HTTP是基于TCP协议的,所以接下来就是建立TCP连接了。具体连接过程可以看这里。目前使用的HTTP协议大多是1.1。在1.1协议中,Keep-Alive是默认开启的,这样建立的TCP连接可以在多个请求中复用。虽然HTTP使用了各种方法来解决它存在的问题,但是基于TCP,每次连接建立的三次握手和断开的四次握手是相当耗时的。如果最后建立连接,然后再做一点工作就完事了,那就太浪费了。构造HTTP请求连接建立后,浏览器会发送一个HTTP请求。请求的格式如下:如图所示,HTTP报文大致分为三部分:请求行、头部、文本实体。接下来,让我们一一认识。请求行请求行中URL为http://blog.muzixizao.com,版本为HTTP1.1。这里要说的是对应的请求方法。有以下几种类型:1)GET请求对于访问网页,最常用的类型是GET。顾名思义,GET就是到服务器去获取一些资源。访问一个网页,获取的资源往往是一个页面。其实还有很多其他的格式,比如返回一个JSON字符串。当然,返回什么是由服务器决定的。比如在云计算中,如果我们的服务器要提供一个基于HTTP协议的API来获取所有云主机的列表,就会使用GET方法去请求,返回的可能是一个JSON字符串,就是一个list,列表中会有每台云主机的信息。2)POST请求还有一种叫做POST。它需要主动告诉服务器一些信息,而不是获取。告知服务器的信息一般放在文本中。文本有多种格式,最常见的是JSON。比如在我们平时的支付场景中,客户端需要告诉服务器“我是谁?我要付多少钱?我要买什么?”这样的信息。这需要POST方法。再比如,在云计算中,如果我们的服务器要提供基于HTTP协议创建云主机的API,也会用到POST方法。这时候往往需要放入“我要创建一个多大的云主机?多少CPU多少内存?多少硬盘?”这样的信息。在JSON字符串中并通过POST方法告诉服务器。除了上面两种常见的类型,还有一种PUT类型,就是将最新的内容上传到指定的资源位置。但是HTTP服务区往往不允许文件上传,所以PUT和POST都成为了向服务器传递东西的方法。在我们实际使用中,PUT和POST还是有区别的。POST常用于创建资源,PUT常用于更新资源。比如已经创建了云主机,要给云主机打标签,说明这台云主机在生产环境,另外一台云主机在测试环境。我们请求修改标签往往是PUT方法。还有一个DELETE方法。这用于删除资源。头域头域在请求行下方。第一部分是键值格式,以冒号分隔。其中,往往会保存一些非常重要的字段。Accpet-Charset:客户端可以接受的字符集。防止传递的字符串客户端不支持,导致出现乱码;Content-Type:文本格式。当我们发起POST请求时,如果文本是JSON,我们应该将这个值设置为application/json;缓存字段Cache-Control、If-Modified-Since。这里我们着重理解缓存字段。为什么要使用缓存?这是因为一个非常大的页面有很多东西。比如我们浏览一个商品的详情,包括商品的价格、库存、展示图片、使用说明书等。商品的展示图片会长期不变,但库存中用户的购买情况会经常发生变化。如果图片很大,库存很小,如果我们每次要更新数据都要刷新整个页面,那么服务器的压力也会很大。对于这种高并发场景下的系统,在真正的业务逻辑出现之前,需要有一个接入层来阻断外部对这些静态资源的请求。架构如下图:其中,DNS和CDN将在后面的章节中详细介绍。这里我们先了解一下Nginx层。它是如何处理HTTP协议的?对于静态资源,有一个Vanish缓存层。当缓存过期后,才会访问真正的Tomcat应用集群。在HTTP头中,Cache-Control用于控制缓存。当客户端发送的请求中包含max-age命令时,如果判断该资源在缓存层的缓存时间值小于指定的时间值,则客户端可以接受该缓存资源;当指定的max-age值为0时,那么缓存层通常需要将请求转发给应用集群。另外,If-Modified-Since也是一个关于缓存的字段。该字段表示如果服务器的资源在一定时间后更新,客户端应该下载最新的资源;如果没有更新,服务器会返回“304NotModified”响应,客户端不需要下载,节省带宽。至此,我们就拼凑出了HTTP请求的报文格式,然后浏览器会交给传输层HTTP请求的发送HTTP协议是基于TCP协议的,所以它是以面向连接的方式发送请求,以stream二进制流的形式传输给对方。当然,当它到达TCP层时,它会将二进制流变成段,一个一个地发送给服务器。在发送每个报文段时,对方需要一个响应ACK以确保消息可靠地到达该地方。没有响应,则TCP层重传,直到可以到达。同一个数据包可能会发送多次,但HTTP层不需要知道这一点,因为TCP层正在做艰苦的工作。后续传输过程如下:TCP层封装目的地址和源地址。TCP层在发送每条报文时,都需要加上自己的地址和自己想去的地址,把这两个信息放在IP头中,交给IP层传输。IP层获取MAC标头。IP层需要检查目标地址和自己是否在同一个局域网内。如果是,则发送ARP协议请求目标地址对应的MAC地址,然后将源MAC和目标MAC放入MAC头中发送出去;如果不在同一个局域网,则需要发给网关,这里也是通过ARP协议获取网关的MAC地址,然后将源MAC和网关MAC放入MAC头发送出去.网关转发。网关收到报文,发现MAC匹配,取出目标IP地址,根据路由协议找到下一跳路由器,得到下一跳路由器的MAC地址,将报文发送给下一个-跳路由器。数据包到达LAN的目标地址。通过ARP协议获取目标地址的MAC地址,将数据包发送出去。目标地址检查消息并返回ACK。目标机发现数据包中的MAC地址和IP地址与本机匹配,于是根据IP头中的协议类型知道是TCP协议,解析TCP头,得到序号。判断机器是否需要该序列号,如果是,则放入缓存并返回ACK,如果不是,则丢弃。根据端口号将数据包发送到指定的应用程序。TCP头中还有一个端口号,HTTP服务器就是监听这个端口号。因此,目标机器很自然地指定它是一个HTTP服务器。该进程想要这个数据包,所以它将数据包发送到HTTP服务器。HTTP服务器处理请求。HTTP服务器处理请求信息并返回数据给客户端。HTTP返回的构造HTTP的返回报文也有一定的格式,如下图:状态行包含状态码和短语。状态代码反映了HTTP请求的结果。200是好运;404是我们最不想看到的,也就是服务器无法响应这个请求。错误的原因在短语中说明。第一个键值。这里常用到以下字段:Retry-After:客户端应该在多长时间后再次尝试连接;Content-Type:返回数据格式构造完返回的HTTP报文,接下来就是发送报文了。当然还是交给Socket发送,交给TCP,这样TCP返回的HTML就被分成了小的数据段,保证每个段都能安全到达。这些小数据段会加上一个TCP头,然后交给IP层,顺着来的路回去。虽然不一定是完全一样的路径,但是逻辑过程是一样的,一路到客户端。客户端取到数据后,会根据端口号交给指定的程序。这是我们的浏览器准备就绪的时候。浏览器拿到HTTP报文,发现返回了200,一切正常,于是将HTML从文本中取出来,展示了一个炫酷的网页。以上就是正常HTTP请求和返回的完整过程。HTTP2.0上面说了,现在大部分使用HTTP1.1版本,HTTP2.0为了解决一些问题,在1.1的基础上做了一些优化。HTTP1.1在应用层以纯文本进行通信。每次通信都必须携带一个完整的HTTP头,并且无论管道模式如何,每个过程都必须像上面描述的那样来回走动。显然,效率上会有问题。为了解决这些问题,HTTP2.0会对HTTP头部进行一定程度的压缩,在每次必须携带的大量键值对的两端建立索引表,只发送索引中的索引相同标题的表。另外,HTTP2.0协议将一个TCP连接分成多个流,每个流都有自己的ID,流可以从客户端发送到服务器,也可以从服务器发送到客户端。它其实只是一个虚拟通道,此外,它还有优先权。HTTP2.0将所有传输的信息分成更小的消息和帧,并以二进制格式对它们进行编码。常见的帧包括Header帧,用于传输Header内容和开启一个新的流。还有Dataframe,用于传输文字实体,多个Dataframe属于同一个流。通过这两种机制,HTTP2.0客户端可以将多个请求分成不同的流,然后将请求内容拆分成帧进行二进制传输。这些帧可以随机顺序发送,然后根据帧头中的流标识符重新组装,并可以根据优先级决定先处理哪些流数据。对于HTTP2.0,让我们看一个例子。假设我们有一个页面需要发送三个独立的请求,一个获取CSS,一个获取JS,一个获取图片jsg。如果使用HTTP1.1,这三个请求是串行的,但是如果使用HTTP2.0,则可以在一个连接中同时反映多个请求和响应,不需要按顺序一一对应。如上所示。HTTP2.0实际上把三个请求变成了三个流,将数据分成帧,乱序发送到一个TCP连接上。HTTP2.0成功解决了HTTP1.1的队头阻塞问题。同时,通过HTTP1.x的管道机制,无需使用多个TCP连接实现并行请求和响应,减少了TCP连接数对服务器性能的影响,加快了传输速度页面组件。HTTP2.0大大提高了并发性,但是由于TCP协议的顺序处理特性,仍然会出现阻塞问题。还记得我们之前讲过的QUIC协议吗?这是它出现的时候了。它使用以下四种机制来解决TCP的一些问题。QUIC协议机制一:自定义连接机制我们知道一个TCP连接是通过四元组来标识的。一旦元素发生变化,就需要重新连接端口。在移动上网的情况下,当我们切换网络或者信号不稳定的时候,会造成重连,从而增加延迟。TCP没有办法解决以上问题,但是基于UDP协议,QUCI可以在自己的逻辑中维护连接机制。它不再使用四元组来标识,而是使用一个64位的随机数作为标识ID,UDP是不存在的。连接,只要ID保持不变,就不需要重新建立连接。机制二:自定义重传机制TCP为了保证可靠性,利用序号和响应机制解决了序号问题和丢包问题。凡是有序列号的包裹发出去,都必须在一定时间内回复,否则会超时重发。此超时通过对往返时间RTT进行采样来不断调整。其实这个超时时间的采样并不是很准确。如上所示。发送一个序号为100的数据包,超时后再发送一个100。然后收到ACK101。这时候client就知道server收到了100,但是怎么计算往返时间呢?是ACK到达时间减去下100的发送时间,还是减去前100的发送时间?前者算时间短,后者算时间长。QUIC还有一个序列号,它是完全递增的。任何数据包发送一次后,下一个序列号将加一。像我们上面的例子,在QUIC协议中,100的包是不会返回的。再次发送时,序列号为101,如果返回ACK100,则为对第一个数据包的响应。如果返回ACK101,就是对第二个数据包的响应。响应,RTT时间计算比较准确,流程如下:在上面的流程中,可能有童鞋会问,服务端怎么知道两个不同序号的包内容是一样的呢?是的,这确实是一个问题。为了解决这个问题,QUIC协议定义了一个Offset的概念。由于QUIC是面向连接的,所以它就像TCP一样是数据流,发送的数据在这个流中有一个offset,可以通过Offset查看数据发到哪里了,这样只要这个Offset的包没来,就一定要重发。如果来了,会根据Offset拼接成一个stream。机制三:非阻塞多路复用通过自定义连接和重传机制,我们可以解决上面HTTP2.0的多路复用问题。与HTTP2.0一样,可以在同一个QUIC连接上创建多个流来发送多个HTTP请求。更妙的是,QUIC是基于UDP的,一个连接上的多个流之间没有依赖关系。这样,如果stream2丢了一个UDP包,后面又丢了一个stream3的UDP包,虽然stream2的包需要重传,但是stream3的包可以发送给用户,不需要等待。机制四:自定义流量控制????TCP的流量控制是通过滑动窗口协议实现的。QUIC的流量控制也使用window_update来告诉对端它可以接受的字节数。但是QUIC的窗口适配了自己的多路复用机制。它不仅控制连接上的窗口,还控制连接中每个流的窗口。你是否记得?在TCP协议中,接收端窗口的起点是下一个要接收和ACK的数据包。即使后续的包全部到达并放入缓存,窗口也不能向右移动,因为TCP的ACK机制是基于序号的累加响应的,一旦一个序号被ACK了,就意味着前一个已经到了,所以只要前面的没有到达,即使后面的到达了,也不能ACK,会导致后面的到达,还可能超时重传,浪费带宽。QUIC的ACK是基于offset的。当每个offset数据包到来时,进入缓存后即可得到应答。回答后,不会被反感。position是当前接收到的最大偏移量,从这个偏移量到当前流能容纳的最大缓冲区,就是真正的窗口大小,显然,这样更准确。另外还有就是整个连接窗口,需要对所有的流窗口做一个统计。总结HTTP协议虽然很常用,但是也很复杂。我们只需要重点记住GET、POST、PUT、DELETE方法,以及重要的头字段即可;HTTP2.0使用标头压缩、分帧、二进制编码和多路复用。使用其他技术来提高性能;QUIC协议通过基于UDP的自定义类TCP连接、重试、多路复用和流量控制技术进一步提高了性能。参考:TCP/IP指南;百度百科——HTTP入门;刘超-网络协议趣谈系列;
