完全掌握网络通讯(七)ConnectionReuseStrategy,ConnectionKeepAliveStrategy解析

 

网络通讯系列文章序java

完全掌握网络通讯(一)Http协议基础知识
完全掌握网络通讯(二)Apache的HttpClient基础知识
完全掌握网络通讯(三)Android源码中HttpClient的在不一样版本的使用
完全掌握网络通讯(四)Android源码中HttpClient的发送框架解析
完全掌握网络通讯(五)DefaultRequestDirector解析
完全掌握网络通讯(六)HttpRequestRetryHandler解析
完全掌握网络通讯(七)ConnectionReuseStrategy,ConnectionKeepAliveStrategy解析
完全掌握网络通讯(八)AsyncHttpClient源码解读
完全掌握网络通讯(九)AsyncHttpClient为何没法用Fiddler来抓包
完全掌握网络通讯(十)AsyncHttpClient如何发送JSON解析JSON,以及一些其余用法编程

前面简单说了下HttpRequestRetryHandler,这篇主要分析下ConnectionReuseStrategy,ConnectionKeepAliveStrategy,链接的重用和长链接浏览器

1:基础介绍
1.1)Keep-Alive解析
http协议做为上层应用层协议,其是基于TCP/IP协议,UDP协议传输层上进行的;http协议经过socket这个套接字完成客户端和服务端的通讯;
http协议目前主要有两个版本HTTP 1.0和HTTP 1.1,他们都是无状态协议服务器

在HTTP 1.0中,每一次请求响应以后,下一次的请求须要断开以前的链接,再从新开始;网络

在HTTP 1.1中,使用keep-alive在一次TCP链接中能够持续发送多份数据而不会断开链接。经过使用keep-alive机制,能够减小tcp链接创建次数,也意味着能够减小TIME_WAIT状态链接,以此提升性能和提升httpd服务器的吞吐率(更少的tcp链接意味着更少的系统内核调用,socket的accept()和close()调用)。框架

所以便出现了Connection: keep-alive的设置,用于创建长链接,即咱们所说的Keep-Alive模式;less

 

如上图,左图是HTTP 1.0 ; 右图是HTTP 1.1socket

http 1.0中默认是关闭的,须要在http头加入”Connection: Keep-Alive”,才能启用Keep-Alive;
在1.0中,若是客户端浏览器支持Keep-Alive,那么就在HTTP请求头中添加一个字段 Connection: Keep-Alive,当服务器收到附带有Connection: Keep-Alive的请求时,它也会在响应头中添加一个一样的字段来使用Keep-Alive。这样一来,客户端和服务器之间的HTTP链接就会被保持,不会断开(超过Keep-Alive规定的时间,意外断电等状况除外),当客户端发送另一个请求时,就使用这条已经创建的链接tcp

http 1.1中默认启用Keep-Alive,若是加入”Connection: close “,才关闭。
Keep-Alive不会永久保持链接,它有一个保持时间,能够在不一样的服务器软件(如Apache)中设定这个时间;
一次完成的http请求是否可以保持,同时也要靠服务端是否具有Keep-Alive能力;oop

1.2)如何判断客户端已经完整的接收到服务端的数据,针对HTTP 1.0和HTTP 1.1
在java中,使用socket编程的时候,咱们常常看到以下代码

Socket socket = new Socket("localhost",10086);
InputStream is = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String info = null;
while((info=br.readLine()) != -1){

}
1
2
3
4
5
6
7
因此在普通的socket编程中,咱们可使用EOF(-1)来判断是否完整的接收到服务端的返回的数据;这是由于在一次普通的http请求中,即没有添加Connection: Keep-Alive属性的http请求中,服务端响应以后,会断开链接,顾使用EOF判断是准确的;

可是这种方式针对添加Connection: Keep-Alive属性的http请求来讲,就没法生效了;
咱们能够经过头消息中的Conent-Length字段来判断;可是对于动态页面或者zip格式的内容,服务端通常会采用Transfer-Encoding: chunked”这样的方式来代替Content-Length;

所以
在Http 1.0及以前版本中,content-length字段无关紧要。
在http1.1及以后版本。若是是keep alive,则content-length和chunk必然是二选一。如果非keep alive,则和http1.0同样。content-length无关紧要。

2:在DefaultRequestDirector.execute方法中有以下代码

// The connection is in or can be brought to a re-usable state.
reuse = reuseStrategy.keepAlive(response, context);
if(reuse) {
// Set the idle duration of this connection
long duration = keepAliveStrategy.getKeepAliveDuration(response, context);
managedConn.setIdleDuration(duration, TimeUnit.MILLISECONDS);
}
1
2
3
4
5
6
7
从这段代码能够看出,当完成一次http请求以后,并无当即关闭这个tcp链接和释放资源;而是经过可重用策略来判断这个链接可否被保持,以及保持多长时间;

咱们先分析下如何判断这个链接是不是可重用的,咱们能够经过上面的keepAlive方法来进行具体分析,reuseStrategy的默认实现者为DefaultConnectionReuseStrategy,咱们看一下keepAlive方法

//该方法的做用就是在一次请求以后,这个链接可以被保持
//若是返回false,则调用者应该当即关闭链接
//若是返回true,则调用者应该保持这个链接从而能够应用于其余请求
public boolean keepAlive(final HttpResponse response,
final HttpContext context) {
if (response == null) {
throw new IllegalArgumentException
("HTTP response may not be null.");
}
if (context == null) {
throw new IllegalArgumentException
("HTTP context may not be null.");
}

HttpConnection conn = (HttpConnection)
context.getAttribute(ExecutionContext.HTTP_CONNECTION);

//当一个链接没有创建的时候,固然是不可重用的,返回false
if (conn != null && !conn.isOpen())
return false;
// do NOT check for stale connection, that is an expensive operation

// Check for a self-terminating entity. If the end of the entity will
// be indicated by closing the connection, there is no keep-alive.
HttpEntity entity = response.getEntity();
ProtocolVersion ver = response.getStatusLine().getProtocolVersion();
//当返回的entity的长度小于0,而且http协议版本号小于1.0,返回false
if (entity != null) {
if (entity.getContentLength() < 0) {
if (!entity.isChunked() ||
ver.lessEquals(HttpVersion.HTTP_1_0)) {
// if the content length is not known and is not chunk
// encoded, the connection cannot be reused
return false;
}
}
}

// Check for the "Connection" header. If that is absent, check for
// the "Proxy-Connection" header. The latter is an unspecified and
// broken but unfortunately common extension of HTTP.
//HTTP.CONN_DIRECTIVE的值为Connection,即获取响应头信息中的Connection字段的内容
HeaderIterator hit = response.headerIterator(HTTP.CONN_DIRECTIVE);
//若是没有这个头信息,则寻找Proxy-Connection的头信息
if (!hit.hasNext()){
//Proxy-Connection是http1.0 时代的产物。老旧的代理,若是设置 connection: keepalive,
//代理原样转发给服务器,服务器会觉得要创建长久链接,可是代理并不支持,这样就出问题了。
//因此改成设置 proxy-connection: keepalive,若是是新的代理,
//支持 keepalive,它会认得这个头,并改为 connection: keepalive 转发给服务器,
//顺利创建持久链接;若是是老的代理,它不认识,会原样转发,这时候服务器也不会创建持久链接
hit = response.headerIterator("Proxy-Connection");
}

if (hit.hasNext()) {
try {
TokenIterator ti = createTokenIterator(hit);
boolean keepalive = false;
while (ti.hasNext()) {
final String token = ti.nextToken();
//若是Connection:close则返回false
if (HTTP.CONN_CLOSE.equalsIgnoreCase(token)) {
return false;
}//若是Connection:Keep-Alive则返回true
else if (HTTP.CONN_KEEP_ALIVE.equalsIgnoreCase(token)) {
// continue the loop, there may be a "close" afterwards
keepalive = true;
}
}
if (keepalive)
return true;
// neither "close" nor "keep-alive", use default policy

} catch (ParseException px) {
// invalid connection header means no persistent connection
// we don't have logging in HttpCore, so the exception is lost
return false;
}
}

// default since HTTP/1.1 is persistent, before it was non-persistent
return !ver.lessEquals(HttpVersion.HTTP_1_0);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
总结
1:该方法的做用就是在一次请求以后,这个链接可否被保持,若是返回false,则调用者应该当即关闭链接;若是返回true,则调用者应该保持这个链接从而能够应用于其余请求
2:因为HTTP 1.0时代,还不能有效支持connection,顾多了一个Proxy-Connection头信息,该字段的出现的缘由是: 在HTTP 1.0中老旧的代理,若是设置 connection: keepalive,代理原样转发给服务器,服务器会觉得要创建长久链接,可是代理并不支持,这样就出问题了。因此改成设置 proxy-connection: keepalive,若是是新的代理,支持 keepalive,它会认得这个头,并改为 connection: keepalive 转发给服务器,顺利创建持久链接;若是是老的代理,它不认识,会原样转发,这时候服务器也不会创建持久链接

当一个请求视为可重用以后,即keepAlive返回true,那这么连接能一直保持连接状态?会不会超过必定时间,链接就断开?
答案是会的,当连接超过预设时间,会自动断开;

当一个连接是可重用的,咱们就能够经过以下代码得到这个连接能够存活的时间

keepAliveStrategy.getKeepAliveDuration
1
keepAliveStrategy的实现为DefaultConnectionKeepAliveStrategy

public class DefaultConnectionKeepAliveStrategy implements ConnectionKeepAliveStrategy {

public long getKeepAliveDuration(HttpResponse response, HttpContext context) {
if (response == null) {
throw new IllegalArgumentException("HTTP response may not be null");
}
HeaderElementIterator it = new BasicHeaderElementIterator(
//HTTP.CONN_KEEP_ALIVE的为“Keep-Alive”
response.headerIterator(HTTP.CONN_KEEP_ALIVE));
while (it.hasNext()) {
HeaderElement he = it.nextElement();
String param = he.getName();
String value = he.getValue();
if (value != null && param.equalsIgnoreCase("timeout")) {
try {
return Long.parseLong(value) * 1000;
} catch(NumberFormatException ignore) {
}
}
}
return -1;
}

}代码很简单,就是经过Keep-Alive头信息中,得到timeout的值,做为超时时间;单位毫秒;

相关文章
相关标签/搜索