从HTTP 2.0想到的关于传输层协议的一些事

0.HTTP协议的历史

我也不知道...

1.关于HTTP 2.0

收到了订阅的邮件,头版是说HTTP 2.0的内容,我本人不是非常关注HTTP这一块儿。可是闲得无聊时也会瞟两眼的。HTTP 2.0的最大改进我认为有两点:
第一:新增了帧层
帧层的优点在于又一次分发流信息,server处理顺序能够不再依赖用户提交请求的顺序了。另外就是不必一定用TCP传输HTTP了。实际上规范一開始就是这么说的。
第二:HTTP头的内容能够增量交互了
非常多的HTTP头里面的信息都是參数的协商,每次都要携带,如key/value的形式,HTTP 2.0改变了,它仅仅携带新增的或者变化的内容。

既然HTTP是基于请求/应答会话的,那么何必不把參数保存在会话中呢?相似SSL/TLS的方式。即便是IPSec这样的无连接的协议族,也仅仅是每次传输一个SPI索引而已。

2.不须要设计一种新的传输层协议

在传输层,仅仅有TCP和UDP就够了。假设想扩展,那就在UDP上扩展好了。

3.几个关于TCP和UDP的误区

在协议改造之前,首先要澄清几个误区,那就是你要知道为何要使用UDP而不再使用TCP,或者反过来,你始终认为TCP比UDP好:

误区一:UDP更高效

非常多人以为是由于UDP效率高,由于不须要ACK。这最有可能是从教科书上学到的。要么就是老师亲口告诉你的,可是看看你立即就要做的事,让UDP变得可靠,按序到达。那么除非你发明一种不要ACK的按序到达机制,否则你的ACK机制可能比TCP的更低效。

误区二:TCP更加健壮防攻击

这是彻头彻尾的误区,TCP协议头里面有什么保证了安全?校验和吗?大多数情况是IP层先替你挡了一刀而已,假设IP层去掉了防火墙。校验和,手无寸铁的TCP将面对什么!序列号吗?一个32位的数字而已。想让对端接收你伪造的TCP段,你仅仅须要构造一个序列号在合理窗体范围内且匹配五元组的段就可以。事实上,TCP的协议规范中从来就没有安全机制,尽管你确实能够在TCP选项中放一些和PKI体系相关的东西。

误区三:TCP和UDP都不安全

这实际上是一个伪命题。问题根本就不在点子上。可能IPv6的出现更加助长了持这样的观点的人的威风。TCP也好。UDP也好。仅仅是一个传输层协议。它提供的是端到端的节点通信,在IP之上提供应用的多路复用机制,而已。

安全不是它的职责,当然也不是IP层的职责。依照严格分层模型,身份认证属于会话层的职责,而数据完整性则是表示层的职责。


误区四:TCP的低效在于其握手。慢启动等协商反馈机制

相反。基于反馈的慢启动和高速重传/高速恢复都是合理的,它们的目的正是探測端到端路径的传输能力,TCP没有使用显式的NAK机制。而将丢包视为拥塞,将连续收到同样ACK视为丢包。这一环套一环的反馈机制已经是TCP能够使用的最高效的方式了,更高效的方式当然是丢包点或者拥堵点直接发送源抑制反馈信号,但那样会破坏简单性原则。有人为了降低TCP短连接的握手开销,发明了TCP重用,即TCP高速打开机制。使得在SYN包中都能够携带数据。这并非一种好方式,为何要在同样两台端到端主机之间频繁开启短连接呢?为何不在期间开一条长连接呢,然后将短连接的应用请求附着而上呢?用长连接平滑掉握手开销才是正解,握手是为了协商參数,这点开销都承受不起,免费的午餐一定是剩饭!

难道所谓能重用的连接不是曾经遗留下来的吗?
       HTTP 1.1支持长连接,可是支持的不够好。每个请求必须依照请求发出的顺序被回应。假设两个请求是关联性非常小的请求,第一个请求的处理延迟使得第二个请求被连累,这是伤不起的。这怎样解释??个人认为这是传输层协议的使用方式不正确的问题而不是协议设计的问题,传输层仅仅是在整个层次上提供了上层的多路复用机制。而这个上层并非说一定就得是应用层。谁让你把两个GET请求塞到一个TCP连接了啊。好吧,那你能够用两条TCP连接,也是,全然能够用多条TCP连接!可是还是有问题,问题在哪儿呢?我们来看下应用的模式。


4.应用模式的演进

最初的史前年代,是没有所谓的应用的,怎样非得说有。那就是两类,一类是文件下载,另一类就是终端登录,看看那些著名的史前协议吧,FTP,Telnet...TCP当时和IP是在一起的,并没有分成两个层级。也没有必要分开,由于无论是文件下载还是登录,都须要严格的数据按序到达,那时的网络事实上就是一个数据精确按序到达的网络。但当它们发展到第三个版本号的时候。它们分手了,至于说它们为什么分手。说好听点就是分层模型的每一层仅仅负责一类处理的职责原则,说得不那么好听但非常easy理解一点就是出现了小三。由于当时出现了一种需求。并不须要提供按序到达的机制。但要提供数据边界,属于一种发后无论的需求,非常多的控制报文的投递属于这一种,它们不须要一个长连接,但须要数据边界。即一个报文从哪里開始在哪里结束,这些控制报文本身提供额外的校验机制,并不须要网络的反馈。显然TCP-IP无法满足这些需求,于是IP就纳妾了,从而变成了TCP/IP,实际上是(TCP-UDP)/IP。
       依照抢先性原则,以后的应用要么使用TCP,要么使用UDP,也有非常多不须要多路复用的直接使用IP,比方非常多路由协议。随着应用的蓬勃发展,一直到HTTP 2.0之前,TCP和UDP都能应对,即便出现了非常多的效率问题。但整体上都有解决办法。

可是HTTP 2.0带来一个全新的时代,一切和曾经都不同了。
       一个应用要请求一台主机上的非常多资源。依照以往对会话的理解,须要建立多个会话,可是为了效率,提倡共享一条TCP连接。HTTP 2.0和以往不同的是,它增加了一个新的帧层,将每个HTTP数据包封装在一个帧中,每个帧都携带一个流标号,这就意味着HTTP的回应不必依照请求的顺序处理了,流标号能够识别一个HTTP回应相应哪个请求。然后问题还是存在。只是是出在了TCP层。

尽管应用server不必再依照请求顺序将处理排队。可是由于TCP连接是严格按序到达的。因此万一有丢包。其兴许的数据将全部被堵塞。直到丢包到达。这是TCP一直都存在的颠簸问题。玩过祖马小游戏的都应该体会过。

在信号不稳定的无线环境下,一个TCP流的颠簸带来的问题是全面且严重的。HTTP 2.0 over TCP带来的问题是TCP的堵塞取代了应用server的堵塞。那么怎么解决呢?

5.会话传输层设想

假设能有一个端到端协议,每个连接占领一个五元组,在该连接上提供帧边界的按序可靠到达,或者在该连接上复用多个流。每个流上按序可靠到达,这不就攻克了HTTP 2.0的问题了吗?同一时候解决的还有port资源占用的问题,再也不用为port不够用犯愁了。


       OSI模型中有会话层。可是TCP/IP模型没有。实际上这个层还是必要的,假设HTTP 2.0承载于会话层而不是传输层,那么传输层就能够用轻量级的UDP了,仅仅要在会话层确保每个会话按序处理就可以,假设承载于传输层,那么对于TCP而言,一个WEB应用的全部会话都要共享一个TCP连接,在按序处理上造成了互相牵制。

6.实现思路与措施

不知道你对OpenVPN的Reliable层有没有了解。这是一个活生生的样例。经过我个人的改造,在它之上实现一个基于UDP的多个流多路复用是非常easy的,协议封装图例如以下:
基于Reliable层的stream1----基于Reliable层的stream2----基于Reliable层的stream3
----------------------------------------------------------------------------------------------------------------------------
                          UDP协议(我使用了一对OpenSSL的BIO来取代)
----------------------------------------------------------------------------------------------------------------------------
依照上图,假设stream2中的一个中间数据包没有到达,它仅仅会堵塞stream2的兴许数据包向应用提交,而stream1和stream3的数据包即便是在没有到达的stream2的数据包后面到达的,仅仅要是按序的。就能够尽快提交给应用层。我的这个stream的协议头非常easy,为了方便就两个字段。一个标识session ID。一个标识长度,事实上这个长度也是不须要的。由于Reliable层中本来就有长度。

依照这个思想,另一个引申出的协议,即依照边界标识来提供按序到达的语义,此时协议头中的长度字段就是必须的了。这个引申的意义是,我仅仅要保证协议头中指示的长度为length的这些数据按序到达就可以。假设这样,每个数据包都须要一个序列号,可是仅仅有length范围内的序列号用于按序到达语义。假设序列号落到了length范围内,则依照按序语义处理,否则直接提交给应用。

在我的測试中,我没有提供性能优化措施,我仅仅是调通了而已,由于我认为优化总是有办法的,再说用BIO来模拟丢包也不是非常准确。
      之所以使用OpenVPN的Reliable层。不是由于它有多么好,而是我比較熟悉它而已,据我对世界历史的了解,轮子仅仅在美索不达米亚被发明过一次,借用总比又一次来一遍要好。整个过程就是对OpenVPN的裁剪过程。终于剩下的C文件是:
buffer.c  error.c  interval.c  list.c  mbuf.c  memcmp.c  otime.c  packet_id.c  reliable.c  schedule.c  session_id.c  ssl.c
相应的H文件和Makefile也要改,我仅仅是在bio_read/write和Reliable层之间加了一个复用的机制而已。BIO真是一个好东西,模拟UDP再好只是了,

6.看到些曙光了吗

写到这里。我有点脱离HTTP 2.0了,事实上本文的大部分都不是在说HTTP 2.0,而全然是针对TCP的,HTTP 2.0执行于TCP之上真的不合适了,由于TCP的逻辑太简单又太复杂。说它简单是由于它依旧和几十年前一样,仅仅适合于文件传输和远程长连接登录。说它复杂是由于针对这两类需求以及少量的这两类需求之外,TCP衍生了各种复杂的基于反馈的算法,要知道,仅仅要是反馈系统就是复杂的。动物正是有了负反馈系统而和植物区分开来!

因此。假设应用逻辑越来越复杂的时候,比方现在的各类WEB应用,TCP是否能作为一匹好马就值得怀疑了。HTTP 2.0正是为了应对复杂的WEB应用而出世的,绝不能被TCP牵绊住,总的来讲,TCP太重了,HTTP 2.0同样也不轻,TCP的诸多优化并非针对WEB的。举一个最简单的样例,TCP的目标是填充带宽延时乘机的管道,为此它和端到端的流控机制有了冲突。接收端仅仅能够接收N个数据,难道发送端仅仅能发N个吗?要知道N个数据还要在网络上跑一会儿呢!所以发送端应该发送的是N*延时个数据。可是假设延时非常长的话,你能保证延时期间接收端的接收窗体保持不变吗?特别在复杂WEB应用环境下。这个接收窗体转瞬即变。因此又须要反馈...假设是用于文件传输。就没有问题了,由于文件传输的目的是尽量填满整个带宽,实现最大吞吐量。
       好了,说了这么多TCP怎么不适合HTTP 2.0,那么我的上面的想法就一定好吗?我不敢说,可是我认为起码攻克了几个问题。第一个就是你不用建立多个连接了(这实际上是HTTP 2.0解决的问题,可是本文不限于HTTP 2.0)。要知道对于同一个目标,你的机器上的每个IP地址仅仅能使用65535个port,这不是你的机器的限制,而是协议的限制,在带宽稀缺且数据量不大(载荷率非常低)的年代。协议头里面的16位port字段要比32port字段节省非常多的带宽!或许你会认为。你在一个port上复用多个session ID,道理不也一样吗? 难道session ID就不怕浪费吗?是的,它也不是取之不尽的,问题的关键来了,这个关键可能推翻IPv6的糟糕架构!
       问题是你愿意横向扩展还是愿意纵向扩展。我不得不用IP层的一个现实来说明问题,因此我不得不扯到IP层。以IPv4为例,它的地址空间总是被认为有限的,人们一直操心它会耗尽,于是IPv6的思想之中的一个非常easy,那就是加大地址空间。一下子变成了128位。说什么地球上每一平方能够拥有多少地址之类的宣传性言语,其无聊性不亚于经济大萧条时美国总统承诺每人锅里面有一仅仅鸡!

这么一来IPv6是攻克了问题,可是协议栈不兼容了。整个IP层要重写,在重写其间须要截断整个上下层的通信。可是这是不能截断的。由于IP眼下基本成了全部通信的必经之路,这个非常不像修路时那样。能够向左向右改道,IP实际上是一个双向2车道的路径。


       有没有办法呢?有!

那就是LISP的思想。即将IP分成两层,外层标识位置,内层标识设备,这样是不是更好呢?相似IPv6的那种宣传,LISP能够说,每个位置都能够有2的32次方个IP地址。是不是更诱人呢?毕竟并没有说一个位置有多大。!

和IPv6不同,实现这个非常easy,仅仅须要提供一个兼容层就可以,过渡非常方便,假设支持LISP,那就处理,假设不支持,那就剥掉外衣直通。注意,我这里说的可不是标准的LISP,而是基于LISP思想而对IPv4的改进。
       回到TCP/UDP的问题,我们假设採用相似的思想,是不是也不错呢?假设一个session ID为32位,那么一个UDPport就能够承载2的32次方个session!全然满足了当前复杂的应用需求,应用能够连接同一个UDP端点。可是发送不同的业务数据,每个业务或者说事务都是session独立的,互不影响。


原文地址:https://www.cnblogs.com/lxjshuju/p/7067261.html