HTTP:连接管理 野性酷女 2022-08-30 11:51 122阅读 0赞 # TCP连接 # 世界上几乎所有的HTTP通信都是TCP/IP承载的,客户端应用程序可以打开一条TCP/IP连接,连接到可能运行在世界任何地方的服务器应用程序。一旦连接建立起来了,在客户端和服务器的计算机之间交换的报文就永远不会丢失、受损或者失序。 ## TCP的可靠数据传输 ## HTTP连接实际上就是TCP连接和一些使用连接的规则。TCP连接是可靠的 > TCP为HTTP提供了一条**可靠的比特传输管道**。从TCP连接一端填入的字节会从另一端以原有的顺序、正确的传输出来 ## TCP流是分段的、由IP分组发送的 ## TCP的数据时通过叫做**IP分组**(即`IP数据报`)的小数据块来发送的。 ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3poaXpoZW5nZ3Vhbg_size_16_color_FFFFFF_t_70] HTTP要传送一条报文时,会以流的形式将报文数据的内容通过一条打开的TCP连接按序传输。TCP收到数据流之后,会将数据流砍成被称为段的小数据块,并将段封装在IP分组中,通过因特网进行传输,如下图所示。所有这些工作但是有TCP/IP软件来处理的,HTTP程序员什么都看不到。 ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3poaXpoZW5nZ3Vhbg_size_16_color_FFFFFF_t_70 1] 每个TCP段都是由IP分组承载的,从一个IP地址发送到另一个IP地址的。每个IP分组中都包括: * 一个IP分组首部(通常为20字节) * 一个TCP段首部(通常为20字节) * 一个TCP数据块(0或者多个字节) IP首部包含了源和目的IP地址、长度和其他一些标记。TCP段的首部包含了TCP端口号、TCP控制标记,以及用于数据排序和完整性检查的一些数字值。 ## 保持TCP连接的正确运行 ## 在任意时刻计算机都可以有几条TCP连接出于打开状态。**TCP是通过端口号来保持所有这些连接的正确运行的**。 TCP连接是通过下面四个值来识别的: `<源IP地址、源端口号、目的IP地址、目的端口号>` 这四个值一起唯一的定义了一条连接。两台不同的TCP连接不同拥有四个完全相同的值。 ## 用TCP套接字编程 ## 操作系统提供了一些操纵其TCP连接的工具。如下,这些套接字API向HTTP程序员隐藏了TCP和IP的所有细节。 ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3poaXpoZW5nZ3Vhbg_size_16_color_FFFFFF_t_70 2] 套接字API允许用户创建TCP的端口数据结构,将这些端点与远程服务器的TCP端点进行连接,并对数据流进行读写。TCP API隐藏了所有底层网络协议的握手细节,以及TCP数据流与IP分组之间的分段和重装系统。 ## 对TCP性能的考虑 ## HTTP紧挨着TCP,位于其上层,所以HTTP事务的性能在很大程序上取决于底层TCP通道的性能。 ### HTTP事务的时延 ### 我们来回顾一下,在HTTP请求的过程中会出现哪些网络时延,并以此开始我们的TCP性能之旅。 ![在这里插入图片描述][f1506f82448c894355e468720defc513.png] 注意,与建立TCP连接,以及传输请求和响应报文的时间相比,事务处理时间可能是很短的。除非客户端或者服务器超载,或者正在处理复杂的动态资源,否则HTTP时延就是TCP网络时延造成的。 HTTP事务的时延有如下几种主要原因: * 通过DNE解析系统将URI的主机名转换成一个IP地址可能要花费数十秒的时间。但是不会造成问题,因为大部分HTTP客户端都会有一个小的DNS缓存。 * 客户端会向服务器发送一条TCP连接请求,并等待服务器会送一个请求接收应答。每条新的TCP连接都会有连接建立时延。这个值通常最多只有一两秒,但是如何有上百个HTTP事务的话,这个值会快速的叠加上去。 * 一旦连接建立起来了,客户端就会通过新连接的TCP管道来发送HTTP请求。数据到达时,Web服务器会从TCP连接中读取请求报文,并对请求进行处理。因特网传输请求报文以及服务器处理请求报文都需要时间 * Web服务器发送HTTP响应也需要时间 这些TCP网络时延的大小取决于硬件速度、网络和服务器的负载,请求和响应报文的尺寸,以及客户端和服务器之间的距离。 ### 性能聚焦区域 ### 下面将列出一些会对HTTP程序员产生影响的、最常见的TCP相关时延,其中包括: * TCP连接建立握手 * TCP慢启动拥塞控制 * 数据聚焦的Nagle算法 * 用于捎带确认的TCP延迟确认算法 * TIME\_WAIT时延和端口耗尽 #### TCP连接的握手时延 #### 建立一条新的TCP连接时,甚至是在发送任意数据之前,TCP软件之间会交换一系列的IP分组,对连接的有关参数进行沟通。如果连接只用来传送少量数据,这些交换过程就会严重减低HTTP的性能。 ![在这里插入图片描述][92cfeab1521305b12dd47f9ec2b308ed.png] TCP连接握手需要经过如下几个步骤: * 请求新的TCP连接时,客户端要向服务器发送一个小的TCP分组(通常是40-60个字节)。这个分组中设置了一个特殊的SYN标记,什么这是一个连接请求。(上图a) * 如果服务器接收了连接,就会对一些连接参数进行计算,并向客户端会送一个TCP分组,这个分组中的SYN和ACK标记都被置位,说明连接请求已经被接受。(上图b) * 最后,客户端向服务器会送一条确认消息,通知它链接已经成功建立(上图c)。现在的TCP栈都运行客户端在这个确认分组中发送数据。 HTTP程序员永远不会看到这些分组-----这些分组都是由TCP/IP软件管理,对其是不可见的。HTTP程序员看到的只是创建TCP链接时存在的时延。 通常的HTTP事务都不会交换太多数据,此时,SYN/SYN+ACK握手会产生一个可测量的时延。TCP连接的ACK分组通常都足够大 (因特网流量中的IP分组通常是几百字节,本地流量中的IP分组为1500字节左右),可以承载整个HTTP请求报文,而且很多HTTP服务器响应报文都可以放入一个IP分组中去 最后的结果是,小的HTTP事务可能在TCP建立上花费50%或者更多的时间。可以通过**重用现存连接****来减少这种TCP建立时延所造成的影响** #### 延迟确认 #### 由于因特网自身无法确保可靠的分组传输(路由器超负荷的话,可以随意丢弃分组),所以TCP实现了自己的确认机制来确保数据的成功传输。 每个TCP段都有一个序列号和数据完整性校验和。每个段的接受者收到完好的段时,都会向发送者会送小的确认分组。如果发送者没有在指定的窗口时间内收到确认信息,发送者就认为分组已经被破坏或者损毁,并重发数据。 由于确认报文很小,所以TCP允许在发往相同方向的输出数据分组中对其进行“捎带”。TCP将返回的确认信息与输出的数据分组结合在一起,可以更有效的利用网络。为了增加确认报文找到同向传输数据分组的可能性,很多TCP栈都实现了一种“延迟确认”算法。延迟确认算法会在一个特定的时间窗口(通常是100~200毫秒)内将输出确认存放在缓冲区中,以寻找能够捎带它的输出数据分组。如果在那个时间段内没有输出数据分组,就将确认信息放在单独的分组中传送。 **但是,HTTP具有双峰特性的请求-应答行为降低了捎带信息的可能。当希望有相反方向会送分组的时候的机会没有那么多。通常,延迟确认算法会引入相当大的时延,根据所使用的操作系统的不同,可以调整或者禁止延迟确认算法**。 #### TCP慢启动 #### > 最初的TCP的实现方式是,在连接建立成功后便会向网络中发送大尺寸的数据包,假如网络出现问题,很多这样的大包会积攒在路由器上,很容易导致网络中路由器缓存空间耗尽,从而发生拥塞。因此现在的TCP协议规定了,新建立的连接不能够一开始就发送大尺寸的数据包,而只能从一个小尺寸的包开始发送,在发送和数据被对方确认的过程中去计算对方的接收速度,来逐步增加每次发送的数据量(最后到达一个稳定的值,进入高速传输阶段。相应的,慢启动过程中,TCP通道处在低速传输阶段),以避免上述现象的发生。这个策略就是慢启动。 TCP数据传输的性能还取决于TCP连接的使用期。TCP连接会随着时间进行自我“调整”,起初会限制连接的最大速度,如果数据成功传输,会随着时间的推移提高传输的速度。这种调谐被称为**TCP慢启动**,用于防止因特网的突然过载或者拥塞。 TCP慢启动限制了一个TCP端点在任意时刻可以传输的分组数。简单来说,每成功接收一个分组,发送端就有了发送另外两个分组的权限。如果某个HTTP事务有大量的数据要发送,是不能一次将所有分组都发送出去的,必须发送一个分组,等待确认;然后可以发送两个分组,每个分组都必须被确认,这样就可以发送四个分组了,依次类推。这种方式被称为**打开拥塞窗口** 由于存在这种拥塞控制特性,所以新连接的传输速度会比已经交换过一定量数据的“已调整”的连接慢一些。由于已调整连接更快一些,所以HTTP中有一些可以重用现存连接的工具,比如“持久连接” #### Nagle算法与TCP\_NODELAY #### TCP有一个数据流接口,应用程序可以通过它将任意尺寸的数据放入TCP栈中----即使一次只放一个字节也可以(!!!造成“发送端傻窗口综合征”)。但是,每个TCP段中都至少装载了40个字节的标记和首部,所以如果TCP发送了大量包含少量数据的分组,网络的性能就会严重下降, Nagle算法试图在发送一个分组之前,将大量的TCP数据绑定在一起,以提高网络效率。 Nagle算法鼓励发送全尺寸的段,只有当所有其他分组都被确认之后,Nagle算法才允许发送非全尺寸的分组。如果其他分组仍然在传输过程中,就将那部分数据缓存起来。只有当挂起分组被确认,或者缓存中积累了足够发送一个全尺寸分组的数据时,才会将缓存的数据发送出去。 Nagle算法会引发几种HTTP性能问题。首先,小的HTTP报文可能无法填满一个分组,可能会因为等待哪些永远不会到达的额外数据而产生时延。其次,Nagle算法与延迟确认之间的交互存在问题------Nagle算法会阻止数据的发送,直到有确认分组到达为止。但确认分组自身会被延迟确认算法延迟100-200ms,当使用管道化连接时这些问题可能会更加严重,因为客户端可能会有多条报文要发送给同一个服务器,而且不希望有时延存在。 HTTP应用程序常常会在自己的栈中设置参数TCP\_NODELAY,禁用Nagle算法,提高性能。如果这样做的话,一定要确保会向TCP写入大块的数据,这样就不会产生一堆小分组了。 #### TIME\_WAIT累积和端口耗尽 #### TIME\_WAIT端口耗尽是很严重的性能问题,会影响到性能基准,但在显示中相对较少出现。大部分遇到性能基准问题的人最终都会碰到这个问题,而且性能都会变得出乎意料的差,所以这个问题值得关注。 当某个TCP端点关闭TCP连接时,会在内存中维护一个小的控制块,用来记录最近所关闭的IP地址和端口号。这些信息只会维持一小段时间,通常是2MSL(所估计的最大分段使用期的两倍)左右,以确保在这段时间内不会创建具有相同地址和端口号的新连接。实际上,这个算法可以防止在两分钟内创建、关闭并重新创建两个具有相同IP地址和端口号的连接。 现在高速路由器的使用,使得重复分组几乎不可能在连接关闭的几分钟之后,出现在服务器上。有些操作系统会将2MSL设置为一个较小的值,但超过此值时要特别注意。分组确实会被复制,如果来自之前连接的复制分组插入了具有相同连接值的新TCP流,会破坏TCP数据。 2MSL的连接关闭延迟通常不是什么问题,但在性能基准环境下就可能会成为一个问题。进行性能基准测试时,通常只有一台或者几台用来产生流量的计算机链接到某系统中去,这就限制了连接到服务器的客户端IP地址数。而且,服务器通常会在HTTP的默认TCP端口80上进行监听。同TIME\_WAIT防止端口号重用时,这些情况也限制了可用的连接值的组合。 在只有一个客户端和一台Web服务器的异常情况下,构建一条TCP连接的4个值: `<源IP地址、源端口号、目的IP地址、目的端口号>` 其中的3个都是固定的------只有源端口号可以随意改变。 每次客户端连接到服务器上时,都会获取一个新的源端口号,以实现连接的唯一性。但由于可用的源端口的数量有限(比如60000个),而且在2MSLb(比如120s)内连接是无法重用的,连接率就被限制在60000/120=500次/秒。**如果要确保不遇到TIME\_WAIT端口耗尽问题,要么优化服务器确保服务器的连接率不高于500次/秒,要么增加客户端负载生成机器的数量,或者确保客户段和服务器在循环使用几个虚拟IP地址以增加更多的连接组合**。 即使没有遇到端口耗尽问题,也要特别小心有大量连接出于打开状态的情况,或者为出于等待状态的连接分配大量控制块的情况。在有大量打开链接或控制块的情况下,有些操作系统的速度会变慢。 # HTTP的连接处理 # 上面我们讨论了TCP连接以及性能相关,接下来我们来讨论操作和HTTP连接优化技术 ## 常被误解的Connection首部 ## > ??? HTTP允许在客户端和最终的源端服务器之间存在一串HTTP中间实体(代理、高速缓存等)。可以从客户端开始,逐跳的将HTTP报文经过这些中间设备,转发到源端服务器上去(或者进行反向传输)。 在某些情况下,两个相邻的HTTP应用程序会为它们共享的连接应用一组选项。HTTP的`Connection`首部字段中有一个由`逗号`分隔的**连接标签**列表,这些标签为此连接指定了一些不会传输到其他连接中去的选项。比如,可以用`Connection:close`来说明发送完吓一跳报文之后必须关闭的连接。 Connection首部可以承载三种不同类型的标签,因此有时会很令人费解: * HTTP首部字段名,列出了只与此连接有关的首部 * 任意标签值,用于描述此连接的非标准选项 * 值clos,说明操作完成之后需要关闭这条持久连接 如果连接标签中包含了一个HTTP首部字段的名称,那么这个首部字段就包含了与一些连接有关的信息,不能将其转发出去。在将报文转发出去之前,必须删除Connection首部列出的所有首部字段。 ![在这里插入图片描述][b3e7ffeeccd3813868a3e04bd41176f7.png] HTTP应用程序收到一条带有Connection首部的报文时,接收端会解析发送端请求的所有选项,并将其应用。然后会在将此报文转发给下一跳地址之前,删除Connection首部以及Connection中列出的所有首部 ## 串行事务处理时延 ## 如果只对连接进行简单的管理,TCP的性能时延可能会叠加起来。比如,假设有一个包含了三个嵌入图片的Web页面。浏览器需要发起四个HTTP事务来显示此页面:一个用于顶层的HTML页面,三个用于嵌入的图片。如果每个事务多需要(串行的建立)一条心连接,那么连接时延和慢启动时延就会叠加起来 ![在这里插入图片描述][d8a94827302fc1a3ba3b6a9b224a6cac.png] 解决方法: * 并行连接:**通过多条TCP连接发起并发的HTTP请求** * 持久连接:**重用TCP连接,以消除连接及关闭时延** * 管道化连接:**通过共享的TCP连接发起并发的HTTP请求** * 复用的连接:交替传送的请求和响应报文 # 并行连接 # HTTP允许客户端打开多条连接,并行的执行多个HTTP事务。也就是说,每个事务都有自己的连接。 ![在这里插入图片描述][f2d749ca3ce81000e17d011546c845b5.png] 由于软件开销的存在,每个连接请求之间总是会有一些小的时延,但连接请求和传输时间基本上都是重叠起来的 注意,即使并行连接的速度可能会更快,但并不一定总是更快: * 客户端的网络带宽不足时,大部分的时间可能都是用来传输数据的。此时,如果并行加载多个对象,每个对象都会竞争这有限的带宽,每个对象都会以较慢的速度按比例加载,这样带来的性能提升就很小了 * 打开大量连接会消耗很多内存资源,从而引发自身的性能问题。 实际上,浏览器确实使用了并行连接,但它们会将并行连接的总是限制在一个较小的值(通常是4个)。服务器可以随意关闭来自特定客户端的超量连接。 # 持久连接 # Web客户端经常会打开到同一个站点的连接。比如,一个Web页面上的大部分内嵌图片通常来自同一个站点,而且相当一部分指向其他对象的超连接通常都指向同一个站点。因此,初始化了对某服务器HTTP请求的应用程序很可能会在不久的将来对那台服务器发起更多的请求。这种性质被称为**站点本地性**(site locality) 因此,HTTP/1.1允许HTTP设备在事务处理结束之后将TCP连接保持在打开状态,以便为未来的HTTP请求重用现存的连接。**在事务处理结束之后仍然保持在打开状态的TCP连接称为持久连接。非持久连接会在每个事务结束之后关闭。持久连接会在不同事务之间保持打开状态,直到客户端或者服务器决定将其关闭为止**。 重用已对目标服务器打开的空闲持久连接,就可以避开缓慢的连接建立阶段。而且,已经打开的连接还可以避免慢启动的拥塞适应阶段,以便更快的进行数据的传输。 ## 持久 VS 并行 ## 并行连接可以提高复合页面的传输速度,但也有缺点: * 每个事务都会打开/关闭一条新的连接,会耗费时间和带宽 * 由于TCP慢启动特性的存在,每条新连接的性能都会有所降低 * 可打开的并行连接数量实际上是有上限的 持久连接有一些比并行连接更好的地方。 * 持久连接降低了时延和连接建立的开销 * 将连接保持在已调谐状态,而且减少了打开连接的潜在数量 但是,管理持久连接是要特别小心,不然会累积出大量的空闲连接,耗费本地以及远程客户端和服务器上的资源。 持久连接和并行连接配合使用可能是最高效的方式。现在,很多Web应用程序都会打开少量并行连接,其中的每一个都是持久连接。 持久连接有两种类型: * 比较老的HTTP/1.0+ "keep-alive"连接 * 现代的HTTP/1.1 "persistent"连接 ## HTTP/1.0+ "keep-alive"连接 ## keep-alive连接是一种早期实验性持久连接。这些早期的持久连接受到了一些互操作性设计方面的困扰,这些问题在后期的HTTP/1.1版本中都得到了修正,但是还是由很多客户端和服务器在使用这些早期的keep-alive连接。 如下,keep-alive连接去除了进行连接和关闭连接的开销 ![在这里插入图片描述][d8094d7588de9ffaf6a047f90ae2f707.png] ## Keep-Alive操作 ## keep-alive已经不再使用了,而且在当前的HTTP/1.1规范中也没有对它进行说明了。但是浏览器和服务器对keep-alive握手的使用仍然相关广泛,因此,HTTP的实现着应该做好与之进行交互操作的准备。现在我们来快速浏览一下keep-alive的操作 采用HTTP/1.1 keep-alive连接的客户端可以通过包含`Connection: Keep-Alive`首部请求将一条连接保持在打开状态 如果服务器愿意为下一条请求将连接保持在打开状态,就在响应中包含相同的首部,如下图所示。如果响应中没有`Connection: Keep-Alive`首部,会在发回响应报文后关闭连接。 ![在这里插入图片描述][b3d595eae8ad01bef814bfbdcc753a76.png] ## Keep-Alive选项 ## 注意,Keep-Alive首部只是请求将连接保持在活跃状态。发出keep-alive请求之后,客户端和服务器并不一定同意进行keep-alive会话。它们可以在任意时刻关闭空闲的keep-alive连接,并可以随意限制keep-alive连接所处理的事务的数量 可以用Keep-Alive通用首部中指定的,由`逗号`分隔的选项来调节keep-alive的行为: * 参数timeout是在Keep-Alive响应首部发送的。它估计了服务器希望将连接保持在活跃状态的时间。这并不是一个承诺值 * 参数max是在Keep-Alive响应首部发送的,它估计了服务器还希望为多少个事务保持此连接的活跃状态。这并不是一个承诺值 * Keep-Alive首部还可支持任意未经处理的属性,这些属性主要用于诊断和调试。语法为`name [=value]` Keep-Alive首部完全是可选的,但只有在提供`Connection:Keep-Alive`是才能使用它。 看个例子:下面说明了服务器最多还会为另外5个事务保持连接的打开状态,或者将打开状态保持到链接空闲了2分钟之后 Connection:Keep-Alive Keep-Alive:max=5, timeout=20 ## Keep-Alive连接的限制和规则 ## * 在HTTP/1.0中,keep-alive并不是默认使用的。客户端必须发送一个`Connection:Keep-Alive`请求首部来激活keep-alive连接 * `Connection:Keep-Alive`首部必须随所有希望保持持久连接的报文一起发送。如果客户端没有发送`Connection:Keep-Alive`首部,服务器就会在那条请求之后关闭连接。 * `Connection:Keep-Alive`首部必须随所有希望保持持久连接的报文一起发送。如果客户端没有发送Connection:Keep-Alive首部,服务器就会在那条请求之后关闭连接 * 客户端探明响应中没有Connection:Keep-Alive响应首部,就可以知道服务器发出响应之后是否会关闭连接了 * 只有在无需检测到连接的关闭即可确定报文实体部分长度的情况下,才能将连接保持在打开状态------也就是说实体的主体部分必须有正确的`Content-Length`,有多部件媒体类型,或者用分块传输编码的方式进行了编码。在一条keep-alive信号中回送错误的Content-Length是很糟糕的事情,这样的话,事务处理的另一端就无法精确地检测出一条报文的结束和另一条报文的开始了 * 代理和网关必须执行Connection首部的规则。代理和网关必须在将报文转发出去或者将其高速缓存之前,删除在Connection首部中命名的所有首部字段以及Connection首部自身 * 严格来说,不应该与无法确定是否支持Connection首部的代理服务器建立keep-alive连接,以防止出现**哑代理问题** * 从技术上讲,应该忽略所有来自HTTP/1.0设备的Connection首部字段(包括Connection:Keep-Alive),因为它们可能是由比较老的代理服务器误转发的 * 除非重复发送请求会产生其他一些副作用,否则如果在客户端收到完整的响应之前连接就关闭了,客户端一定要做好重试请求的准备。 ## HTTP/1.1持久连接 ## HTTP/1.1逐渐停止了对keep-alive连接的支持,用一种名为**持久连接**的改进型设计取代了它。持久连接的目的与keep-alive连接的目的相同,但是工作机制更优一些。 ### 机制 ### **与HTTP/1.0的keep-alive连接不同,HTTP/1.1持久连接在默认情况下是激活的。除非特别指明,否则HTTP/1.1假定所有连接都是持久的。要在事务处理结束之后将连接关闭,HTTP/1.1应用程序必须向报文中显示的添加一个`Connection:close`首部**。这是与以前的HTTP协议很重要的区别,在以前的版本中,keep-alive连接要么是可选的,要么根本不支持。 **HTTP/1.1客户端假定在收到响应后,除非响应中包含了`Connection:close`首部,不然HTTP/1.1连接就仍维持在打开状态。但是,客户端和服务器仍然可以随时关闭空闲的连接。不发送`Connection:close`并不意味着服务器承诺永远将连接保持在打开状态** ### 限制和规则 ### * 发送了`Connection:close`请求首部之后,客户端就无法在那条连接上发送更多的请求了 * 如果客户端不想在连接上发送其他请求了,就应该在最后一条请求中发送一个`Connection:close`请求首部 * **只有当连接上所有的报文都是正确的、自定义报文长度时----也就是说,实体主体部分的长度都和相应的Content-Length一致,或者是用分块传输编码方式编码的-----连接才能持久保持** * HTTP/1.1的代理必须能够分别管理与客户端和服务器的持久连接------每个持久连接都只适用于一条传输 * (由于较老的代理会转发Connection首部,所以)HTTP/1.1的代理服务器不应该与HTTP/1.0客户端建立持久连接,除非它们了解客户端的处理能力。实际上,这很难做到 * 尽管服务器不应该试图在传输报文的过程中关闭连接,而且在关闭连接之前至少应该响应一条请求,但不管Connection首部取了什么值,HTTP/1.1设备都可以在任意时刻关闭连接。 * HTTP/1.1应用程序必须能够从异步的关闭中恢复出来。只要不存在可能会累积起来的副作用,客户端都应该重试这条请求 # 管道化连接 # ## 机制 ## HTTP/1.1运行在持久连接上可选的使用**请求管道**。这是在keep-alive连接上的进一步性能优化。在响应到达之前,可以将多条请求放入队列。当第一条请求通过网络流向地球另一端的服务器时,第二条和第三天也可以开始发送了。在高时延网络条件下,这样做可以降低网络的环回时间,提高性能。 ![在这里插入图片描述][4fe1dee9237af69febeb06edb15ec32d.png] ## 限制 ## * 如何HTTP客户端无法确定连接是持久的,就不应该使用管道 * **必须按照与请求相同的顺序会送HTTP响应。HTTP报文中没有序列号标签,因此如果收到的响应失序了,就没办法将其与请求匹配了**。 * HTTP客户端必须做好连接会在任意时刻关闭的准备,还要准备好重发所有未完成的管道化请求。如果客户端打开了一条持久连接,并立即发出了10条请求,服务器可能只处理了5条请求之后就关闭连接了。剩下的5条请求会失败,客户端必须能够应对这些过早关闭连接的请求,重新发出这些请求。 * HTTP客户端不应该用管道化的方式发送会产生副作用的请求(比如POST)。总之,出错的时刻,管道化的方式会阻碍客户端了解服务器执行的是一系列管道话请求中的哪一些。由于无法安全的重试POST这样的非幂等请求,所以出错时,就存在某些方法永远不会被执行的风险。 # 关闭连接 # 连接管理—尤其是知道在什么时候一起如何去关闭连接----是HTTP的使用魔法之一。 ## “任意”解除连接 ## 所有HTTP客户端、服务器或代理都可以在任意时刻关闭一条TCP传输连接。**通常会在一条报文结束时关闭连接**,但出错的时候,也可能在首部行的中间或者其他奇怪的地方关闭连接(**除非服务器怀疑出现了客户端或者网络故障,否则就不应该在请求的中间关闭连接**) 对管道化持久连接来说,这种情形是很常见的。HTTP应用程序可以在经过任意一段时间之后,关闭持久连接。比如,在持久连接空闲一段时间之后,服务器可能会决定将其关闭。 但是,服务器永远都无法确定在它关闭“空闲”连接的那一刻,在线路那一条的客户端有没有数据要发送。如果出现这种情况,客户端就会在写入半截请求报文时发现连接错误 ## Content-Length及截尾操作 ## **每条HTTP响应都应该有精确的Content-Length首部,用以描述响应主体的尺寸**。一些老的HTTP服务器会省略Content-Length首部,或者包含错误的长度指示,这样就要依赖服务器发出的连接关闭来说明数据的真实末尾 客户端或者代理收到一条随连接关闭而结束的HTTP响应,且实际传输的实体长度与Content-Length并不匹配或者没有Content-Length时,接收端就应该质疑长度的正确性。 如果接收端是个缓存代理,接收端就不应该缓存这条形影(以降低今后将潜在的错误报文混合起来的可能)。代理应该将有问题的报文原封不动的转发出去,而不应该试图去“矫正”Content-Length,以维护语义的透明性。 ## 连接关闭容限、重试以及幂等性 ## 即使在非错误情况下,连接也可以在任意时刻关闭。**HTTP应用程序要做好正确处理非预期关闭的准备**。 * 如果在客户端执行事务的过程中,传输连接关闭了,那么,除非事务处理会带来一些副作用,否则客户单应该重新打开链接,并重试一次 * 对管道化连接来说,这种情况更加严重一些。客户端可以将大量请求放入队列中排队,但源端服务器可以关闭连接,这样就会留下大量未处理的请求,需要重新调度 副作用是很严重的问题。如果在发送出一些请求数据之后,收到返回结果之前,连接关闭了,客户端就无法百分之百的确认服务器端实际激活了多少事务。有些事务,如果GET,可以重复执行多次,也不会有什么变化。而比如POST一个订单就不能重复执行。 **如果一个事务,不管是执行一次还是多次,得到的结果都相同,这个事务就是幂等的**。实现者可以认为**GET、HEAD、PUT、DELETE、TRACE和OPTIONS**方法都共享这一特性。**客户端不应该以管道化方式传送非幂等请求**,否则,传输连接的过早终止会造成一些不确定的后果。要发送一条非幂等请求,就需要等待来自前一条请求的响应状态。 ## 正常关闭连接 ## 如下图,TCP连接是双向的。TCP连接的每一端都有一个输入队列和一个输出队列,用于数据的读或写。放入一端的输出队列的数据最终会出现在另一端的输入队列中。 ![在这里插入图片描述][91380a419783d1df0d3135dac783b812.png] ### 完全关闭和半管 ### 应用程序可以关闭TCP输入和输出信道中的任意一个,或者两者都关闭 * 套接字调用close()将TCP连接的输入和输出信号都关闭了,这叫做**完全关闭** * 套接字调用shutdonw()单独关闭输入或者输出信道,这叫做**半关闭** ### TCP关闭及重置错误 ### 简单的HTTP应用程序可以只使用完全关闭。但当应用程序开始与很多其他类型的HTTP客户端、服务器和代理进行对话而且开始使用管道化持久连接时,使用半关闭来防止对等实体收到非预期的写入错误就变得很重要了。 * **关闭连接的输出信道总是很安全的。连接另一端的对等实体会从其缓冲区中读出所有数据之后收到一条通知,说明流结束了,这样它就知道你将连接关闭了** * **关闭连接的输入信道比较危险,除非你明确知道另一端不打算再发送其他数据了。如果另一端向你已关闭的输入信道发送数据,操作系统就会向另一端的机器会送一条TCP连接被对端重置的报文**。大部分操作系统都会将这种情况作为很严重的错误来处理,删除对端还未读取的所有缓存数据。对管道化连接来说,这是非常糟糕的事情。 比如说你已经在一条持久连接上发送了10条管道式请求了,响应也已经收到了,正在操作系统的缓冲区中存着(应用程序还未将其读走),现在,假设你发送了第11条请求,但服务器认为你使用这条连接的时间已经够长了,决定将其关闭。那么你的第11条请求就会被发送到一条已关闭的连接上去,并会向你会送一条重置信息,这个重置信息会清空你的输入缓冲区。 当你最终要去读取数据的时候,会得到一个连接被对端重置的错误,已缓存的未读响应数据也会丢失。 ### 正常关闭 ### 实现正常关闭的应用程序**首先应该关闭它的输出信道,然后等待另一端的对等实体关闭它的输出信道**。当两端都告知对方它们不会再发送任何数据之后,连接就会被完全关闭,而不会有重置的风险。 但不幸的是,无法确保对等实体会实现半关闭,或者对其进行检查。因此,**想要正常的关闭连接的应用程序应该先半关闭其输出信道,然后周期性的检查其输入信道的状态(查找数据、或者流的末尾)。如果在一定时间内对端没有关闭输入信道,应用程序可以强制关闭连接,以节省资源** [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3poaXpoZW5nZ3Vhbg_size_16_color_FFFFFF_t_70]: /images/20220829/048ac2bbe2884b3bbb6633bcee7157fd.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3poaXpoZW5nZ3Vhbg_size_16_color_FFFFFF_t_70 1]: /images/20220829/9cc182fa4f654d76a7120e18d44994ea.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3poaXpoZW5nZ3Vhbg_size_16_color_FFFFFF_t_70 2]: /images/20220829/b0886f4293fd49fe9c334ca66ddd277e.png [f1506f82448c894355e468720defc513.png]: /images/20220829/8d12c552208c458b9505490ecda54602.png [92cfeab1521305b12dd47f9ec2b308ed.png]: /images/20220829/3a8b2733316f44a185221b8bceba7d81.png [b3e7ffeeccd3813868a3e04bd41176f7.png]: /images/20220829/4a405b27d7244928b46c8cc8d965dcd6.png [d8a94827302fc1a3ba3b6a9b224a6cac.png]: /images/20220829/1b4e2dfe32b1405d89f598ec5a7fb726.png [f2d749ca3ce81000e17d011546c845b5.png]: /images/20220829/4c9b0115f9f049368f79b1d0369cebfe.png [d8094d7588de9ffaf6a047f90ae2f707.png]: /images/20220829/98f0607d2ad74b6e9f7740306d109c68.png [b3d595eae8ad01bef814bfbdcc753a76.png]: /images/20220829/c2706277819e499f8ebec170f7a5169b.png [4fe1dee9237af69febeb06edb15ec32d.png]: /images/20220829/5aac6116d8a1479797fb0a0a1fc9876f.png [91380a419783d1df0d3135dac783b812.png]: /images/20220829/3fd9a81e1b324c41b41bfc4985d9e9ba.png
还没有评论,来说两句吧...