计算机网络(HTTP、TCP)的连接管理

  • 在浏览器上输入一个网址基本上会发生的事情,,简单的图来介绍
    在这里插入图片描述
  • HTTP要传送一条报文时,会以流的形式将报文数据的内容通过一条打开的TCP连接按序传输。TCP收到数据流之后,会将数据流砍成被称作段的小数据块,并将段封装在IP分组中,通过因特网进行传输。
  • 每个TCP段都是由IP分组继承,从一个IP地址发送到另一个IP地址的。每个IP分组中都包括:
  • 一个IP分组首部(通常是20字节)
  • 一个TCP段首部(通常是20字节)
  • 一个TCP数据块(0个或多个字节)
保持TCP连接的正确性
  • 在任何时刻计算机都可以有几条TCP连接处于打开状态。TCP 是通过端口号来保持所有这些连接的正确运行的。
  • TCP连接时通过4个值来判断识别的:源IP地址、源端口号、目的IP地址、目的端口号。
对TCP性能的考虑
  • HTTP 紧挨着TCP,位于其上层,所以HTTP事务的性能在很大程度上取决与底层TCP通道的性能。

  • HTTP事务的时延
    在这里插入图片描述

  • 注意:与建立TCP连接,以及传输请求和响应报文的时间相比,事务处理时间可能时很短的。除非客户端或服务器超载,或正在处理复杂的动态资源,否则HTTP时延就是由TCP网络时延构成的。

HTTP事务的时延由一下几种主要原因
  • 客户端首先需要根据URL 确定Web 服务器的IP 地址和端口号。如果最近没有对URL 中的主机名进行访问,通过DNS 解析系统将URL 中的主机名转换成以一个IP 地址可能要花费数十秒的时间。。**但是我们会发现并没有那么长的时间,因为大多数HTTP客户端都有一个小的DNS 缓存,用来保存近期访问站点的IP地址。如果已经在本地缓存了IP地址,查询就可以立即完成。因为大多数Web 浏览器浏览的都是少数常用站点,所以通常都可以很快将主机名解析出来。 **
  • 接下来,客户端会向服务器发送一条TCP 连接请求,并等待服务器回送一个请求接受应答。每条新的TCP连接都会有连接建立时延。这个值通常最多只有一两秒钟,但如果有数百个HTTP事务的话,这个值会快速地叠加加上。
  • 一旦连接建立起来了,客户端就会通过新建立的TCP 管道来发送HTTP请求。数据到达时,Web 服务器会从TCP 连接中读取请求报文,并对请求进行处理。因特网传输请求报文,以及服务器处理请求报文都需要时间。
  • 然后,Web 服务器会回送HTTP 响应,这也需要花费时间。
  • 这些TCP 网络时延的大小取决与硬件速度、网络和服务器的负载,请求和响应报文的尺寸,以及客户端和服务器之间的距离。TCP协议的技术复杂性也会对时延产生巨大的影响。
    **最常见的TCP 相关时延:TCP 连接建立握手、TCP慢启动拥塞控制、数据聚集的Nagle算法、用于捎带确认的TCP延迟确认算法、TIME_WAIT时延和端口耗尽。 **
TCP连接的握手时延
  • TCP 连接握手需要经过一下几个步骤。
  • 请求新的TCP连接时,客户端要向服务器发送一个小的TCP分组(通常时40—60个字节)。这个分组中设置了一个特殊的SYN标记,说明这是一个连接请求。
  • 如果服务器接受了连接,就会对一些连接参数进行计算,并向客户端回送一个TCP 分组,这个分组中的SYN和ACK 标记都被置位,说明连接请求已被接受。
  • 最后,客户端向服务器回送一条确认消息,通知它连接已成功建立,现代的TCP栈都允许客户端在确认分组中发送数据。
  • 如果连接只用来传送少量的数据,这些交换过程就会严重降低HTTP 的性能。
延迟确认
  • 由于因特网自身无法确保可靠的分组传输(因特网路由器超负荷的话,可以随意丢弃分组),所以TCP 实现了自己的确认机制来确保数据的成功传输。
  • 每个TCP 段都有一个序列号和数据完整性校验和。每个段的接收者收到完好的段时,都会向发送者回送小的确认分组。如果发送者没有在指定的窗口时间内收到确认消息,发送者就认为分组已被破坏或者损毁,并重发数据。
  • 由于确认报文很小,所以TCP 允许在发往相同方向的输出数据分组中对其进行 ”捎带“。TCP 将返回的确认信息与输出的数据分组结合在一起,可以更有效地利用网络。为了增加确认报文找到同向传输数据分组的可能性,很多TCP 栈都实现了一种”延迟确认“ 算法。延迟确认算法会在一个特定的窗口时间(通常是100—200毫秒)内输出确认存放在缓冲区中,以寻找能够捎带它的输出数据分组。如果在那个时间段内没有输出数据分组,就将确认信息放在单独的分组中传送。
  • 如果希望有相反方向回传分组的时候,偏偏没有那么多,通常,延迟确认算法会引入相当大的时延。当然我们可以调整或者禁止延迟算法确认算法。
TCP 慢启动
  • TCP 慢启动限制了一个TCP 端点在任意时刻可以传输的分组数。简单来说,每成功接收一个分组,发送端就有了发送另外两个分组的权限。如果某个HTTP事务有大量数据要发送,是不能一次将所有分组都发送出去的。必须发送一个分组,等待确认,然后可以发送两个分组,每个分组都必须被确认,这样就可以发送四个分组了,依次类推。这种方式被称为 ” 打开拥塞窗口 “。
  • 由于存在这种拥塞控制特征,所以新连接的传输速度会比已经交换过一定量数据的、”已调谐“ 连接慢一些。由于已调谐连接要更快一些,所以HTTP 中有一些可以重用现存连接的工具。
Nagle 算法与TCP_NODELAY
  • 如果TCP 发送了大量包含少量数据的分组,网络的性能就会严重下降。Nagle 算法试图在发送一个分组之前,将大量TCP数据绑定在一起,已提高网络效率。
  • Nagle 算法鼓励发送全尺寸(LAN 上最大尺寸的分组大约是1500字节,在因特网上得几百字节)的段。只有其他分组都被确认之后,Nagle 算法才允许发送非全尺寸的分组。如果其他分组任然在传输过程中,就将部分数据缓存起来。只有当挂起分组被确认,或者缓存中积累了足够发送一个全尺寸分组的数据时,才会将缓存的数据发送出去。
  • Nagle 算法会引发几种HTTP 性能问题。首先,小的HTTP报文可能无法填满一个分组,可能会因为等待奈雪儿永远不会到来的额外数据而产生时延。其次,Nagle 算法与延迟确认之间的交互存在问题----Nagle 算法会阻止数据的发送,直到有确认分组抵达为止,但确认分组自身会延迟确认算法延迟100–200毫秒。
  • HTTP 应用程序常常会在自己的栈中设置参数TCP_NODELAY,禁止Nagle 算法,提高性能。但是如果要这么做的话,一定要确保会向TCP 写入大块的数据,这样就不会产生一堆小分组了。
TIME_WAIT 累积与端口耗尽
  • 当某个TCP 端点关闭TCP 连接时,会在内存中维护一个小的控制块,用来记录最近所关闭连接的IP地址和端口号。这类信息只会维护一小段时间,通常是所估计的最大分段使用期的两倍左右,已确保在这段时间内不会创建具有相同地址和端口号的新连接。实际上,这个算法可以防止在2MSL时间内创建、关闭并重新创建两个具有相同IP地址和端口号的连接。
  • 客户端每次连接到服务器上去时,都回获得一个新的源端口,以实现连接的唯一性。但由于可用源端口的数量有限(比如6000个),而且在2MSL秒(比如120秒)内连接是无法重用的,连接率就被限制在了 6000/120 = 500次/秒。如果再不断进行优化,并且服务器的连接率不高于500次/秒,就可确保不会遇到 TIME_WAIT端口耗尽问题。要修正这个问题,可以增加客户端负载生成机器的数量,或者确保客户端和服务器在循环使用几个虚拟IP 地址以增加更多的连接组成。
  • 即使没有遇到端口耗尽问题,也要特别小心有大量连接处于打开状态的情况,或为处于等待状态的连接分配了大量控制块的情况。在有大量打开连接或控制块的情况下,有些操作系统的速度会严重减缓。

HTTP连接的处理

connection 首部
  • 在某些情况下,两个相邻的HTTP 应用程序会为他们共享的连接应用一组选项。HTTP 的connection 首部字段中有一个逗号分隔的连接标签列表,这些标签为此连接指定了一个不会传播到其他连接中去的选项。
  • connection 首部可以承载3中不同类型的标签,因此有时会很令人费解:
  • HTTP 首部字段名,列出了只与此连接有关的首部
  • 任意标签值,用于描述此连接的非标准选项
  • 值 close ,说明操作完成之后需要关闭这条持久连接。
  • 如果连接标签中包含了一个HTTP 首部字段的名称,那么这个首部字段就包含了与一些连接有关的信息,不能将其转发出去。在将报文转发出去之前,必须删除connection 首部列出的所有首部字段。
串行事务处理时延
  • 假设有一个包含了3个嵌入图片的Web页面。浏览器需要发起4个HTTP 事务来显示此页面;1个用于顶层的HTML 页面,3个用于嵌入的图片。如果每个事务都需要一个新的连接,那么连接时延和慢启动时延就会叠加起来。
  • 除了串行加载引入的实际时延之外,加载一幅图片时,页面上其他地方都没有动静也会让人觉得速度很慢。用户希望能够同时加载多副图片。
  • 串行加载的另一个缺点是,有些浏览器在对象加载完毕之前无法获知对象的尺寸,而且他们可能需要尺寸信息来决定将对象放在屏幕的什么位置上,所以在加载了足够多的对象之前,无法在屏幕上显示任何内容。在这种情况下,可能浏览器串行装载的对象的进度很正常,但用户面对的却是一个空白的屏幕,对装载的进度一无所知。

并行连接

  • HTTP 允许客户端打开多条连接,并行地执行多个HTTP 事务。
  • 并行连接可能会提高页面的加载速度。
  • 即使并行连接的速度可能会更快,但并不一定总是更快。客户端的网络带宽不足时,大部分的时间可能都是用来传送数据的。在这种情况下,一个连接到速度较快服务器上的HTTP 事务就会很容易地耗尽所有可用的带宽。如果并行加载多个对象,每个对象都会去竞争这有限的带宽,每个对象都会以较慢的速度按比例加载,这样带来的i性能提升就很小,甚至没有什么提升。
  • 实际上,浏览器确实使用了并行连接,但他们会将并行连接的总数限制为一个较小的值(通常是4个)。服务器可以随意关闭来自特定客户端的超量连接。
  • 并行连接可能让人”感觉“更快一些,但是实际上并没有。因为资源就那么多,只是用户觉得页面加载的更快了。

持久连接

  • HTTP/1.1 允许HTTP 设备在事务处理结束之后将TCP 连接在打开状态,以便为未来的HTTP 请求重用现存的连接。在事务处理结束之后任然保持在打开状态的TCP 连接被称之为持久连接。非持久连接会在每个事务结束之后关闭。持久连接会在不同事务之间保持打开状态,直到客户端或服务器决定将其关闭为止。
  • 并行连接的一些缺点
    • 每个事务都会打开/关闭一条新的连接,会耗费时间和带宽
    • 由于TCP 慢启动特征的存在,每条新连接的性能都会有所降低
    • 可打开的并行连接数量实际上是有限的
  • 持久连接有一些比并行连接更好的地方。持久连接降低了时延和连接建立的开销,将连接保持在已调谐状态,而且减少了打开连接的潜在数量。但是,管理持久连接要小心,不然就会累积出大量的空闲连接,耗费本地以及远程客户端和服务器上的资源。
  • 持久连接有两种类型:比较老的 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,会在发回响应报文之后关闭连接。
  • 参数 timeout 是在 keep-alive 响应首部发送的。它估计了服务器希望将连接保持在活跃状态的时间。这并不是一个承诺值。
  • 参数max 是在 keep-alive 响应首部发送的。它估计了服务器还希望为多少个事务保持此连接的活跃状态。这并不是一个承诺值。
  • keep-alive 首部还可支持任意未经处理的属性,这些属性主要用于诊断和调试。语法为 name 【=value】
  • keep-alive 首部完全是可选的,但只有在提供 connect:keep-alive 时才能使用它。
    ** Keep-Alive 连接的限制和规则**
  • 在HTTP/1.0 中,keep-alive 并不是默认使用的。客户端必须发送一个connection:keep-alive 请求首部来激活 keep-alive 连接。
  • connect:keep-alive 首部必须随所有希望保持持久连接的报文一起发送。如果客户端没有发送connect:keep-alive 首部,服务器就会在那条请求之后关闭连接。
  • 客户端探明响应中没有connect:keep-alive 响应首部,就可以知道服务器发出响应之后是否会关闭连接。
  • 实体的主体部分长度必须是确定的,也就是说实体的主体部分必须有正确的content-length。在一条keep-alive 信道回送错误的content-length 是很糟糕的事情,这样的话,事务处理的另一端就无法精确的检测出一条报文的结束和另一条报文的开始了。
  • 代理和网关必须执行connection 首部的规则。代理或网关必须在将报文转发出去或将其高速缓存之前,删除在 connection 首部中命名的所有首部字段以及connection 首部自身。
  • 严格来说,不应该与无法确定是否支持connection 首部的代理服务器建立keep-alive 连接,以防止出现哑代理问题。
  • 除非重复发送请求会产生一些副作用,否则如果在客户端收到完整的响应之前连接就关闭了,客户端就一定要做好重试请求准备。
Keep-Alive 和哑代理
  • connection 首部和盲中继:问题出在了代理上-----尤其是那些不理解connection 首部,而且不知道在沿着转发链路将其发送出来之前,应该将首部删除的代理。很多老的或简单的代理都是盲中继,他们只是将字节从一个连接转发到另一个连接中去,对 connection 首部进行特殊的处理。
  • 为了避免出现此类代理通信问题,现在的代理绝不能转发connection 首部和所有名字出现在 connection 值中的首部。
  • 在网景的变通做法是:浏览器会向代理发送非标准的 proxy-Connection 扩展首部,而不是官方支持的著名的 connection 首部。如果代理是盲中继,它将无意义的 proxy-connection 首部转发给Web 服务器,服务器会忽略此首部,不会带来任何问题。但如果代理是个聪明的代理(能够理解持久的握手动作),就用一个connection 首部取代无意义的proxy-connection 首部,然后将其发送给服务器,以收到预期的效果。
  • 但是这个解决方案还是有问题的,在客户端和服务端只有一个代理的时候可以使用这种方案来解决问题,但是如果在哑代理的任意一侧还有一个聪明代理,这个问题就会再次露头了。
    在这里插入图片描述

在这里插入图片描述

HTTP/1.1 持久连接
  • HTTP/1.1 在默认情况下所有连接都是持久的。
  • HTTP/1.1 客户端假定在收到响应后,除非响应中包含了Connection:close 首部,不然HTTP/1.1 连接就任维持在打开状态。但是,客户端和服务器可以随时关闭空闲的连接。不发送 Connection:close 并不意味着服务器承诺永远将连接保持在打开状态。
持久连接的限制和规则
  • 发送了connection:close 请求首部后,客户端就无法在那条连接上发送更多的请求了。
  • 如果客户端不想在连接上发送其他请求了,就应该在最后一条请求中发送一个connection:close 请求首部。
  • 只有当连接上所有的报文都正确的、自定义报文长度时----也就是说,实体主体部分的长度都和相应的content-length 一致,或者是用分块传输编码方式编码的-----连接才能持久保持。
  • HTTP/1.1 的代理必须能够分别管理与客户端和服务器的持久连接-----每个持久连接都只适用于一跳传输。
  • HTTP/1.1 的代理服务器不应该与HTTP/1.0 客户端建立持久连接,除非他们了解客户端的处理能力。实际上,做到这一点非常难。
  • 尽管服务器不应该试图在传输报文的过程中关闭连接,而且在关闭连接之前至少应该响应一条请求,但不管connection首部取了什么值。HTTP/1.1 设备都可以在任意时刻关闭连接。
  • HTTP/1.1 应用程序必须能够从异步的关闭中恢复出来。只要不存在可能会累积起来的副作用,客户端都应该重试这条请求。
  • 除非重复发起请求会产生副作用,否则如果在客户端收到整条响应之前连接关闭了,客户端就必须要重新发起请求。
  • 一个用户客户端对任何服务器或代理最多只能维护两条持久连接,以防服务器过载。代理可能需要更多到服务器的连接来支持并发用户的通行,所以,如果有N个用户访问服务器的话,代理最多要维持2N条到任意服务器或父代理的连接。
管道化连接
  • HTTP/1.1 允许在持久连接上可选的使用请求管道。这是这keep-alive 连接上的进一步性能优化。在响应到达之前,可以将多条请求放入到队列。当第一条请求通过网络流向地球另一端的服务器时,第二条和第三条请求也可以开始发送了。在高时延网络条件下,这样做可以降低网络的环回时间,提高性能。
对管道化连接有几条限制
  • 如果HTTP 客户端无法确认连接是持久的,就不应该使用管道。
  • 必须按照与请求相同的顺序回送HTTP 响应。HTTP 报文中没有序列号标签,因此如果收到的响应失序了,就没有办法将其与请求匹配起来。
  • HTTP客户端必须做好连接会在任意时刻关闭的准备,还要准备好重发所有未完成的管道化请求。如果客户端打开了一条持久连接,并立即发出了10条请求,服务器可能在只处理5个之后关闭了连接,剩下的5条请求会失败,客户端必须能够应对这些过早关闭连接的情况,重新发出这些请求。
  • HTTP 客户端不应该用管道化的方式发送回产生副作用的请求(比如POST)。总之,出错的时候,管道化方式会阻碍客户端了解服务器执行的是一系列管道化请求中的哪一些。由于无法安全地重试POST 这样的非幂等请求,所以出错时,就存在某些方法永远不会被执行的风险。
关闭连接的奥秘
任意的解除连接
  • 所有的HTTP 客户端、服务器或代理都可以在任意时刻关闭一条TCP 传输连接。通常会在一条报文结束时关闭连接。
  • 对管道化持久连接来说,这种情形是很常见的。HTTP 应用程序可以在经过任意一段时间之后,关闭持久连接。比如,在持久连接空闲一段时间之后,服务器可能会决定将其关闭。
  • 但是,服务器永远都无法确定在它关闭 “ 空闲 ” 连接的那一刻,在线路那一头的客户端有没有数据要传送。如果出现这种情况,客户端就会在写入半截请求报文时发现了连接错误。
Content-Length 以及截尾操作
  • 客户端或者代理收到一条随连接关闭而结束的HTTP 响应,且实际传输的实体长度与Content-Length 并不匹配(或者没有Content-Length)时,接收端就应该质疑长度的正确性。
  • 如果接收端是个缓存代理,接收端就不应该缓存这个响应(以降低今后将潜在的错误报文混合起来的可能)。代理应该将有问题的报文原封不动地转发出去,而不应该试图去 “ 校正”Content-Length ,以维护语义的透明性。
连接关闭容限、重试以及幂等性
  • 即使在非错误情况下,连接也可以在任意时刻关闭。HTTP 应用程序要做好正确处理非预期关闭的准备。如果在客户端执行事务的过程中,传输连接关闭了,那么除非事务处理会带来一些副作用,否则客户端就应该重新打开连接,并重试一次。对管道化连接来说,这种情况更加严重一些。客户端可以将大量请求放入队列中排队,但源端服务器可以关闭连接,这样就会留下大量未处理的请求,需要重新调度。
  • 副作用是很重要的问题,如果在发送一些请求数据之后,收到返回结果之前,连接关闭了,客户端不知道服务器实际激活了多少事务。比如:向一个在线书店POST 一张订单,就不能重复执行,不然会有下一张订单的危险。
  • 如果一个事务,不管是执行一次还是很多次,得到的结果都相同,这个事务就是幂等的。客户端不应该以管道化方式传送非幂等请求(比如POST)。否则,传输连接的过早终止就会造成一些不确认的后果,要发送一条非幂等请求,就需要等待来自前一条请求的响应状态。
  • 尽管用户Agent 代理可能会让操作员来选择是否对请求进行重试,但一定不能自动重试非幂等方法或序列。比如,大多数浏览器都会在重载一个缓存的POST 响应时提供一个对话框,询问用户是否希望再次发起事务处理。
正常关闭连接
  • TCP 连接是双向的,TCP连接的每一端都有一个输入队列和一个输出队列,用于数据的读或写。放入一段输出队列中数据最终会出现在另一端的输入队列中。
  • 完全关闭和半关闭:应用程序可以关闭TCP 输入和输出信道中的任意一个,或者将两者都关闭了。套接字调用close() 会将连接的输入和输出信道都关闭了。这就被称作 “ 完全关闭 ”,还可以用套接字调用shutdwon() 单独关闭输入或输出信道,这就是称为“半关闭”。
  • TCP 关闭以及重置错误:简单的HTTP 应用程序可以只使用完全关闭。但当应用程序开始与很多其他类型的HTTP 客户端、服务器和代理进行对话且开始使用管道化持久连接时,使用半关闭来防止对等实体收到非预期的写入错误就变得很重要了。
  • 总之,关闭连接的输出信道总是很安全的。连接另一端的对等实体会在从其缓冲区中读出所有数据之后收到一条通知,说明流结束了,这样它就知道你将连接关闭了。
  • 关闭连接的输入信道比较危险,除非你知道另一端不打算在发送其他数据了。如果另一端向你已关闭输入信道发送数据,操作系统就会向另一端的机器回送一条TCP “ 连接被对端重置” 的报文,大部分操作系统都会将这种情况作为很严重的错误来处理,删除对端还未读取的所有缓存数据,对于管道化连接来说,这是非常糟糕的事情。
  • 比如:你已经在一条持久连接上发送了10条管道式请求了,响应也已经收到了,正在操作系统的缓冲区存着呢,应用程序还未将其读走。现在,假设你发送了第11条请求,但服务器认为你使用这条连接的时间已经够长了,决定将其关闭。那么你的第11条请求就会被发送到一条已经关闭的连接上去,并会向你回送一条重置信息。这个重置信息会清空你的输入缓冲区。当你最终要去读取数据的时候,会得到一个连接被对端重置的错误,已经缓存的未响应数据都丢失了,尽管大部分都已经抵达你的机器了。
正常关闭
  • HTTP 规范建议,当客户端或服务器突然要关闭一条连接时,应该 “ 正常的关闭传输连接”,但它并没有说明如何去做。
  • 总之,实现正常关闭的应用程序首先应该关闭他们输出信道,然后等待连接另一端的对等实体关闭它的输出信道。当两端都告诉对方他们不会再发送任何数据(比如关闭输出信道)之后,连接就会被完全关闭,而不会有重置的危险。
  • 但是不幸的是,无法确保对等实体会实现半关闭,或对其进行检查。因此,想要正常关闭连接的应用程序应该先关闭其输出信道,然后周期性的检查其输入信道的状态(查找数据、或流的末尾)。如果在一定的时间区间内对端没有关闭输入信道,应用程序可以强制关闭连接,以节省资源。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值