总体概述
七层协议是一个广为人知的协议,tcp
协议是在传输层,http
协议是在应用层,也就是说客户端与服务器端先建立tcp
连接,然后在tcp
连接的基础上传送http
报文。
http
协议是一个请求-应答
的模式,也就是当没有启动keep-alive
的时候,每一次建立http
连接都是现用现建立,用完就断开
的工作样式。而如果开启了keep-alive
模式的话,客户端和服务器之间http
连接就会被保持,不会断开(超过Keep-Alive
规定的时间,意外断电等情况除外),当客户端发送另外一个请求时,就使用这条已经建立的连接。
Keep-Alive
的规定时间在客户端(浏览器里)是如何确定的呢?例如Keep-Alive: timeout=5, max=100
,表示这个TCP
通道可以保持5秒,max=100
表示这个长连接最多接收100次请求就断开。
Keep-alive
在http 1.1
版本里是默认开启的,只有加入Connection: close
才会关闭,现在大部分浏览器都是使用http 1.1
协议,所以说在客户端已经是默认发起keep-alive
的连接请求。但是能否会完成一个完整的keep-alive
还要看服务器端的具体配置情况。
在nginx
里就直接支持keepalive_timeout
指令,其使用0值来停用keep-alive
,举例配置如下:
1
2
3
4
5location /XXX/ {
alias /url/var/www/html/;
keepalive_timeout 75;
expires 5m;
}
使用长连接之后,客户端和服务端怎么知道本次传输结束呢?两部分:1. 判断传输数据是否达到了Content-Length
指示的大小,这个是最简单的最傻瓜的,普遍应用于静态的图片或者页面;2. 往往动态生成的文件没有Content-Length
,它是分块传输(chunked)
,这时候怎么办呢?就要根据chunked
编码来判断,chunked
编码的数据在最后有一个空chunked
块,表明本次传输数据结束,这种情况更多应用于动态的页面。
进一步的说chunked
HTTP请求报文的格式是这样的:
1
2
3
4<method> <request-URL> <version>
<headers>
<entity-body>
其中在请求头的地方有一个叫Content-Length
的字段,如果没有这个字段那么就会有叫Transfer-encoding
的字段,它用来表示http
报文的传输格式,这个字段的取值有很多,但是真正有意义的只有一个—chunked
。
如果一个HTTP
消息(请求消息或应答消息)的Transfer-Encoding
消息头的值为chunked
,那么,消息体由数量未定的块组成,并以最后一个大小为0的块为结束。
每一个非空的块都以该块包含数据的字节数(字节数以十六进制表示)开始,跟随一个CRLF
(回车及换行),然后是数据本身,最后块CRLF
结束。在一些实现中,块大小和CRLF
之间填充有白空格(0x20)。
最后一块是单行,由块大小(0)、一些可选的填充白空格、以及CRLF
组成。最后一块不再包含任何数据,但是可以发送可选的尾部,包括消息头字段。消息最后以CRLF
结尾。
注意1.chunked
和multipart
两个名词在意义上有类似的地方,不过在HTTP
协议当中这两个概念则不是一个类别的。multipart
是一种Content-Type
,标示HTTP
报文内容的类型,而chunked
是一种传输格式,标示报头将以何种方式进行传输;
注意2.chunked
传输不能事先知道内容的长度,只能靠最后的空chunk
块来判断,因此对于下载请求来说,是没有办法实现进度的。在浏览器和下载工具中,偶尔我们也会看到有些文件是看不到下载进度的,即采用chunked
方式进行下载;
注意3.chunked
的优势在于,服务器端可以边生成内容边发送,无需事先生成全部的内容。HTTP/2
不支持Transfer-Encoding: chunked
,因为HTTP/2
有自己的streaming
传输方式。
http keep-alive与tcp keep-alive
http的keep-alive
与tcp的keep-alive
可不是同一回事,意图也不一样。http的keep-alive
是为了让tcp活得更久一点,以便在同一个连接上传送多个http,提高socket
的效率。而tcp的keep-alive
是tcp的一种检测tcp连接状况的保鲜机制。tcp的keep-alive
是一个保鲜定时器,支持三个系统内核配置参数:
1
2
3echo 1800 > /proc/sys/net/ipv4/tcp_keepalive_time
echo 15 > /proc/sys/net/ipv4/tcp_keepalive_intvl
echo 5 > /proc/sys/net/ipv4/tcp_keepalive_probes
keepalive
是TCP保鲜定时器,当网络两端建立了tcp连接之后,闲置idle(双方没有任何数据流发送往来)了tcp_keepalive_time
后,服务器内核就会尝试向客户端发送侦测包,来判断TCP连接状况(有可能客户端崩溃、强制关闭了应用、主机不可达等等)。如果没有收到对方的回答(ack包),则会在tcp_keepalive_intvl
后再次尝试发送侦测包,直到收到对对方的ack
,如果一直没有收到对方的ack
,一共会尝试tcp_keepalive_probes
次,每次的间隔时间在这里分别是15s、30s、45s、60s、75s
。如果尝试tcp_keepalive_probe
s,依然没有收到对方的ack包,则会丢弃该TCP连接。TCP连接默认闲置时间是2小时,一般设置为30分钟足够了。
也就是说,仅当nginx
的keepalive_timeout
值设置高于tcp_keepalive_time
,并且距此tcp连接传输的最后一个http
响应,经过了tcp_keepalive_time
时间之后,操作系统才会发送侦测包来决定是否要丢弃这个TCP连接。一般不会出现这种情况,除非你需要这样做。
keep-alive与TIME_WAIT
使用http的keep-alive
,可以减少服务端TIME_WAIT
数量(因为由服务端httpd
守护进程主动关闭连接)。道理很简单,相较而言,启用keep-alive
,建立的tcp
连接更少了,自然要被关闭的tcp
连接也相应更少了。
补充
建议在服务器提供Web站点服务时(一个页面除了动态内容,还包含非常多的JS、图片、css文件等)开启keep-alive
。在“服务器提供的是一个接口服务,除了动态内容,几乎没有引用任何静态内容”这样的场景,不建议开启keep-alive
。
参考资料
http://www.cnblogs.com/skynet/archive/2010/12/11/1903347.html
https://hit-alibaba.github.io/interview/basic/network/HTTP.html
http://51write.github.io/2014/04/09/keepalive/
http://www.nowamagic.net/academy/detail/23350305