HTTP学习笔记:连接管理

HTTP规范对HTTP报文解释得很清楚,但对HTTP连接介绍并不多,而HTTP连接时HTTP报文传输的关键通道。本文将介绍HTTP是如何使用TCP连接,HTTP的优化以及管理连接时应该注意的问题等。

1. TCP连接

世界上几乎所有的HTTP通信都是由TCP/IP承载的,TCP/IP是一种常用的分组交换网络分层协议集。客户端应用程序可以打开一条TCP/IP连接,连接到服务器应用程序。一旦连接建立,客户端和服务器计算机之间交换报文就永远不会丢失、受损或失序。

TCP为HTTP提供了一条可靠的比特传输管道。从TCP连接一端流入的字节会从另一端以原有的顺序正确地传送出来。TCP的数据是通过名为IP分组的小数据块来发送的。每个TCP段由IP分组承载,IP分组包括:IP分组首部、TCP段首部、TCP数据块。在任意时刻计算机都可以有几条TCP连接处于打开状态。TCP是通过端口号来保持所有连接正常运行的,它由<源IP地址、源端口号、目的IP地址、目的端口号>唯一确定。

操作系统通常提供了一些用套接字操纵TCP连接的API,允许用户创建TCP的端点数据结构,与远程服务器的TCP端点进行连接,并对数据流进行读写。TCP API隐藏了所有底层网络协议细节。下图展示了客户端和服务器如何通过TCP套接字接口进行通信,至于套接字API编程则不再赘述。

TCP套接字接口通信过程

注:原书简要介绍了对TCP的性能的考虑,但TCP是个很复杂的话题,属于计算机网络的范畴,因此此处略过TCP性能考虑的详细介绍。最常见的TCP的相关时延包括:TCP连接建立握手、TCP慢启动拥塞控制、数据聚集的Nagle算法、用于捎带确认的TCP延迟确认算法、TIME_WAIT时延和端口耗尽。

2. HTTP连接的处理

2.1 常被误解的Connection首部

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

  • HTTP首部字段名,列出了只与此连接有关的首部。在将报文转发出去之前,必须删除列出的所有首部字段。
  • 任意标签值,用于描述此连接的非标准选项
  • 值close,说明操作完成之后需关闭这条持久连接

2.2 串行连接

如果只对连接进行简单的管理,TCP的性能时延可能会叠加起来。比如,假设有一个包含3个图片的Web页面,浏览器需要发起4个HTTP事务来显示。加载一副图片时,页面上其他地方都没有动静会让人觉得速度很慢,此外,浏览器在对象加载完毕前无法获知对象的尺寸,所以串行事务会让用户面对一个空白的屏幕,对装载进度一无所知。为了提高HTTP的连接性能,有并行连接,持久连接,管道化连接等技术可以改善。

2.3 并行连接

HTTP允许客户端打开多条连接,并行地执行多个HTTP事务。并行连接可能会提高页面的加载速度,但有的情况下反而更慢。比如客户端的网络带宽不足时(如通过28.8kbps的Modem上网),一个连接到速度较快的服务器的HTTP事务就会很容易耗尽所有可用带宽。而且打开大量连接会消耗很多内存,这会造成服务器性能严重下降。实际上,浏览器确实使用了并行连接,但并行的总数会限制为一个较小的值(通常是4个)。服务器也可以关闭来自特定客户端的过量连接。

2.4 持久连接

HTTP在允许事务处理结束后将TCP连接保持在打开状态,以便未来的HTTP请求重用现存的连接,这被称为TCP的持久连接。相比并行连接,持久连接降低了时延和连接建立的开销,减少了打开连接的潜在数量。但是要小心管理这些连接,不然会累积出大量的空闲连接,耗费机器资源。现在,很多Web应用程序都会打开少量的并行连接,其中每一个都是持久连接。持久连接有两种类型:较老的HTTP/1.0+“keep-alive”连接和现代HTTP/1.1“persistent”连接。

2.4.1 Keep-Alive持久连接

Keep-Alive已经不再使用了,但是有一些浏览器和服务器对keep-alive握手的使用仍然相当广泛,因此这里简单叙述一下keep-alive操作。首先客户端可以通过包含Connection: Keep-Alive首部请求将一条连接保持在打开状态,如果服务器同意,就在响应中包含相同的首部。若响应中没有相应的首部,客户端就会认为服务器不支持keep-alive,接收到响应后关闭连接。注意,Keep-Alive首部只是请求将连接保持在活跃状态,发出请求后,双端不一定会同意进行keep-alive会话,它们可以在任意时刻关闭或限制空闲的keep-alive连接。下面给出一个用Keep-Alive通用首部指定选项来调节keep-alive行为的例子,该例子说明服务器最多还会为另外5个事务保持连接打开状态,将打开状态保持到连接空闲了2分钟之后:

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

使用keep-alive连接有一些限制:Connection: Keep-Alive首部必须随所有希望保持持久连接的报文一起发送;实体的主体部分必须有正确的Content-Length,才能打开持久连接;代理和网管必须在将报文转发出去之前,删除在Connection首部及其中命名的所有首部字段(Connection首部应该只对离开客户端的TCP链路产生影响,但很多老的或简单的代理都是盲中继,即只将字节从一个连接转发到另一个连接而不对首部进行处理,代理对keep-alive对话毫不知情,而客户端和服务器都认为它们在进行keep-alive对话,这就会造成错误的通信方式,具体情况如下图所示)。为了避免此类代理通信问题的发生,现代的代理都决不能转发Connection首部和所有名字出现在Connection值中的首部。

Keep-Alive无法与不支持Connection首部的代理进行互操作

对于单个盲中继问题,Netscape的浏览器引入一个名为Proxy-Connection的新首部,即使盲中继代理转发这个首部,Web服务器也会将其狐狸。如果代理是“聪明”的,收到这个首部后就会发送自己的Connection: Keep-Alive来建立keep-alive连接。当然,这种做法只能解决只有一个代理的情况,如果客户端和服务器之间有多个代理,同样会有问题。透明的Web应用程序正确实现持久连接时非常重要的。

2.4.2 HTTP 1.1持久连接

HTTP/1.1逐渐停止了对keep-alive连接的支持,用一种名为persistent connection的改进型设计取代了它。在HTTP/1.1下,持久连接默认情况下是激活的,除非响应报文中包含Connection: close,否则连接就维持在打开状态。当然,客户端和服务器仍然可以随时关闭空闲的连接。

使用persistent连接同样有一些限制:发送Connection: close请求首部后,客户端就无法在那条连接上发送更多请求了;实体主体部分长度都和相应的Content-Length一致,连接才能持久保持;服务器不应试图在传输报文的过程中关闭连接,而且关闭之前应该至少响应一条请求;客户端收到整条响应之前连接关闭了,客户端都要重新发起请求,除非重复发请求会产生副作用。

2.4.3 管道化连接

HTTP/1.1允许在持久连接上可选地使用请求管道,在响应到达之前,可以将多条请求放入队列,当第一条请求发出之后,后面的请求也可以开始陆续发送。这样可以在高时延网络条件下降低网络的环回时间,提高性能。管道化连接也有一些限制:如果HTTP客户端无法确认连接时持久的,就不应该使用管道;必须按照与请求相同的顺序回送HTTP响应;客户端必须做好连接会在任意时刻关闭的准备,并准备好重发所有未完成的管道化请求;客户端不应该用管道化的方式发送会产生副作用的请求(如POST)。

下图是各种连接的时延对比。

三种连接形式的时延对比

2.5 关闭连接

无论是错误还是非错误情况,连接都可能在任意时刻关闭。HTTP应用程序要做好正确处理非预期关闭的准备。通常来说当传输连接意外关闭时,客户端会重新打开并重试。注意,有些事务比如GET一个静态页面,反复执行多次也不会有什么变化(幂等请求),但对于POST(非幂等请求),就不能重复执行。

TCP连接是双向的,连接的每一端都有一个输入队列和一个输出队列。应用程序可以关闭TCP输入和输出信道中的任一个,或者两者都关闭,分别称为“完全关闭”和“半关闭”。其中,关闭连接的输出信道总是安全的,而关闭输入信道则比较危险,除非你知道另一端不打算再发送其他数据。如果另一端向你已关闭的输入信道发送数据,操作系统就会向另一端的机器回送一条TCP“连接被对端重置”的报文。这个重置信息会清空输入缓冲区,尽管其中的大部分都已经成功抵达你的机器。

总之,实现正常关闭的应用程序首先应该关闭它们的输出信道,然后等待连接另一端关闭它的输出信道。当两端都告诉对方它们不会再发送任何数据之后,连接就会被完全关闭,而不会有重置的危险。

参考文献:人民邮电出版社《HTTP权威指南》第4章

当前网速较慢或者你使用的浏览器不支持博客特定功能,请尝试刷新或换用Chrome、Firefox等现代浏览器