本篇文章为蠢作者对 TCP Keep-alive 和 HTTP Keep-alive 的分析
平常所说到的 Keep-alive 有两种,一种为 TCP Keep-alive,另一种为 HTTP Keep-alive。首先说明这两个概念是不同的
TCP 的 Keep-alive (截取自TCP Keepalive HOWTO并翻译)
首先TCP是通过三次握手建立连接的,用来传输上层协议的报文。难免会遇到上层协议迟迟不发送报文,或者隔很久才会发送一次报文的情况(例如 WebSocket),这时便需要判断连接是断掉了还是确实没有数据传输。Keep-alive 报文便是为此而生的。当超过一段时间之后,会发送一个数据为空的报文给对方,如果对方回应了这个报文,说明对方还在线,连接可以继续保持,如果对方没有报文返回,并且重试了多次无果则认为连接已经丢失。
HTTP 的 Keep-alive (摘自 wiki-HTTP持久连接)
HTTP持久连接(HTTP persistent connection,也称作HTTP keep-alive 或HTTP connection reuse)是使用同一个TCP连接来发送和接收多个HTTP请求/应答,而不是为每一个新的请求/应答打开新的连接的方法。
综合上面的定义,蠢作者认为 TCP 的 Keep-alive 具体是指一种用来检测连接是否存活的机制(心跳包);而 HTTP 的 Keep-alive 则是复用一条 TCP 连接来传输 HTTP request/response 的技术(减少三次握手四次挥手的开销)
好了,蠢作者码了段代码,这段代码中在 client 发送 HTTP request 后,经过 30s 才会发送对应的 response。借此进行抓包分析
import tornado.ioloop
import tornado.gen
class MainHandler(tornado.web.RequestHandler):
@tornado.gen.coroutine
def get(self):
yield tornado.gen.sleep(30)
self.write("Hello, world")
def make_app():
return tornado.web.Application([
(r"/", MainHandler),
], debug=True)
if __name__ == "__main__":
app = make_app()
app.listen(8888)
tornado.ioloop.IOLoop.current().start()
抓包结果如下图
黑色高亮的便是心跳包 可以看到 HTTP 请求发送后, Server 端并没有及时返回(sleep 30s)。此时双端并没有释放掉这个 TCP 连接,而是浏览器不断地发送 Keep-alive 来检测连接是否存活。
经过测试心跳包发送的间隔 Firefox 下为 10s,而 Chrome 为 45s,curl 命令下则为 60s
其实这个心跳包的间隔是可以自定义的,相关配置在 /proc/sys/net/ipv4
目录下的这几个文件
tcp_keepalive_intvl
The number of seconds between TCP keep-alive probes 心跳包的重试间隔 默认75stcp_keepalive_probes
The maximum number of TCP keep-alive probes to send before giving up and killing the connection if no response is obtained from the other end 重试多少次认为连接失效 默认9次tcp_keepalive_time
The number of seconds a connection needs to be idle before TCP begins sending out keep-alive probes. Keep-alives are sent only when the SO_KEEPALIVE socket option is enabled. 多久未发送/接收数据后发送心跳包 默认7200s
当然也可以在编程时修改掉(以 Python
为例)
socket.TCP_KEEPCNT
<=> tcp_keepalive_probes
socket.TCP_KEEPIDLE
<=> tcp_keepalive_time
socket.TCP_KEEPINTVL
<=> tcp_keepalive_intvl
当然上面的例子使用的是 HTTP/1.1 默认 HTTP Keep-alive。那么再让我们试试 Connection: Close,看看TCP层 会不会发生变化
curl 'http://127.0.0.1:8888/' -H 'Connection: close' -H 'Host: 127.0.0.1:8888'
可以看到并没有什么卵用,证实了本文一开始的观点:HTTP 的 Keep-alive 和 TCP 的 Keep-alive 两个概念。
根据定义 HTTP 的 Keep-alive 会对 TCP 连接进行复用,那么是否只使用一条连接呢? 蠢作者还是通过一个本地服务来演示,所有资源都从同一个 web 服务获取,排除向 cdn 请求的情况。
从上图可以看到有连接被复用,但是并不是只通过一条TCP连接完成所有请求。浏览器是存在并发请求的。最大并发请求数各浏览器存在差异,Sever 端如果使用 nginx 可以通过设置 keepalive_timeout time
来指定这条 TCP 连接的维持时间。