前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >HTTP连接管理

HTTP连接管理

作者头像
zy010101
发布2022-10-28 16:33:55
5100
发布2022-10-28 16:33:55
举报
文章被收录于专栏:程序员程序员

HTTP连接管理

HTTP协议是应用层协议,HTTP3之前的HTTP协议,都是建立在传输层的TCP协议之上的。本文主要介绍HTTP1.1的连接管理。

TCP 为 HTTP 提供了一条可靠的比特传输管道。从 TCP 连接一端填入的字节会从另一端以原有的顺序、正确地传送出来。TCP是传输层协议,在它下面是网络层的IP协议,IP协议是无连接,不可靠协议,并且是分组交换(报文会分成多个数据包)。TLS是在传输层施加的。因此,HTTP和HTTPS的协议栈如下所示:

HTTP 要传送一条报文时,会以流的形式将报文数据的内容通过一条打开的 TCP 连接按序传输。TCP 收到数据流之后,会将数据流砍成被称作段的小数据块,并将段封装在 IP 分组中,通过因特网进行传输。所有这些工作都是由 TCP/IP 软件来处理的,HTTP 程序员什么都看不到。

操作系统提供了一些操纵其 TCP 连接的工具套接字(socket),套接字API向 HTTP 程序员隐藏了 TCP 和 IP 的所有细节。

HTTP性能

HTTP 紧挨着 TCP,位于其上层,所以 HTTP 事务的性能在很大程度上取决于底层TCP 通道的性能。会对 HTTP 程序员产生影响的、最常见的TCP 相关时延。

TCP 连接建立

建立一条新的 TCP 连接时,甚至是在发送任意数据之前,TCP 软件之间会交换一系列的 IP 分组,对连接的有关参数进行沟通,如果连接只用来传送少量数据,这些交换过程就会严重降低 HTTP 的性能。小的 HTTP 事务可能会在 TCP 建立上花费 50%,或更多的时间。因此,传输少量数据的HTTP连接,大部分时间都花费在了建立TCP连接上,真正传输数据的时间占比少。

TCP 慢启动和拥塞控制

TCP连接在刚开始建立的阶段,发送速率起始慢,TCP希望能够快速找到可用的带宽数,用于防止因特网的突然过载和拥塞。在慢启动阶段发送速率以指数形式增长。例如:发送一个分组,等待确认;然后可以发送两个分组,每个分组都必须被确认,这样就可以发送四个分组了,以此类推。当拥塞发生的时候,那么TCP应当降低速率。当TCP速率低于某个值的时候,为了快速恢复TCP速率。TCP采用的策略是重启慢启动,让其迁入慢启动状态。

由于存在这种拥塞控制特性,所以新连接的传输速度会比已经交换过一定量数据的、“已调谐”连接慢一些。由于已调谐连接要更快一些,所以HTTP 中有一些可以重用现存连接的工具。

数据聚集的 Nagle 算法

TCP 有一个数据流接口,应用程序可以通过它将任意尺寸的数据放入 TCP 栈中,即使一次只放一个字节也可以!但是,每个 TCP 段中都至少装载了 40 个字节的标记和首部(IP头固定20字节,TCP头至少20字节),所以如果 TCP 发送了大量包含少量数据的分组,网络的性能就会严重下降。

Nagle 算法(根据其发明者 John Nagle 命名)试图在发送一个分组之前,将大量TCP 数据绑定在一起,以提高网络效率。RFC 896“IP/TCP 互连网络中的拥塞控制”对此算法进行了描述。

Nagle 算法鼓励发送全尺寸(LAN 上最大尺寸的分组大约是 1500 字节,在因特网上是几百字节)的段。只有当所有其他分组都被确认之后,Nagle 算法才允许发送非全尺寸的分组。如果其他分组仍然在传输过程中,就将那部分数据缓存起来。只有当挂起分组被确认,或者缓存中积累了足够发送一个全尺寸分组的数据时,才会将缓存的数据发送出去。

Nagle 算法会引发几种 HTTP 性能问题。首先,小的 HTTP 报文可能无法填满一个分组,可能会因为等待那些永远不会到来的额外数据而产生时延。其次,Nagle 算法与延迟确认之间的交互存在问题——Nagle 算法会阻止数据的发送,直到有确认分组抵达为止,但确认分组自身会被延迟确认算法延迟 100 ~ 200 毫秒。

HTTP 应用程序常常会在自己的栈中设置参数TCP_NODELAY,禁用 Nagle 算法,提高性能。如果要这么做的话,一定要确保会向 TCP 写入大块的数据,这样就不会产生一堆小分组了。

TIME_WAIT累积与端口耗尽

TIME_WAIT 端口耗尽是很严重的性能问题,会影响到性能基准,但在现实中相对较少出现。大多数遇到性能基准问题的人最终都会碰到这个问题,而且性能都会变得出乎意料地差,所以这个问题值得特别关注。

这个问题,主要是由于主动关闭TCP连接的一方会收到 FIN 命令,进入TIME_WAIT 状态。这时候单向的数据传输终止(假设此处是客户端发起FIN请求,服务器回复客户端ACK)。但是TCP并不清楚服务器上层应用接下来会干什么?是否还有数据需要传输到客户端,所以仅仅回复客户端一个ACK,而不是ACK+FIN。等待服务器端的上层应用不再传输数据的时候,服务器才主动给客户端发送FIN报文段,并等待接收方ACK回复,收到该ACK的时候,通信的双方才真正的断开连接。

实现的时候是,当某个 TCP 端点关闭 TCP 连接时,会在内存中维护一个小的控制块,用来记录最近所关闭连接的 IP 地址和端口号。这类信息只会维持一小段时间,通常是所估计的最大分段使用期的两倍(称为 2MSL,通常为 2 分钟)左右,以确保在这段时间内不会创建具有相同地址和端口号的新连接。实际上,这个算法可以防止在两分钟内创建、关闭并重新创建两个具有相同 IP 地址和端口号的连接。

在只有一个客户端和一台 Web 服务器的异常情况下,构建一条 TCP 连接的 4 个值:

代码语言:javascript
复制
<source-IP-address, source-port, destination-IP-address, destination-port>

其中的 3 个都是固定的——只有源端口号可以随意改变:

代码语言:javascript
复制
<client-IP, source-port, server-IP, 80>

客户端每次连接到服务器上去时,都会获得一个新的源端口,以实现连接的唯一性。( 三次握手的过程是于欢迎套接字之间进行的,真正的数据传输是在新的套接字上进行的。)但由于可用源端口的数量有限(比如,60 000 个),而且在 2MSL 秒(比如,120秒)内连接是无法重用的,连接率就被限制在了 60 000/120=500次 / 秒。如果再不断进行优化,并且服务器的连接率不高于 500 次 / 秒,就可确保不会遇到 TIME_WAIT 端口耗尽问题。

解决上述 time_wait 状态大量存在,导致新连接创建失败的问题,一般解决办法:

  1. 客户端 HTTP 请求的头部,connection 设置为 keep-alive,保持存活一段时间:现在的浏览器,一般都这么进行了
  2. 服务器端 允许 time_wait 状态的 socket 被重用 缩减 time_wait 时间,设置为 1 MSL(即,2 mins)

用于捎带确认的 TCP 延迟确认算法;

每个 TCP 段都有一个序列号和数据完整性校验和。每个段的接收者收到完好的段时,都会向发送者回送小的确认分组。如果发送者没有在指定的窗口时间内收到确认信息,发送者就认为分组已被破坏或损毁,并重发数据。

由于确认报文很小,所以 TCP 允许在发往相同方向的输出数据分组中对其进行“捎带”。TCP 将返回的确认信息与输出的数据分组结合在一起,可以更有效地利用网络。为了增加确认报文找到同向传输数据分组的可能性,很多 TCP 栈都实现了一种“延迟确认”算法。延迟确认算法会在一个特定的窗口时间(通常是 100 ~ 200 毫秒)内将输出确认存放在缓冲区中,以寻找能够捎带它的输出数据分组。如果在那个时间段内没有输出数据分组,就将确认信息放在单独的分组中传送。

通常,延迟确认算法会引入相当大的时延。根据所使用操作系统的不同,可以调整或禁止延迟确认算法。

在对 TCP 栈的任何参数进行修改之前,一定要对自己在做什么有清醒的认识。TCP中引入这些算法的目的是防止设计欠佳的应用程序对因特网造成破坏。对 TCP 配置进行的任意修改,都要绝对确保应用程序不会引发这些算法所要避免的问题。

HTTP头部的Connection字段

Connection 首部可以承载 3 种不同类型的标签,因此有时会很令人费解:

  • HTTP 首部字段名,列出了只与此连接有关的首部;
  • 任意标签值,用于描述此连接的非标准选项;
  • 值 close,说明操作完成之后需关闭这条持久连接。

如果连接标签中包含了一个 HTTP 首部字段的名称,那么这个首部字段就包含了与一些连接有关的信息,不能将其转发出去。(主要原因还是在于盲代理)在将报文转发出去之前,必须删除Connection 首部列出的所有首部字段。由于 Connection 首部可以防止无意中对本地首部的转发,因此将逐跳首部名放入Connection 首部被称为“对首部的保护”。

在这里插入图片描述
在这里插入图片描述

HTTP 应用程序收到一条带有 Connection 首部的报文时,接收端会解析发送端请求的所有选项,并将其应用。然后会在将此报文转发给下一跳地址之前,删除Connection 首部以及 Connection 中列出的所有首部。而且,可能还会有少量没有作为 Connection 首部值列出,但一定不能被代理转发的逐跳首部。其中包括Prxoy-Authenticate、Proxy-Connection、Transfer-Encoding 和 Upgrade。

串行事务处理时延

如果只对连接进行简单的管理,TCP 的性能时延可能会叠加起来。如果有一个页面,个包含了 3 个嵌入图片的 Web 页面。浏览器需要发起 4 个 HTTP 事务来显示此页面:1 个用于顶层的 HTML 页面,3 个用于嵌入的图片。如果每个事务都需要(串行地建立)一条新的连接,那么连接时延和慢启动时延就会叠加起来。

串行加载的另一个缺点是,有些浏览器在对象加载完毕之前无法获知对象的尺寸,而且它们可能需要尺寸信息来决定将对象放在屏幕的什么位置上,所以在加载了足够多的对象之前,无法在屏幕上显示任何内容。在这种情况下,可能浏览器串行装载对象的进度很正常,但用户面对的却是一个空白的屏幕,对装载的进度一无所知。(HTML 的设计者可以在图片等嵌入式对象的 HTML 标签中显式地添加宽高属性,以消除这种“布局时延”)

因此,为了提高HTTP的性能,人们提出了下面的方法。

  • 并行连接
  • 持久连接
  • 管道化连接
  • 复用的连接

并行连接

HTTP 允许客户端打开多条连接,并行地执行多个 HTTP 事务。例如前面的一个包含3张图片的HTML页面,可以先建立一个HTTP连接获取到HTML页面的信息,然后在建立3个HTTP连接来并行获取3张图片。

并行连接的速度可能会更快,但并不一定总是更快。客户端的网络带宽不足(比如,浏览器是通过一个 28.8kbps 的 Modem 连接到因特网上去的)时,大部分的时间可能都是用来传送数据的。在这种情况下,一个连接到速度较快服务器上的HTTP 事务就会很容易地耗尽所有可用的 Modem 带宽。如果并行加载多个对象,每个对象都会去竞争这有限的带宽,每个对象都会以较慢的速度按比例加载,这样带来的性能提升就很小,甚至没什么提升(严重情况下,甚至反向提升)。

即使在客户端带宽不足的情况下,对于用户而言,感觉上可能会觉得快,因为多个组件对象同时出现在屏幕上时,用户能够看到加载的进展。

持久连接

Web 客户端经常会打开到同一个站点的连接。比如,一个 Web 页面上的大部分内嵌图片通常都来自同一个 Web 站点,因此,初始化了对某服务器 HTTP 请求的应用程序很可能会在不久的将来对那台服务器发起更多的请求,这种性质被称为站点局部性(site locality)。

因此,HTTP/1.1(以及 HTTP/1.0 的各种增强版本)允许 HTTP 设备在事务处理结束之后将 TCP 连接保持在打开状态,以便为未来的 HTTP 请求重用现存的连接。在事务处理结束之后仍然保持在打开状态的 TCP 连接被称为持久连接。非持久连接会在每个事务结束之后关闭。持久连接会在不同事务之间保持打开状态,直到客户端或服务器决定将其关闭为止。

持久连接和并行连接配合使用

持久连接与并行连接配合使用可能是最高效的方式。现在,很多 Web 应用程序都会打开少量的并行连接,其中的每一个都是持久连接。持久连接可以分为:HTTP/1.0+“keep-alive”连接,以及现代的 HTTP/1.1“persistent”连接。

HTTP/1.0+的keep-alive连接

实现 HTTP/1.0 keep-alive 连接的客户端可以通过包含 Connection: Keep-Alive 首部请求将一条连接保持在打开状态。如果服务器愿意为下一条请求将连接保持在打开状态,就在响应中包含相同的首部。如果响应中没有 Connection: Keep-Alive 首部,客户端就认为服务器不支持 keep-alive,会在发回响应报文之后关闭连接。客户端和服务器可以在任意时刻关闭空闲的 keep-alive 连接,并可随意限制 keep-alive 连接所处理事务的数量。

可以在HTTP报文的首部中添加 Keep-Alive 字段来指定由逗号分隔的选项来调节 keep-alive 的行为。

  • 参数 timeout 是在 Keep-Alive 响应首部发送的。它估计了服务器希望将连接保持在活跃状态的时间。这并不是一个承诺值。
  • 参数 max 是在 Keep-Alive 响应首部发送的。它估计了服务器还希望为多少个事务保持此连接的活跃状态。这并不是一个承诺值。
  • Keep-Alive 首部还可支持任意未经处理的属性,这些属性主要用于诊断和调试。语法为 name[=value]。

Keep-Alive 首部完全是可选的,但只有在提供 Connection: Keep-Alive 时才能使用它。这里有个 Keep-Alive 响应首部的例子,这个例子说明服务器最多还会为另外 5 个事务保持连接的打开状态,或者将打开状态保持到连接空闲了 2 分钟之后。

代码语言:javascript
复制
Connection: Keep-Alive
Keep-Alive: max=5, timeout=120

在 HTTP/1.0 中,connection默认是close,如果需要持久连接,那么必须在HTTP请求报文首部中加入Connection: Keep-Alive来尝试激活keep-alive 连接。

只有在无需检测到连接的关闭即可确定报文实体主体部分长度的情况下,才能将连接保持在打开状态——也就是说实体的主体部分必须有正确的Content-Length,有多部件媒体类型,或者用分块传输编码的方式进行了编码。在一条keep-alive 信道中回送错误的 Content-Length 是很糟糕的事,这样的话,事务处理的另一端就无法精确地检测出一条报文的结束和另一条报文的开始了。

在HTTP/1.0中,代理或网关必须在将报文转发出去或将其高速缓存之前,删除在 Connection 首部中命名的所有首部字段以及Connection 首部自身,以防止出现下面要介绍的哑代理问题。从技术上来讲,应该忽略所有来自 HTTP/1.0 设备的 Connection 首部字段(包括 Connection: Keep-Alive),因为它们可能是由比较老的代理服务器误转发的。

盲中继 问题出在代理上——尤其是那些不理解 Connection 首部,而且不知道在沿着转发链路将其发送出去之前,应该将该首部删除的代理。很多老的或简单的代理都是盲中继(blind relay),它们只是将字节从一个连接转发到另一个连接中去,不对Connection 首部进行特殊的处理。如下所示:

在这里插入图片描述
在这里插入图片描述
  1. Web 客户端向代理发送了一条报文,其中包含了 Connection: Keep-Alive 首部,如果可能的话请求建立一条 keep-alive 连接。客户端等待响应,以确定对方是否认可它对 keep-alive 信道的请求。
  2. 代理收到了这条 HTTP 请求,但它并不理解 Connection 首部(只是将其作为一个扩展首部对待)。代理不知道 keep-alive 是什么意思,因此只是沿着转发链路将报文一字不漏地发送给服务器。但 Connection 首部是个逐跳首部,只适用于单条传输链路,不应该沿着传输链路向下传输。接下来,就要发生一些很糟糕的事情了。
  3. 经过中继的 HTTP 请求抵达了 Web 服务器。当 Web 服务器收到经过代理转发的 Connection: Keep-Alive 首部时,会误以为代理(对服务器来说,这个代理看起来就和所有其他客户端一样)希望进行 keep-alive 对话!对Web 服务器来说这没什么问题——它同意进行 keep-alive 对话,并回送了一个 Connection: Keep-Alive 响应首部。所以,此时 Web 服务器认为它在与代理进行 keep-alive 对话,会遵循 keep-alive 的规则。但代理却对 keepalive 一无所知。
  4. 代理将 Web 服务器的响应报文回送给客户端,并将来自 Web服务器的 Connection: Keep-Alive 首部一起传送过去。客户端看到这个首部,就会认为代理同意进行 keep-alive 对话。所以,此时客户端和服务器都认为它们在进行 keep-alive 对话,但与它们进行对话的代理却对keep-alive 一无所知。
  5. 由于代理对 keep-alive 一无所知,所以会将收到的所有数据都回送给客户端,然后等待源端服务器关闭连接。但源端服务器会认为代理已经显式地请求它将连接保持在打开状态了,所以不会去关闭连接。这样,代理就会挂在那里等待连接的关闭。(糟糕的事情发生了
  6. 客户端收到了回送的响应报文时,会立即转向下一条请求,在 keepalive 连接上向代理发送另一条请求。而代理并不认为同一条连接上会有其他请求到来,请求被忽略,浏览器就在这里转圈,不会有任何进展了。(更糟糕的事情来了
  7. 这种错误的通信方式会使浏览器一直处于挂起状态,直到客户端或服务器将连接超时,并将其关闭为止。

为避免此类代理通信问题的发生,HTTP/1.0代理都绝不能转发 Connection 首部和所有名字出现在 Connection 值中的首部。因此,如果一个代理收到了一个Connection: Keep-Alive 首部,是不应该转发 Connection 首部,或所有名为Keep-Alive 的首部的。也不能被代理转发或作为缓存响应使用的首部。其中包括 Proxy-Authenticate、Proxy-Connection、Transfer-Encoding 和 Upgrade。

Netscape 的浏览器及代理实现者们提出了一个对盲中继问题的变通做法,这种做法并不要求所有的 Web 应用程序支持高版本的 HTTP。这种变通做法引入了一个名为Proxy-Connection 的新首部,解决了在客户端后面紧跟着一个盲中继所带来的问题——但并没有解决所有其他情况下存在的问题。 网景的变通做法是,浏览器会向代理发送非标准的 Proxy-Connection 扩展首部,而不是官方支持的著名的 Connection 首部。如果代理是盲中继,它会将无意义的 Proxy-Connection 首部转发给 Web 服务器,服务器会忽略此首部,不会带来任何问题。但如果代理是个聪明的代理(能够理解持久连接的握手动作),就用一个 Connection 首部取代无意义的 Proxy-Connection 首部,然后将其发送给服务器,以收到预期的效果。在客户端和服务器之间只有一个代理时可以用这种方案来解决问题。如果在哑代理的任意一侧还有一个聪明的代理,这个问题就会再次露头了。而且,网络中出现“不可见”代理的情况现在变得很常见了,这些代理可以是防火墙、拦截缓存,或者是反向代理服务器的加速器。这些设备对浏览器是不可见的,所以浏览器不会向它们发送 Proxy-Connection 首部。透明的 Web 应用程序正确地实现持久连接是非常重要的。

HTTP/1.1持久连接

HTTP/1.1 逐渐停止了对 keep-alive 连接的支持,用一种名为持久连接(persistent connection)的改进型设计取代了它。持久连接的目的与 keep-alive 连接的目的相同,但工作机制更优一些。

与 HTTP/1.0+ 的 keep-alive 连接不同,HTTP/1.1 持久连接在默认情况下是激活的。除非特别指明,否则 HTTP/1.1 假定所有连接都是持久的。要在事务处理结束之后将连接关闭,HTTP/1.1 应用程序必须向报文中显式地添加一个 Connection:close 首部。这是与以前的 HTTP 协议版本很重要的区别,在以前的版本中,keepalive 连接要么是可选的,要么根本就不支持。

HTTP/1.1 客户端假定在收到响应后,除非响应中包含了 Connection: close 首部,不然 HTTP/1.1 连接就仍维持在打开状态。但是,客户端和服务器仍然可以随时关闭空闲的连接。不发送 Connection: close 并不意味着服务器承诺永远将连接保持在打开状态。

注意事项

  1. 如果客户端不想在连接上发送其他请求了,就应该在最后一条请求中发送一个Connection: close 请求首部。
  2. 只有当连接上所有的报文都有正确的、自定义报文长度时——也就是说,实体主体部分的长度都和相应的 Content-Length 一致,或者是用分块传输编码方式编码的——连接才能持久保持。
  3. HTTP/1.1 的代理必须能够分别管理与客户端和服务器的持久连接——每个持久连接都只适用于一跳传输。

管道化连接

HTTP/1.1 允许在持久连接上可选地使用请求管道。这是相对于 keep-alive 连接的又一性能优化。在响应到达之前,可以将多条请求放入队列。当第一条请求通过网络流向地球另一端的服务器时,第二条和第三条请求也可以开始发送了。在高时延网络条件下,这样做可以降低网络的环回时间,提高性能。

对管道化连接有几条限制。

  • 如果 HTTP 客户端无法确认连接是持久的,就不应该使用管道。
  • 必须按照与请求相同的顺序回送 HTTP 响应。HTTP 报文中没有序列号标签,因此如果收到的响应失序了,就没办法将其与请求匹配起来了。
  • HTTP 客户端必须做好连接会在任意时刻关闭的准备,还要准备好重发所有未完成的管道化请求。如果客户端打开了一条持久连接,并立即发出了 10 条请求,服务器可能在只处理了,比方说,5 条请求之后关闭连接。剩下的 5 条请求会失败,客户端必须能够应对这些过早关闭连接的情况,重新发出这些请求。
  • HTTP 客户端不应该用管道化的方式发送会产生副作用的请求(比如 POST)。总之,出错的时候,管道化方式会阻碍客户端了解服务器执行的是一系列管道化请求中的哪一些。由于无法安全地重试 POST 这样的非幂等请求,所以出错时,就存在某些方法永远不会被执行的风险。
在这里插入图片描述
在这里插入图片描述

关闭连接

所有 HTTP 客户端、服务器或代理都可以在任意时刻关闭一条 TCP 传输连接。通常会在一条报文结束时关闭连接,18 但出错的时候,也可能在首部行的中间,或其他任何地方关闭连接。

对管道化持久连接来说,这种情形是很常见的。HTTP 应用程序可以在经过任意一段时间之后,关闭持久连接。比如,在持久连接空闲一段时间之后,服务器可能会决定将其关闭。但是,服务器永远都无法确定在它关闭“空闲”连接的那一刻,在线路那一头的客户端有没有数据要发送。如果出现这种情况,客户端就会在写入半截请求报文时发现出现了连接错误。客户端必须重新发出失败的请求。

Content-Length及截尾操作

每条 HTTP 响应都应该有精确的 Content-Length 首部,用以描述响应主体的尺寸。一些老的 HTTP 服务器会省略 Content-Length 首部,或者包含错误的长度指示,这样就要依赖服务器发出的连接关闭来说明数据的真实末尾。客户端或代理收到一条随连接关闭而结束的 HTTP 响应,且实际传输的实体长度与Content-Length 并不匹配(或没有 Content-Length)时,接收端就应该质疑长度的正确性。如果接收端是个缓存代理,接收端就不应该缓存这条响应(以降低今后将潜在的错误报文混合起来的可能)。代理应该将有问题的报文原封不动地转发出去,而不应该试图去“校正”Content-Length,以维护语义的透明性。

连接关闭容限、重试以及幂等性

即使在非错误情况下,连接也可以在任意时刻关闭。HTTP 应用程序要做好正确处理非预期关闭的准备。如果在客户端执行事务的过程中,传输连接关闭了,那么,除非事务处理会带来一些副作用,否则客户端就应该重新打开连接,并重试一次。对管道化连接来说,这种情况更加严重一些。客户端可以将大量请求放入队列中排队,但源端服务器可以关闭连接,这样就会留下大量未处理的请求,需要重新调度。副作用是很重要的问题。如果在发送出一些请求数据之后,收到返回结果之前,连接关闭了,客户端就无法百分之百地确定服务器端实际激活了多少事务。有些事务,比如 GET 一个静态的 HTML 页面,可以反复执行多次,也不会有什么变化。而其他一些事务,比如向一个在线书店 POST 一张订单,就不能重复执行,不然会有下多张订单的危险。如果一个事务,不管是执行一次还是很多次,得到的结果都相同,这个事务就是幂等的。实现者们可以认为 GET、HEAD、PUT、DELETE、TRACE 和 OPTIONS 方法都共享这一特性。19 客户端不应该以管道化方式传送非幂等请求(比如 POST)。否则,传输连接的过早终止就会造成一些不确定的后果。要发送一条非幂等请求,就需要等待来自前一条请求的响应状态。大多数浏览器都会在重载一个缓存的 POST 响应时提供一个对话框,询问用户是否希望再次发起事务处理。

正常关闭

TCP 连接是双向的。TCP 连接的每一端都有一个输入队列和一个输出队列,用于数据的读或写。放入一端输出队列中的数据最终会出现在另一端的输入队列中。应用程序可以关闭 TCP 输入和输出信道中的任意一个,或者将两者都关闭了。套接字调用 close() 会将 TCP 连接的输入和输出信道都关闭了。这被称作“完全关闭”,还可以用套接字调用 shutdown() 单独关闭输入或输出信道。这被称为“半关闭”。

总之,关闭连接的输出信道总是很安全的。连接另一端的对等实体会在从其缓冲区中读出所有数据之后收到一条通知,说明流结束了,这样它就知道你将连接关闭了。关闭连接的输入信道比较危险,除非你知道另一端不打算再发送其他数据了。如果另一端向你已关闭的输入信道发送数据,操作系统就会向另一端的机器回送一条TCP“连接被对端重置”的报文。大部分操作系统都会将这种情况作为很严重的错误来处理,删除对端还未读取的所有缓存数据。对管道化连接来说,这是非常糟糕的事情。比如你已经在一条持久连接上发送了 10 条管道式请求了,响应也已经收到了,正在操作系统的缓冲区中存着呢(但应用程序还未将其读走)。现在,假设你发送了第 11 条请求,但服务器认为你使用这条连接的时间已经够长了,决定将其关闭。那么你的第 11 条请求就会被发送到一条已关闭的连接上去,并会向你回送一条重置信息。这个重置信息会清空你的输入缓冲区。 当你最终要去读取数据的时候,会得到一个连接被对端重置的错误,已缓存的未读响应数据都丢失了,尽管其中的大部分都已经成功抵达你的机器了。

实现正常关闭的应用程序首先应该关闭它们的输出信道,然后等待连接另一端的对等实体关闭它的输出信道。当两端都告诉对方它们不会再发送任何数据(比如关闭输出信道)之后,连接就会被完全关闭,而不会有重置的危险。 但不幸的是,无法确保对等实体会实现半关闭,或对其进行检查。因此,想要正常关闭连接的应用程序应该先半关闭其输出信道,然后周期性地检查其输入信道的状态(查找数据,或流的末尾)。如果在一定的时间区间内对端没有关闭输入信道,应用程序可以强制关闭连接,以节省资源。

参考资料

《HTTP权威指南》

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2022-10-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • HTTP连接管理
    • HTTP性能
      • TCP 连接建立
      • TCP 慢启动和拥塞控制
      • 数据聚集的 Nagle 算法
      • TIME_WAIT累积与端口耗尽
      • 用于捎带确认的 TCP 延迟确认算法;
    • HTTP头部的Connection字段
      • 串行事务处理时延
        • 并行连接
          • 持久连接
            • 持久连接和并行连接配合使用
            • HTTP/1.0+的keep-alive连接
            • HTTP/1.1持久连接
            • 管道化连接
          • 关闭连接
            • Content-Length及截尾操作
            • 连接关闭容限、重试以及幂等性
            • 正常关闭
          • 参考资料
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档