使客户端到服务器端的连接持续有效,当出现对服务器的后继请求时,Keep-Alive功能避免了建立或者重新建立连接。
HTTP/1.0中默认使用Connection:close, 在HTTP/1.1中已经默认使用Connection: keep-alive。TCP协议层默认并不开启KeepAlive功能。
用法
http1.1中因默认支持长连接,所以如果不希望使用则需要在header中指明connection的值为close;而server也不想支持,则在response中也需要明确说明connection的值为close。
HTTP Connection的 close设置允许客户端或服务器中任何一方关闭底层的连接时双方都会要求在处理请求后关闭它们的TCP连接。即无论request还是response的header中包含了值为close的connection,都表明当前正在使用的tcp链接在请求处理完毕后会被断掉,以后client再进行新的请求时就必须创建新的tcp链接了。(程序中可以在过滤器中加入:response.setHeader("connection", "close");)
浏览器在请求头部添加 Connection:keep-alive,以此告知服务器自己支持长连接方式,而倘若服务器也支持,那么就在响应头部添加 Connection:keep-alive,从而告诉浏览器长连接。服务器还可以通过 Keep-Alive:timeout=10, max=100 的头部告诉浏览器“10 秒算超时时间,最长不能超过 100 秒”。 客户端和服务端都可以设置timeout和max属性值,但http连接保持时间是由服务端的消息头connection字段和keep-alive字段定的!
//org.apache.http.impl.execchain.MainClientExec#execute
......
//从连接池中lease connection
final HttpClientConnectionmanagedConn = connRequest.get(timeout > 0 ? timeout : 0, TimeUnit.MILLISECONDS);
......
//将conenction封装在ConnectionHolder中
final ConnectionHolder connHolder = new ConnectionHolder(this.log, this.connManager, managedConn);
......
// The connection is in or can be brought to a re-usable state.
//如果返回值消息头中connection设置为close,则返回false
if (reuseStrategy.keepAlive(response, context)) {
// Set the idle duration of this connection
//取出response消息头中,keep-alive的timeout值
final long duration = keepAliveStrategy.getKeepAliveDuration(response, context);
if (this.log.isDebugEnabled()) {
final String s;
if (duration > 0) {
s = "for " + duration + " " + TimeUnit.MILLISECONDS;
} else {
s = "indefinitely";
}
this.log.debug("Connection can be kept alive " + s);
}
//设置失效时间
connHolder.setValidFor(duration, TimeUnit.MILLISECONDS);
connHolder.markReusable();
} else {
connHolder.markNonReusable();
}
TCP KeepAlive与 Http keep-alive 区别
http keep-alive 意图在于连接复用。是为了让TCP存活更久以便复用TCP连接,在一个TCP连接上进行多次的HTTP请求从而提高性能。
tcp KeepAlive是TCP的一种检测TCP连接状况的保鲜机制, 意图在于保活、心跳,检测连接错误。
TCP一般而言为服务器端提供保活功能: 如果客户端已经消失,使得服务器上保留一个半开放的连接,而服务器又在等待来自客户端的数据,则服务器将永远等待客户端的数据, 保活功能就是试图在服务器端检测到这种半开放的连接。
保活心跳机制
及时有效地检测到一方的非正常断开,保证连接的资源被有效利用。
- 自实现
大致方法是:服务器在一个 Timer事件中定时向客户端发送一个短小精悍的数据包,然后启动一个低级别的线程,在该线程中不断检测客户端的回应, 如果在一定时间内没有收到客户端的回应,即认为客户端已经掉线;同样,如果客户端在一定时间内没有收到服务器的心跳包,则认为连接不可用。 - Keep-Alive
keepalive只能检测连接是否存活,不能检测连接是否可用。eg. 服务器因为负载过高导致无法响应请求但是连接仍然存在,此时keepalive无法判断连接是否可用。
- HTTP保活:
Httpd守护进程,一般都提供了keep-alive timeout时间设置参数。eg: nginx的keepalive_timeout,和Apache的KeepAliveTimeout。
一个http产生的tcp连接在传送完最后一个响应后,还需要hold住 keepalive_timeout秒后,如果守护进程在这个等待的时间里,一直没有收到浏览器发过来http请求,则关闭这个http连接。
Tomcat中的相关设置,在conf/server.xml 中的Connector 元素中:
keepAliveTimeout:
The number of milliseconds this Connector will wait for another HTTP request before closing the connection. The default value is to use the value that has been set for the connectionTimeout attribute.
maxKeepAliveRequests:
The maximum number of HTTP requests which can be pipelined until the connection is closed by the server. Setting this attribute to 1 will disable HTTP/1.0 keep-alive, as well as HTTP/1.1 keep-alive and pipelining. Setting this to -1 will allow an unlimited amount of pipelined or keep-alive HTTP requests. If not specified, this attribute is set to 100.
- TCP保活:
没有一个全局的选项去开启TCP的KeepAlive,可在TCP的socket中通过setsockopt系统调用针对单独的socket进行设置开启(使用时需要#include <netinet/tcp.h>, 否则SOL_TCP和TCP_KEEPIDLE等3个宏找不到)。
int keepAlive = 1; // 开启keepalive属性. 缺省值: 0(关闭)
int keepIdle = 60; // 如果在60秒内没有任何数据交互,则进行探测. 缺省值:7200(s)
int keepInterval = 5; // 探测时发探测包的时间间隔为5秒. 缺省值:75(s)
int keepCount = 2; // 探测重试的次数. 全部超时则认定连接失效..缺省值:9(次)
setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (void*)&keepAlive, sizeof(keepAlive));
setsockopt(s, SOL_TCP, TCP_KEEPIDLE, (void*)&keepIdle, sizeof(keepIdle));
setsockopt(s, SOL_TCP, TCP_KEEPINTVL, (void*)&keepInterval, sizeof(keepInterval));
setsockopt(s, SOL_TCP, TCP_KEEPCNT, (void*)&keepCount, sizeof(keepCount));
// TCP_KEEPCNT : 覆盖 tcp_keepalive_probes ;
// TCP_KEEPIDLE : 覆盖tcp_keepalive_time ;
// TCP_KEEPINTVL : 覆盖 tcp_keepalive_intvl int keepAlive = 1;
- 在windows系统中可以通过修改注册表等来达到调整:HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters
- KeepAliveTime: 默认7,200,000毫秒(两个小时)。如果这段时间内没有活动,则会发送保持活动信号。默认情况下不发送保活数据包。
如果需要对丢失接收方敏感,更快地发现丢失了接收方,需要考虑减小这个值。如果长期不活动的空闲连接出现次数较多,而丢失接收方的情况出现较少,可能会要提高该值以减少开销。缺省情况下,如果空闲连接 7200000 毫秒(2 小时)内没有活动,Windows 就发送保持活动的消息。通常,1800000 毫秒是首选值,从而一半的已关闭连接会在 30 分钟内被检测到。 - KeepAliveInterval:默认1000毫秒(1 秒)。 确定在收到响应之前,保活重传之间的时间间隔。
一旦收到一个响应,将由 KeepAliveTime 值重新控制在下一次保活传输之前的延迟。如果经过 TcpMaxDataRetransmissions 指定的重新传输次数后仍无响应,将放弃连接。
缺省情况下,在未收到响应而重新发送保持活动的消息之前,Windows 会等待 1000 毫秒(1 秒)。如果期望较长的响应时间,需要提高该值以减少开销。如果需要减少花在验证接收方是否已丢失上的时间,请考虑减小该值或 TcpMaxDataRetransmissions
- tcp_keepalive_time(开启keepalive的闲置时长)tcp_keepalive_intvl(keepalive探测包的发送间隔)和 tcp_keepalive_probes (如果不予应答,探测包的发送次数)。
修改配置文件, 对整个系统所有的socket有效。(在系统重启后三个参数的值又会恢复到默认值)
#cat /proc/sys/net/ipv4/tcp_keepalive_time 7200
#cat /proc/sys/net/ipv4/tcp_keepalive_intvl 75
#cat /proc/sys/net/ipv4/tcp_keepalive_probes 9
#echo 60 > /proc/sys/net/ipv4/tcp_keepalive_time
#echo 5 > /proc/sys/net/ipv4/tcp_keepalive_intvl
#echo 3 > /proc/sys/net/ipv4/tcp_keepalive_probes
- 全局设置可更改/etc/sysctl.conf, 使用 sudo sysctl -p立即生效;(永久有效)
net.ipv4.tcp_keepalive_intvl = 20
net.ipv4.tcp_keepalive_probes = 3
net.ipv4.tcp_keepalive_time = 60
HTTP如何区分多个请求
Content-Length
表示实体内容的长度。浏览器通过这个字段来判断当前请求的数据是否已经全部接收。
所以,当浏览器请求的是一个静态资源时,即服务器能明确知道返回内容的长度时,可以设置Content-Length
来控制请求的结束。但当服务器并不知道请求结果的长度时,如一个动态的页面或者数据,Content-Length
就无法解决上面的问题,这个时候就需要用到Transfer-Encoding
字段。
Transfer-Encoding
表示传输编码。
还有一个类似的字段叫做:Content-Encoding
。区别是Content-Encoding
用于对实体内容的压缩编码(Content-Encoding: gzip
); Transfer-Encoding
则改变了报文的格式。
当服务端无法知道实体内容的长度时,可指定Transfer-Encoding:chunked
(还可同时指定Transfer-Encoding: gzip
),
表明实体内容数据不仅是gzip压缩的,还是分块传递的。最终当浏览器接收到一个长度为0的chunked时, 标识当前请求内容已全部接收。
chunked
分块编码, 标识将数据分成一块一块的发出。Chunked编码将使用若干个Chunk串连而成,由一个标明长度为0 的chunk标示结束。
chunk-size指定十六进制的数字代表后面chunk-data的字节长度,如果是“0”,则表示chunk-size为0,该chunk为last-chunk,无chunk-data部分。
Chunked-Body = *chunk //0至多个chunk
last-chunk //最后一个chunk
trailer //尾部
CRLF //结束标记符
chunk = chunk-size [ chunk-extension ] CRLF chunk-data CRLF // 单个chunk内容
chunk-size = 1*HEX
last-chunk = 1*("0") [ chunk-extension ] CRLF
chunk-extension= *( ";" chunk-ext-name [ "=" chunk-ext-val ] )
chunk-ext-name = token
chunk-ext-val = token | quoted-string
chunk-data = chunk-size(OCTET)
trailer = *(entity-header CRLF)
总结
1、Content-Length如果存在并且有效的话,则必须和消息内容的传输长度完全一致。(如果过短则会截断,过长则会导致超时)
2、如果存在Transfer-Encoding(重点是chunked),则在header中的Content-Length会被忽视。
3、如果采用短连接,则直接可以通过服务器关闭连接来确定消息的传输长度。