HTTP/1.1允许http设备在事务处理结束之后将TCP连接保持在打开状态,以便为未来的http请求重用现存的连接。在事务处理结束之后仍然保持打开状态的TCP连接被称为持久连接。持久连接会在不同事务之间保持打开状态,直到客户端或者服务器决定将其关闭。
已经打开的连接可以避免慢启动的拥堵适应阶段。以便更快的进行数据传输。

持久以及并行连接

并行连接的缺点:

  • 每个事务打开/关闭一个新的连接,会耗费时间和宽带的;
  • 由于TCP慢启动,每条新连接的性能都会有降低;
  • 可打开的并行连接数量实际上是有限的。

持久连接降低了时延和建立连接的开销,但是持久连接时可能会累积出大量的空闲连接。所以需要配合使用持久连接和并行连接。

HTTP/1.0+ keep-alive连接

客户端

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

服务端

如果服务器愿意为下一条请求将连接保持在打开状态,就在通用首部中包含相同的内容。如果没有包含Connection: Keep-Alive首部,客户端就认为服务器不支持keep-alive,会在发回响应报文之后关闭连接。
HTTP:1.0 keep-alive事务首部的握手过程.png

⚠️注意
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时才能使用。例如:

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

Keep-Alive连接的限制和规则

  • 在HTTP1.0中,keep-alive并不是默认使用的。客户端必须发送一个Connection:Keep-Alive请求首部来激活keep-alive连接;
  • 代理或网关必须将报文转发或将其高速缓存之前,删除在Connection首部中命名的所有首部字段以及Connection首部自身。
  • 不应该与无法确定是否支持Connection首部代理服务器建立keep-alive连接,以防止出现哑代理。

HTTP/1.1 持久连接(Persistent Connection)

HTTP/1.1 持久连接在默认情况下是激活的,要在事务处理结束之后将连接关闭,HTTP/1.1应用程序必须向报文中显示的添加一个Connection:close首部。不然HTTP/1.1 连接就仍然维持在打开状态。

⚠️
不发送Connection:close并不意味着服务器承诺永远将连接保持在打开状态。

持久连接的限制和规则

  • 发送了Connection:close请求首部之后,客户端就无法在那条连接上发送更多的请求了。
  • 如果客户端不想再连接上发送其他请求,就应该在最后一条请求中发送一个Connection:close请求首部。
  • 只有当连接上所有报文的实体主体部分的长度和首部字段Content Length一样(或者用分块传输编码方式),这样连接才能持久保持
  • HTTP1.1的代理服务器不应该与HTTP1.0客户端建立持久连接。
  • HTTP1.1设备可以在任意时刻关闭连接,尽管是出于传输报文的过程中关闭连接。

管道化连接

HTTP1.1允许在持久连接上可选的使用请求管道。这是在keep-alive连接上的进一步性能优化。
在响应到达前,可以将多条请求放入队列中,这样做可以降低网络的环回时间,提高性能。

管道化连接的几个限制

  • 如果http客户端无法确认连接是持久的,就不应该使用管道;
  • 必须按照与请求相同的顺序回送http响应。
  • http客户端必须做好连接会在任意时刻关闭的准备,还要准备好重发所有未完成的管道化请求。
  • http客户端不应该用管道化的方式发送会产生副作用的请求(比如POST)。

管道化连接.png

关闭连接

在连接管理中需要知道什么时候关闭连接以及如何去关闭连接。

“任意”接触连接

所有的HTTP客户端、服务器或者代理都可以在任意时刻关闭一条TCP传输连接。通常会在一条报文结束时关闭连接。

但是,服务器永远都无法确定在它关闭“空闲”连接那时候,在客户端有没有数据要发送。如果出现这种情况,客户端就会在写入半截请求报文时发现出现连接错误的请求。

Content-Length及截尾操作

每条HTTP响应都应该有精确的Content-Length首部,用以描述响应主题的尺寸。
客户端或者代理受到一条随连接关闭而结束的http响应,且实际传输的实体长度与Content-Length并不匹配,接收端就应该质疑长度的正确性。
如果接收端是缓存代理,接收端就不应该缓存这条响应。代理应该将有问题的报文原封不动地转发出去,而不应该试图去“校正”Content-Length。

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

如果客户端执行事务的过程中,传输连接关闭了,那么除非事务处理会带来一些副作用,否则客户端就应该重新打开连接,并重试。

幂等

如果一个事务,不管是执行一次还是多次,得到的结果都是相同的,那么这个事务就是幂等的。

GET、HEAD、PUT、DELETE、TRACE和OPTIONS方法都是的。

客户端不应该用管道化的方式传送非幂等请求(就是说比如POST请求不应该使用管道化方式)。

正常关闭连接

TCP连接都是双向的。tcp连接的每一端都有一个输入队列和一个输出队列,用于数据的读或写。
tcp双向的连接.png

完全关闭和半关闭

应用程序可以关闭tcp输入和输出信道中的任意一个,或者将两者都关闭了。

套接字调用close()会将tcp连接的输入和输出信道都关闭,这就是完全关闭
shutdown()单独关闭输入或者输出信道,被称为半关闭

TCP关闭及重置错误

总之,关闭连接的输出信道总是很安全的。连接另一端的对等实体会在其缓冲区中读出所有数据之后收到一条通知,说明流结束了。
关闭连接的输入信道比较危险,除非你知道另一端不打算再发送其他数据。如果另一端向你已关闭的输入信道发送数据,操作系统会向另一端的机器回送一条TCP“连接被对端重置”。

大部分操作系统都会将这种情况作为严重错误来处理,删除对端还未读去的所有缓存数据。但是这样做对于管道化来说简直就是噩梦,因为:比如你已经在一条持久连接上发送了10条管道式请求,响应已经收到了,正在操作系统的缓存区中存着。但是你发送了第11条请求,但是服务器认为你使用这条连接的时间太长,决定将其关闭,这个重置信息会清空你的缓冲区。

正常关闭

实现正常关闭的应用程序首先应该关闭它们的输出信道,然后等待另一端的对等实体关闭它的输出信道。当两端都告诉对方它们不会再发送任何数据之后,连接就会被完全关闭,而不会有重置风险。如果在一定的时间区间内对端没有关闭输入信道,应用程序可以强制关闭连接。