什么是HTTP长连接? HTTP长连接,与一般每次发起http请求或响应都要建立一个tcp连接不同,http长连接利用同一个tcp连接处理多个http请求和响应,也叫 HTTP keep-alive,或者http连接重用。使用http长连接可以提高http请求/响应的性能。
使用http长连接有很多好处,包括:
更少的建立和关闭tcp连接,可以减少网络流量。 因为已建立的tcp握手,减少后续请求的延时。 长时间的连接让tcp有充足的时间判断网络的拥塞情况,方便做出下步操作。
这些优点在使用https连接时更显著。可以减少多次建立高消耗的SSL/TLS握手。 在HTTP/1.1中,默认使用的是长连接方式。客户端默认服务端会保持长连接,即便返回错误响应;除非明确指示不使用长连接。同时,协议中也指定了客户 端可以发送关闭信号到服务端来关闭TCP连接。
怎样是连接可以重用? 因为TCP是基于流的协议,所以HTTP协议需要有一种方式来指示前一个响应的结束和后一个响应的开始来重用已建立的连接。所以,它要求连接中传输的信息 必须有自定义的消息长度。自定义消息长度可以通过设置 Content-Length 消息头,若传输编码的实体内容块,则每个数据块的标明数据块的大小,而且响应体也是以一个特殊的数据块结束。
若中间存在代理服务器将会如何? 因为长连接仅占用一条传输链路,所以代理服务器能否正确得与客户端和服务器端(或者其他代理服务器)发送长连接或非长连接的信号尤为重要。但是HTTP的 客户端或服务器端来看,代理服务器对他们来说是透明的,即便长连接是需要关注的。
当前的JDK如何处理Keep-Alive? JDK同时支持HTTP/1.1 和 HTTP/1.0。 当应用程序读取完响应体内容后或者调用 close() 关闭了URLConnection.getInputStream()返回的流,JDK中的HTTP协议句柄将关闭连接,并将连接放到连接缓存中,以便后 面的HTTP请求使用。 对HTTP keep-Alive 的支持是透明的。但是,你也可以通过系统属性http.keepAlive和http.maxConnections以及HTTP/1.1协议中的特定的 请求响应头来控制。控制Keep-Alive表现的系统属性有:
http.keepAlive=<布尔值> 默认: true 指定长连接是否支持
http.maxConnections=<整数> 默认: 5 指定对同一个服务器保持的长连接的最大个数。
影响长连接的HTTP header是: Connection: close 如果请求或响应中的Connection header被指定为close,表示在当前请求或响应完成后将关闭TCP连接。
JDK中的当前实现不支持缓存响应体,所以应用程序必须读取完响应体内容或者调用close()关闭流并丢弃未读内容来重用连接。此外,当前实现在清理连接时并未使用阻塞读,这就意味这如果响应体不可用,连接将不能被重用。
JDK1.5中的新特性 当应用接收到400或500的HTTP响应时,它将忽略IOException 而另发一个HTTP 请求。这种情况下,底层的TCP连接将不会再保持,因为响应内容还在等待被读取,socket 连接未清理,不能被重用。应用可以在捕获IOException 以后调用HttpURLConnection.getErrorStream() ,读取响应内容然后关闭流。但是现存的应用没有这么做,不能体现出长连接的优势。为了解决这个问题,介绍下workaround。
当响应体的状态码大于或等于400的时候,workaround 将在一定时间内缓存一定数量的响应内容,释放底层的socket连接来重用。基本原理是当响应状态码大于或等于400时,服务器端会发送一个简短的响应体来指明连接谁以及如何恢复连接。
下面介绍一些SUN实现中的特定属性来帮助接收到错误响应体后清理连接: 主要的一个是: sun.net.http.errorstream.enableBuffering=<布尔值> 默认: false
当上面属性设置为true后,在接收到响应码大于或等于400是,HTTP 句柄将尝试缓存响应内容。释放底层的socket连接来重用。所以,即便应用不调用getErrorStream()来读取响应内容,或者调用 close()关闭流,底层的socket连接也将保持连接状态。
下面的两个系统属性是为了更进一步控制错误流的缓存行为: sun.net.http.errorstream.timeout=<int> in 毫秒 默认: 300 毫秒
你如何做可以保持连接为连接状态呢? 不要忽略响应体而丢弃连接。这样会是TCP连接闲置,当不再被引用后将会被垃圾回收器回收。 如果getInputStream()返回成功,读取全部响应内容。如果抛出IOException ,捕获异常并调用getErrorStream() 读取响应内容(如果存在响应内容)。
即便你对响应内容不感兴趣,也要读取它,以便清理连接。但是,如果响应内容很长,你读取到开始部分后就不感兴趣了,可以调用close()来关闭流。值得注意的是,其他部分的数据已在读取中,所以连接将不能被清理进而被重用。
下面是一个基于上面建议的代码样例:
try {
URL a = new URL(args[0]);
URLConnection urlc = a.openConnection();
is = conn.getInputStream();
int ret = 0;
while ((ret = is.read(buf)) > 0) {
processBuf(buf);
}
// close the inputstream
is.close();
} catch (IOException e) {
try {
respCode = ((HttpURLConnection)conn).getResponseCode();
es = ((HttpURLConnection)conn).getErrorStream();
int ret = 0;
// read the response body
while ((ret = es.read(buf)) > 0) {
processBuf(buf);
}
// close the errorstream
es.close();
} catch (IOException ex) {
// deal with the exception
}
}
|
如果你预先就对响应内容不感兴趣,你可以使用HEAD 请求来代替GET 请求。例如,获取web资源的meta信息或者测试它的有效性,可访问性以及最近的修改。下面是代码片段:
URL a =
new
URL(args[0]);
URLConnection urlc = a.openConnection();
HttpURLConnection httpc = (HttpURLConnection)urlc;
// only interested in the length of the resource
httpc.setRequestMethod(
"HEAD"
);
int len = httpc.getContentLength();