HTTP连接管理

    世界上几乎所有的 HTTP 通信都是由 TCP/IP 承载的,TCP/IP 是全球计算机及网络设备都在使用的一种常用的分组交换网络分层协议集。客户端应用程序可以打开一条 TCP/IP 连接,连接到可能运行在世界任何地方的服务器应用程序。一旦连接建立起来了,在客户端和服务器的计算机之间交换的报文就永远不会丢失、受损或失序。- from 《HTTP权威指南》

    本文较长,建议通过旁边目录检索自己想要看的部分。文章主要讲解这几个问题:

    • HTTP是如何使用TCP连接的?TCP有哪些特点?
    • TCP连接的时延,瓶颈以及存在的障碍。
    • HTTP优化,包括并行连接、keep-alive(持久连接)和管道化连接。
    • 管理连接时可能会出现的问题,应该以及不应该做的事情。

    TCP链接

    HTTP连接是HTTP报文传输的关键通道。这里的HTTP连接指的就是TCP连接和一些实用链接的规则。


    TCP的可靠数据管道

    TCP 为 HTTP 提供了一条可靠的比特传输管道。从 TCP 连接一端填入的字节会从另一端以原有的顺序、正确地传送出来

    注意这里的可靠的比特传输管道

    TCP会按序,无差错的承载HTTP数据。下面看看Web浏览器通过TCP连接和Web服务器进行交互的过程。

    tcp连接与web服务器交互过程


    TCP流是分段的、由IP分组传送

    TCP 的数据是通过名为 IP 分组(或 IP 数据报)的小数据块来发送的。这样的话,HTTP 就是“HTTP over TCP over IP”这个“协议栈”中的最顶层了。其安全版本 HTTPS 就是在 HTTP 和 TCP 之间插入了一个(称为 TLS 或 SSL的)密码加密层。

    http over everything

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

    或许我们可以从下面这张图看出点什么:

    主机间通信过程

    每个 TCP 段都是由 IP 分组承载,从一个 IP 地址发送到另一个 IP 地址的。每个 IP分组中都包括:

    • 一个 IP 分组首部(通常为 20 字节);
    • 一个 TCP 段首部(通常为 20 字节);
    • 一个 TCP 数据块(0 个或多个字节)。

    上面的那张图中可以看到传输层那部分,包含的三个部分。

    IP 首部包含了源和目的 IP 地址、长度和其他一些标记。TCP 段的首部包含了 TCP
    端口号、TCP 控制标记,以及用于数据排序和完整性检查的一些数字值。


    保持TCP连接的正确运行

    在任意时刻计算机都可以有几条 TCP 连接处于打开状态。TCP 是通过端口号来保持所有这些连接的正确运行的。

    关于IP和端口下面这个比喻我觉得特别棒:

    端口号和雇员使用的电话分机号很类似。就像公司的总机号码能将你接到前台,而分机号可以将你接到正确的雇员位置一样,IP 地址可以将你连接到正确的计算机,而端口号则可以将你连接到正确的应用程序上去。

    TCP是通过下面4个值来识别的:
    < 源 IP 地址、源端口号、目的 IP 地址、目的端口号 >

    这个识别标识就像人的ID一样,唯一的定义了一条连接。两个不同的 TCP 连接不能拥有4个完全相同的地址组件值。

    IP分组

    这张图:承载 TCP 段的 IP 分组,它承载了 TCP 数据流中的小块数据。是真实流动在TCP通道中的小数据包。

    再强调下:注意,有些连接共享了相同的目的端口号(C 和 D 都使用目的端口号 80)。有些连接使用了相同的源 IP 地址(B 和 C)。有些使用了相同的目的 IP 地址(A 和 B,C和 D)。但没有两个不同连接所有的 4 个值都一样。

    唯一ID


    用TCP套接字编程

    操作系统提供了一些操纵其 TCP 连接的工具。我们来看一个 TCP 编程接口。下面显示了套接字 API 提供的一些主要接口。这个套接字 API向 HTTP 程序员隐藏了 TCP 和 IP 的所有细节。套接字 API 最初是为 Unix 操作系统开发的,但现在几乎所有的操作系统和语言中都有其变体存在。socket…

    注意套接字不是协议,是一个接口。我们用它来编程。

    tcp套接字

    套接字 API 允许用户创建 TCP 的端点数据结构,将这些端点与远程服务器的 TCP端点进行连接,并对数据流进行读写。TCP API 隐藏了所有底层网络协议的握手细节,以及 TCP 数据流与 IP 分组之间的分段和重装细节。

    下面我们看看TCP客户端和服务器是如何通过TCP套接字进行通信的;

    通信过程

    其实是Web服务器一直等待客户端的连接。

    我们从 Web 服务器等待连接开始。客户端根据 URL 判定出 IP 地址和端口号,并建立一条到服务器的 TCP 连接。建立连接可能要花费一些时间,时间长短取决于服务器距离的远近、服务器的负载情况,以及因特网的拥挤程度。

    一旦建立了连接,客户端就会发送 HTTP 请求,服务器则会读取请求。一旦服务器获取了整条请求报文,就会对请求进行处理,执行所请求的动作,并将数据写回客户端。客户端读取数据,并对响应数据进行处理。


    对TCP性能的考虑

    HTTP 紧挨着 TCP,位于其上层,所以 HTTP 事务的性能在很大程度上取决于底层TCP 通道的性能。理解了 TCP 的某些基本性能特点之后,就可以更好地理解 HTTP 的连接优化特性,这样就能设计实现一些更高性能的 HTTP 应用程序了。


    HTTP事务的时延

    先用一张图看看网络延时。还有HTTP事务的连接,传输以及处理时延。

    HTTP事务

    我以前刚看到这张图的时候,觉得很不理解,怎么连接,请求和相应的时间会比处理的时间多这么多呢?现在想想,很容易就明白了。记住:与建立TCP连接,以及传输请求和响应报文的时间相比,事务处理时间可能是很短的。除非客户端或服务器超载,或正在处理复杂的动态资源,否则 HTTP 时延就是由 TCP 网络时延构成的。

    HTTP 事务的时延有以下几种主要原因。

    • 客户端首先需要根据 URI 确定 Web 服务器的 IP 地址和端口号。如果最近没有对URI 中的主机名进行访问,通过 DNS 解析系统将 URI 中的主机名转换成一个 IP地址可能要花费数十秒的时间。(这个10s我表示怀疑=。=)
    • 接下来,客户端会向服务器发送一条 TCP 连接请求,并等待服务器回送一个请求接受应答。每条新的 TCP 连接都会有连接建立时延。这个值通常最多只有一两秒钟,但如果有数百个 HTTP 事务的话,这个值会快速地叠加上去。
    • 一旦连接建立起来了,客户端就会通过新建立的 TCP 管道来发送 HTTP 请求。数据到达时,Web 服务器会从 TCP 连接中读取请求报文,并对请求进行处理。因特网传输请求报文,以及服务器处理请求报文都需要时间。
    • 然后,Web 服务器会回送 HTTP 响应,这也需要花费时间。

    小tip:

    大多数 HTTP 客户端都有一个小的 DNS 缓存,用来保存近期所访问站点的 IP 地址。
    如果    已经在本地“缓存”(记录)了 IP 地址,查询就可以立即完成。
    因为大多数 Web 浏览器浏览的都是少数常用站点,所以通常都可以很快地将主机名解析出来。
    

    这些 TCP 网络时延的大小取决于硬件速度、网络和服务器的负载,请求和响应报文的尺寸,以及客户端和服务器之间的距离。TCP 协议的技术复杂性也会对时延产生巨大的影响。


    性能聚焦区域

    下面看几种最常见的TCP相关时延。具体有:

    1. TCP 连接建立握手;
    2. TCP 慢启动拥塞控制;
    3. 数据聚集的 Nagle 算法;
    4. 用于捎带确认的 TCP 延迟确认算法;
    5. TIME_WAIT 时延和端口耗尽

    TCP连接的握手时延

    建立一条新的 TCP 连接时,甚至是在发送任意数据之前,TCP 软件之间会交换一系
    列的 IP 分组,对连接的有关参数进行沟通。如果连接只用来传送少量数据,这些交换过程就会严重降低 HTTP 的性能。这可以理解成我们说的三次握手。

    在发送数据之前,TCP 要传送两个分组来建立连接

    TCP 连接握手需要经过以下几个步骤。

    (1) 请求新的 TCP 连接时,客户端要向服务器发送一个小的 TCP 分组(通常是 40 ~60 个字节)。这个分组中设置了一个特殊的 SYN 标记,说明这是一个连接请求。

    (2) 如果服务器接受了连接,就会对一些连接参数进行计算,并向客户端回送一个
    TCP 分组,这个分组中的 SYN 和 ACK 标记都被置位,说明连接请求已被接受。

    (3) 最后,客户端向服务器回送一条确认信息,通知它连接已成功建立。

    现代的 TCP 栈都允许客户端在这个确认分组中发送数据。

    注意:HTTP程序员永远不会看到这些分组,这些分组都由 TCP/IP 软件管理。HTTP 程序员看到的只是创建 TCP 连接时存在的时延。小的 HTTP 事务可能会在 TCP 建立上花费 50%,或更多的时间。后面的小节会讨论 HTTP 是如何通过重用现存连接,来减小这种 TCP 建立时延所造成的影响的。


    延迟确认

    简单来说,就是如何确认数据正确传输,怎么知道分组被破坏,怎么确定应不应该重发数据呢?

    由于因特网自身无法确保可靠的分组传输(因特网路由器超负荷的话,可以随意丢
    弃分组),所以 TCP 实现了自己的确认机制来确保数据的成功传输。

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

    由于确认报文很小,所以 TCP 允许在发往相同方向的输出数据分组中对其进行“捎带”。TCP 将返回的确认信息与输出的数据分组结合在一起,可以更有效地利用网络。

    为了增加确认报文找到同向传输数据分组的可能性,很多 TCP 栈都实现了一种“延迟确认”算法延迟确认算法会在一个特定的窗口时间(通常是 100 ~ 200 毫
    秒)内将输出确认存放在缓冲区中,以寻找能够捎带它的输出数据分组。如果在那
    个时间段内没有输出数据分组,就将确认信息放在单独的分组中传送。

    但是,HTTP 具有双峰特征的请求 - 应答行为降低了捎带信息的可能。当希望有相
    反方向回传分组的时候,偏偏没有那么多。通常,延迟确认算法会引入相当大的时
    延。根据所使用操作系统的不同,可以调整或禁止延迟确认算法。

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


    TCP慢启动

    什么是TCP 慢启动?简单来说,就是TCP连接的时间是不一样的,开始是慢的,当确定后续可能还有的时候,它的速度是会不断的增加的。

    TCP 数据传输的性能还取决于 TCP 连接的使用期(age)。TCP 连接会随着时间进行自我“调谐”,起初会限制连接的最大速度,如果数据成功传输,会随着时间的推移
    提高传输的速度。这种调谐被称为 TCP 慢启动(slow start),用于防止因特网的突然过载和拥塞。

    TCP 慢启动限制了一个 TCP 端点在任意时刻可以传输的分组数。简单来说,每成功
    接收一个分组,发送端就有了发送另外两个分组的权限。

    如果某个 HTTP 事务有大量数据要发送,是不能一次将所有分组都发送出去的。必须发送一个分组,等待确认;然后可以发送两个分组,每个分组都必须被确认,这样就可以发送四个分组了,以此类推。这种方式被称为“打开拥塞窗口”。

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


    Nagle算法与TCP_NODELAY

    如何解决当TCP发送了大量包含少量数据的分组,网络性能会被严重下降的现象?我们知道TCP有一个数据流接口,应用程序可以通过它将任意尺寸的数据放在TCP栈中。即使一次只放一个字节。而且我们也知道,每个TCP段中都至少装在了40个字节的标记和首部。

    Nagle 是一个算法,TCP_NODELAY 是一个参数。

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

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

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


    TIME_WAIT累积与端口耗尽

    什么是 TCP/IP 端口耗尽?

    当客户端启动到服务器的 TCP/IP 套接字连接时,客户端通常连接到服务器上的特定端口,并请求服务器通过临时(或暂时)TCP 或 UDP 端口进行响应。如在 Windows Server 2003 和 Windows XP 中,客户端应用程序所使用的临时端口的默认范围为 1025 到 5000。在某些情况下,有可能耗尽默认范围的可用端口。

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

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

    为什么要那样防止在两分钟之内呢?是不是可能是为了防止DDOS???

    现在高速路由器的使用,使得重复分组几乎不可能在连接关闭的几分钟之后,出现在服务器上。有些操作系统会将 2MSL 设置为一个较小的值,但超过此值时要特别小心。分组确实会被复制,如果来自之前连接的复制分组插入了具有相同连接值的新 TCP 流,会破坏 TCP 数据。

    2MSL 的连接关闭延迟通常不是什么问题,但在性能基准环境下就可能会成为一个问题。进行性能基准测试时,通常只有一台或几台用来产生流量的计算机连接到某系统中去,这样就限制了连接到服务器的客户端 IP 地址数。而且,服务器通常会在HTTP 的默认 TCP 端口 80 上进行监听。用 TIME_WAIT 防止端口号重用时,这些情况也限制了可用的连接值组合。在只有一个客户端和一台 Web 服务器的异常情况下,构建一条 TCP 连接的 4 个值:

    1
    <source-IP-address, source-port, destination-IP-address, destination-port>

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

    1
    <client-IP, source-port, server-IP, 80>

    客户端每次连接到服务器上去时,都会获得一个新的源端口,以实现连接的唯一性。但由于可用源端口的数量有限(比如,60 000 个),而且在 2MSL 秒(比如,120秒)内连接是无法重用的,连接率就被限制在了 60 000/120=500 次 / 秒。如果再不断进行优化,并且服务器的连接率不高于 500 次 / 秒,就可确保不会遇到 TIME_WAIT 端口耗尽问题。要修正这个问题,可以增加客户端负载生成机器的数量,或者确保客户端和服务器在循环使用几个虚拟 IP 地址以增加更多的连接组合。

    即使没有遇到端口耗尽问题,也要特别小心有大量连接处于打开状态的情况,或为处于等待状态的连接分配了大量控制块的情况。在有大量打开连接或控制块的情况下,有些操作系统的速度会严重减缓。


    HTTP连接的处理

    从 HTTP 的 Connection 首部开始介绍,这是 HTTP 连接管理中一个很容易被
    误解,但又很重要的部分。然后会介绍一些 HTTP 连接优化技术。


    常被误解的Connection首部

    我们知道 HTTP 允许在客户端和最终的源端服务器之间存在一串 HTTP 中间实体(代理、高速缓存等)。可以从客户端开始,逐跳地将 HTTP 报文经过这些中间设备,转发到源端服务器上去(或者进行反向传输)。

    在某些情况下,两个相邻的 HTTP 应用程序会为它们共享的连接应用一组选项。HTTP 的 Connection 首部字段中有一个由逗号分隔的连接标签列表,这些标签为此连接指定了一些不会传播到其他连接中去的选项。比如,可以用 Connection:close 来说明发送完下一条报文之后必须关闭的连接。

    Connection 首部可以承载 3 种不同类型的标签:

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

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

    首部保护

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


    串行事务处理时延

    相当于每次一个HTTP请求都要建立一次TCP连接。假设有一个包含了 3 个嵌入图片的 Web 页面。浏览器需要发起 4 个 HTTP 事务来显示此页面:1 个用于顶层的 HTML 页面,3 个用于嵌入的图片。如果每个事务都需要(串行地建立)一条新的连接,那么连接时延和慢启动时延就会叠加起来。

    串行

    除了串行加载引入的实际时延之外,加载一幅图片时,页面上其他地方都没有动静也会让人觉得速度很慢。用户更希望能够同时加载多幅图片。

    串行加载的另一个缺点是,有些浏览器在对象加载完毕之前无法获知对象的尺寸,而且它们可能需要尺寸信息来决定将对象放在屏幕的什么位置上,所以在加载了足够多的对象之前,无法在屏幕上显示任何内容。在这种情况下,可能浏览器串行装载对象的进度很正常,但用户面对的却是一个空白的屏幕,对装载的进度一无所知。

    后面几种新兴和现存的方法可以提高HTTP的连接性能。比如

    • 并行连接,通过多条TCP连接发起并发的HTTP请求。
    • 持久连接,重用TCP连接,以消除连接及关闭时延。
    • 管道化连接,通过共享TCP连接发起的并发的HTTP请求。

    并行连接

    如前所述,浏览器可以先完整地请求原始的 HTML 页面,然后请求第一个嵌入对象,然后请求第二个嵌入对象等,以这种简单的方式对每个嵌入式对象进行串行处理。但这样实在是太慢了!HTTP 允许客户端打开多条连接,并行地执行多个 HTTP 事务。在这个例子中,并行加载了四幅嵌入式图片,每个事务都有自己的 TCP 连接。

    相当于每次 http 请求都还是要建立一次TCP连接,不同于上面串行的是,这几个TCP链接是并行的而已。所以有可能会出现下面几种问题:

    并行连接可能会提高页面的加载速度

    包含嵌入对象的组合页面如果能(通过并行连接)克服单条连接的空载时间和带宽限制,加载速度也会有所提高。时延可以重叠起来,而且如果单条连接没有充分利用客户端的因特网带宽,可以将未用带宽分配来装载其他对象。

    主要是合理的李勇网络带宽,就可以达到提高页面加载速度的情况。

    串行

    这里注意下,这三个链接的时间,不是三条线平行,而是三条线有重叠的情况。这就是并发处理。并行是完全重叠,是一个时间点。并发是在一个时间段上有重叠。

    并行连接不一定更快

    如果带宽不足的话,每一个连接都需要占用一部分带宽。那么意味着,每个对象都会去竞争这样有限的带宽。那么每个对象可能就会按照比较慢的速度加载,那么久有可能性能提升的非常小。甚至没有提升。

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

    而且,打开大量连接会消耗很多内存资源,从而引发自身的性能问题。复杂的 Web页面可能会有数十或数百个内嵌对象。客户端可能可以打开数百个连接,但 Web 服务器通常要同时处理很多其他用户的请求,所以很少有 Web 服务器希望出现这样的情况。一百个用户同时发出申请,每个用户打开 100 个连接,服务器就要负责处理10 000 个连接。这会造成服务器性能的严重下降。对高负荷的代理来说也同样如此。

    实际上,浏览器确实使用了并行连接,但它们会将并行连接的总数限制为一个较小的值(通常是 4 个)。服务器可以随意关闭来自特定客户端的超量连接。

    并行连接可能让人“感觉”更快一些

    刚刚我们知道了并行连接并不总是能使页面加载得更快一些。但如前所述,即使实际上它们并没有加快页面的传输速度,并行连接通常也会让用户觉得页面加载得更快了。因为多个组件同时出现在屏幕上时,用户可以看到加载的进展。


    持久连接

    先看看一个名词:站点本地性。什么是站点本地性?

    Web客户端经常会打开到同一个站点的连接,比如:一个Web页面上的大部分内嵌图片通常都来自同一个Web站点,而且相当一部分指向其他对象的超链接也都指向同一个站点。

    因此当初始化了对某服务器HTTP请求的应用程序,可能会在不久的将来对那台服务器发起更多的请求,比如获取在线图片。这就是站点的本地性。

    因此,HTTP1.1及以上的版本,允许HTTP设备在处理事务结束后,依然将TCP连接保持在打开状态。以便在未来请求重用现在的连接。

    持久连接:在事务处理结束后仍然保持在打开状态的TCP连接成为持久连接。非持久连接会在每个事务结束之后关闭,持久连接会在不同事务之间仍然保持连接,一直到客户端或者服务器决定关闭为止。

    这样的好处也很明显。重用已对目标服务器打开的空闲持久连接,就可以避开缓慢的连接建立阶段。而且,已经打开的连接还可以避免慢启动的拥塞适应阶段,以便更快的进行传输。


    持久连接和并行连接比较

    并行连接可以提高复合页面的传输速度,但是并行连接也有一些缺点,比如:

    1. 每个事务都会打开、关闭一个新的连接,会耗费时间和带宽
    2. 由于TCP慢启动特性的存在,每一条新连接的性能都会降低。
    3. 可打开并行连接的数量实际是有限的。

    持久连接有一些比并行连接更好的地方,持久链接降低了时延和连接建立的开销,将状态保持在已调谐状态。但是,要注意,要管理好持久连接,否则可能会出现大量的空闲连接,耗费本地和远程服务器的资源。

    现在的一种趋势和比较高效的方式,就是WEB应用程序都会打开少量的并行连接,其中的每一个都是持久连接

    持久连接有两种类型:第一种是HTTP、1.0+的keep-alive和现代的HTTP1.1的persistent连接。


    Keep-Alive操作

    现在keep-alive已经不在被使用了,在HTTP/1.1规范中也没有了对它的说明。但浏览器和服务器对keep-alive的使用仍然非常频繁。

    实现HTTP/1.0 keep-alive 连接的客户端通过包含Connection:Keep-Alive首部请求一条连接保持在打开状态。

    此时如果服务器如果愿意为下一条请求保持在打开状态,就在响应中包含同样的首部。同时,如果响应中没有包含Connection:Keep-Alive,客户端就会认为服务器不支持Keep-Alive

    Keep-Alive

    1
    2
    Connection: Keep-Alive
    Keep-Alive: max=5, timeout=120

    表示的是服务器最多还会为5个事务保持连接的打开状态,或者打开状态保持到2分治之后。


    Keep-Alive和哑代理

    Connection首部和盲中继

    盲中继blind relay很多老的或者简单的代理都是盲中继,他们只是将字节从一个连接转发到另一个连接中去,不对Connection首部进行特殊的处理。

    盲中继

    这幅图发生了下面的情况:

    1. 客户端发送了Connection:Keep-Alive的首部,请求持久连接。客户端会等待响应,确认服务器是否认可它的请求。
    2. 哑代理收到这个HTTP请求,它不知道Connection是什么东西,就把它当做是一个扩展的首部对待,代理不知道Keep-Alive是什么,所以就只是沿着转发链路将报文一字不漏的发给服务器。但是Connection首部是个逐跳首部,只适用于单条传输链路,不应该沿着传输链路向下传输,这样就会出现问题了。
    3. 经过中继的HTTP到了服务器,服务器收到了Connection:Keep-Alive,会认为代理希望进行Keep-alive对话,因为代理对服务器来说就是客户端。然后服务器就同意了,并回送了一个Connect:Keep-Alive的响应首部。但代理却不知道什么是Keep-alive是什么。问题出现了。
    4. 哑代理将服务器的响应报文发送给了客户端,并且把keep-alive首部也传回去了,客户端看到了,就认为服务器统一了keep-alive对话。所以这个时候,客户端和服务器认为它们在进行keep-alive对话。但是代理什么都不知道。
    5. 由于代理对keep-alive什么都不知道,所以会把收到的数据都给客户端,然后等待资源服务器关闭资源。但是源服务器会认为代理已经显示的请求它,所以不会关闭,那么代理就会一直挂着等待连接关闭。
    6. 这个时候,客户端收到回送的响应报文的时候,就会立即转向下一条请求,在Keep-alive连接上向代理发送另外一个请求。但是 ,,,,代理不会认为同一条连接上,还会有其他的请求到来,所以请求被忽略了。浏览器就在这里转圈,不会有下一步了。
    7. 这种错误的通信方式会导致浏览器一直处于挂起的状态,直到客户端或服务器连接超时,将其关闭。
    代理和逐跳首部

    为了避免上面的代理通信的问题,现在的代理都不允许转发Connection首部和所有名字中出现Connection值中的首部。因此,如果一个代理收到了Connection:Keep-Alive首部,是不应该转发Connection首部,或所有名为Keep-Alive的首部的。


    管道连接

    HTTP/1.1允许在持久连接上可选的使用请求管道。这是在Keep-Alive连接上的进一步优化,在响应到达前,可以将多条请求放入队列。当第一条请求通过网络流向服务器时,第二条第三条就可以开始发送了。

    持久连接只是复用了TCP连接,其中的每个HTTP请求仍然是串行的。但是管道连接就是在这个基础上使得每个HTTP请求是并行的。

    比较


    总结

    整个就是介绍了下HTTP通道事务处理。了解一些基本的连接方法。解决了以前我的很多疑惑。关键是里面有一些名词解释:比如TCP慢启动,比如延迟确认,比如Nagel算法,端口耗尽,还有管道化连接。这些以前只是听说过,但是具体的都不是很了解,学习了。最后那一张图,就可以代表整个文章的大部分内容了。学习整理自《HTTP权威指南》感谢。