一、TCP连接
世界上几乎所有的HTTP
通信都是TCP/IP
承载的,TCP/IP
是全球计算机及网络设备都在使用的一种常用的分组交换网络分层协议集。
1.1 TCP的可靠数据管道
TCP
连接是因特网上的可靠连接,为HTTP
提供了一条可靠的比特传输管道,从TCP
连接一段填入的字节会从另一端以原有的顺序正确的传送出来。
1.2 TCP流是分段的,由IP分组传送
TCP
的数据通过名为IP分组
(IP数据报
)的小数据块来发送的。HTTP
要发送一条报文时会以流的形式使用TCP
连接传输,TCP
接收到数据流之后将其进行截断分组封装到IP分组
内。每个IP分组
由以下几部分组成:
- 一个
IP分组
首部(通常为20字节),包括源IP
地址和目的IP
地址,长度以及一些其他的标记; - 一个
TCP段
首部(通常为20字节),包括TCP
端口号,TCP
控制标记以及用于数据排序和完整性检查的数据; - 一个
TCP
数据块。
HTTP
模型:HTTP
-> TCP
-> IP
...
HTTPS
模型:HTTP
-> TLS
或SSL
-> TCP
-> IP
...
1.3 保持TCP连接的正常运行
TCP
连接时通过4哥值来识别的,这四个值一起唯一定义一条连接:
<源IP 源端口 目标IP 目标端口>
这其中任一值不同都不能称为同一个连接。
二、对TCP性能的考虑
HTTP
位于TCP
上层,以所HTTP
事务性能再很大程度上取决于底层TCP
通道的性能。
2.1 HTTP事务的时延
一般来说,在整个HTTP
事务周期内,服务端对事务的处理所占时间可能是最短的(除非服务端超载或者处理复杂的动态资源),所以大部分时延都是由TCP
网络时延构成的。
HTTP
事务时延有以下几种原因:
-
DNS
查询,可能最多会花费数十秒时间。 - 建立
TCP
连接,一个TCP
连接需要SYN/ACK+SYN
三次报文传输才能建立,但是第三次的IP数据报
中可以装载HTTP
报文了。 - 服务器处理事务,连接建立后,服务器接收到客户端的报文,然后开始处理,处理事务也会花费一定时间。
- 网络传输,服务端返回结果在网络中传输需要一定时间。
从以上原因可以看出,HTTP
时延的大小取决于:硬件速度,网络和服务器负载,报文尺寸,以及端到端的距离,还有TCP
协议产生的时延。
2.2 TCP产生时延的总结
-
TCP
连接握手 -
TCP
慢启动 - 数据聚集的
Nagle
算法 - 用于捎带确认的
TCP
延迟确认算法 -
TIME_WAIT
时延和端口耗尽
2.3 握手时延
众所周知建立TCP
连接需要三次握手:
- 客户端发送一个
TCP
分组(40~60字节),分组中设置了SYN
标记。 - 服务端接收连接之后,对连接参数进行计算,并把
TCP
分组的SYN
和ACK
都置位。 - 最后客户端向服务端返回一条确认
TCP
分组,其中ACK
被置位,并且在这个分组中可以添加HTTP
报文。
2.4 延迟确认
TCP
为了确保可靠传输,实现了确认机制,及每个TCP
段都有一个序列号和数据完整性校验和,段的接收者在收到完好的段后会会向发送者回复一个确认TCP
分组,如果发送者没有在指定窗口时间内收到确认分组就会认为发送失败并且会重新发送数据。
由于确认分组很小,所以TCP
会允许在发往同方向的分组中对其进行捎带,为了增加捎带的可能,TCP
实现了一种延迟确认的算法。即将确认信息缓存下来在特定时间窗口内(100 ~ 200ms)如果有可以捎带的TCP
分组,那么从缓存中取出来确认信息,然后进行捎带。如果在这个窗口内没有同方向的TCP
分组,那么就需要创建一个TCP
分组单独传输确认信息。
2.5 TCP慢启动
TCP
连接刚建立后,传输速率会被限制,然后随着时间的推移慢慢提高传输速率,主要用于防止网络突然过载。
2.6 Nagle算法
一个IP分组
最小都携带了40字节大小的首部,如果被传输的数据很少的话,那么TCP
的效率就想当低了,所以Nagle
算法鼓励发送全尺寸的段(因特网上一般为几百字节),所以当有TCP
段还没有收到确认分组时,也会被缓存起来,和新的数据凑在一起凑够全尺寸再发送出去。Nagle
算法和TCP
的延迟确认机制存在一定的冲突。。。所以大部分HTTP
应用会设置TCP_NODELAY
来禁用Nagle
算法。
2.7 端口耗尽
当某个TCP
连接关闭时,会在内存中维护一个小的控制块,用来记录关闭连接端口和IP
地址,时间为最大分段使用时间的2倍(2MSL,通常为2分钟)。在这段时间内,该端口不能再建立重复的连接,而端口时有上限的,所以连接速率被限制在60000/120 = 500 次/秒
。
三、HTTP连接
提高HTTP
连接性能的技术:
- 并行连接,通过多条
TCP
同时发起HTTP
事务。 - 持久连接,重用
TCP
连接。 - 管道化连接,通过共享的
TCP
连接发起并发HTTP
请求。 - 复用的连接,交替传送请求和响应的报文。
四、持久连接
HTTP/1.1
允许HTTP
客户端在事务处理结束之后将TCP
连接保持在打开状态,以便为未来的HTTP
请求重用现存的连接。
在事务处理结束后仍然保持在打开状态的
TCP
连接被称为持久连接。
持久连接有两种类型:HTTP/1.0+
的keep-alive
和HTTP/1.1
的persistent
。
4.1 使用keep-alive
客户端发送HTTP
请求时需加首部connection:keep-alive
,如果服务器支持keep-alive
,并且同意了持久连接,那么响应的首部也会加入connection:keep-alive
,如果服务端不同意持久连接,那么就不会返回这个首部,并且在请求结束后会关闭空闲的TCP
连接。响应首部中还会有keep-alive
首部对持久连接进行描述:
-
timeout
,服务器预期保持活跃时间。 -
max
,估计服务器能为多少个HTTP
事务保持活跃连接。 - 扩展值,一般为
name[=value]
,多个在值以逗号隔开。
4.2 使用keep-alive的限制
- 客户端必须自主发送
connection:keep-alive
来激活持久连接。 - 服务端必须也返回
connection:keep-alive
才表示激活了持久连接,否则服务端随时会关闭该连接。 - 使用持久连接的
HTTP
事务必须在在首部写入正确的content-length
,则否TCP
无法区分该TCP
段属于哪一个HTTP
事务。 - 代理和网关在缓存持久连接的请求时需要删除
connection
首部。 - 服务端应当忽略
HTTP/1.0
及以下协议的connection
,因为他们不支持持久化连接,这个首部可能是误转发的。
4.3 keep-alive哑代理
某些代理服务器可能根本不支持keep-alive
,但是它转发的时候没有删除客户端的connection:keep-alive
,导致服务端以为它想建立持久连接,于是服务端返回keep-alive
,代理虽然不知道这个首部是什么意思,于是又转发给了客户端,客户端收到keep-alive
后以为服务器同意建立持久连接,但是在复用TCP
连接的时候,代理服务器根本不支持持久连接,所以请求就会被挂起。
4.4 proxy-connection
Netscape
的浏览器使用proxy-connection
替代connection
,当低版本代理转发该请求时,直接转发到服务器,服务器不认识该首部,于是直接忽略,但是遇到支持该首部的代理,会把该首部再换成connection
,便可以与服务器正常建立持久连接了。
4.5 HTTP/1.1 persistent持久连接
不像HTTP/1.0
必须主动声明持久连接,在HTTP/1.1
中默认都使用持久连接,除非主动在首部中加入connection:close
表示短链接。
4.6 使用persistent
- 客户端发送
connection:close
后就无法再使用这个TCP
连接了。 - 和
keep-alive
一样,使用持久连接的HTTP
请求需要正确的content-length
或者使用分块传输编码。 -
HTTP/1.1
的代理不应该与HTTP/1.0
的客户端建立长连接。 - 一个客户端最多与服务器维持2条长连接。
4.7 管道化连接
HTTP/1.1
允许在持久连接上使用请求管道,即在响应到达之前,可以将多条请求放入队列,当第一条请求发出之后,第二条第三条等也可以逐个开始发送了。这样做的限制是:
- 如果客户端无法确认当前使用的是否持久连接,那么就不应该使用管道。
- 必须按照与请求相同的顺序交付
HTTP
响应,因为HTTP
没有序号标签。 - 客户端需要做好处理请求失败的处理,因为长连接随时会关闭。
- 不允许使用管道发送
POST
等非幂等请求,否则不知道失败后重新请求是否会造成严重影响。
五、关闭连接的奥秘
5.1 任意关闭连接
对于持久连接来说,服务器在该连接的空闲时间的任意时刻都可能关闭该连接,而我们知道HTTP
请求可能被分为多个TCP
段,那么可能造成一个HTTP
请求的部分数据传输出现问题?客户端需要做好错误重传处理!
5.2 重试的幂等性
前面说过,持久连接任何时刻都可能会被关闭,尤其是使用了管道化传输的,在队列中积压了大量的请求,在请求失败后,如果客户端尝试重新打开连接传输,对于非幂等请求可能造成很大的副作用,那么如POST
,PUT
,DELETE
等请求方法就不应该使用持久连接管道化传输。
参考文献
[1]《HTTP权威指南》