網絡通訊系列文章序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的值,做爲超時時間;單位毫秒;