HttpClient 教程 (二)

第二章 連接管理

HttpClient有一個對連接初始化和終止,還有在活動連接上I/O操作的完整控制。而連接操作的很多方面可以使用一些參數來控制。

2.1 連接參數

這些參數可以影響連接操作:

  • 'http.socket.timeout':定義了套接字的毫秒級超時時間(SO_TIMEOUT),這就是等待數據,換句話說,在兩個連續的數據包之間最大的閒置時間。如果超時時間是0就解釋爲是一個無限大的超時時間。這個參數期望得到一個java.lang.Integer類型的值。如果這個參數沒有被設置,那麼讀取操作就不會超時(無限大的超時時間)。
  • 'http.tcp.nodelay':決定了是否使用Nagle算法。Nagle算法視圖通過最小化發送的分組數量來節省帶寬。當應用程序希望降低網絡延遲並提高性能時,它們可以關閉Nagle算法(也就是開啓TCP_NODELAY)。數據將會更早發送,增加了帶寬消耗的成文。這個參數期望得到一個java.lang.Boolean類型的值。如果這個參數沒有被設置,那麼TCP_NODELAY就會開啓(無延遲)。
  • 'http.socket.buffer-size':決定了內部套接字緩衝使用的大小,來緩衝數據同時接收/傳輸HTTP報文。這個參數期望得到一個java.lang.Integer類型的值。如果這個參數沒有被設置,那麼HttpClient將會分配8192字節的套接字緩存。
  • 'http.socket.linger':使用指定的秒數拖延時間來設置SO_LINGER。最大的連接超時值是平臺指定的。值0暗示了這個選項是關閉的。值-1暗示了使用了JRE默認的。這個設置僅僅影響套接字關閉操作。如果這個參數沒有被設置,那麼就假設值爲-1(JRE默認)。
  • 'http.connection.timeout':決定了直到連接建立時的毫秒級超時時間。超時時間的值爲0解釋爲一個無限大的時間。這個參數期望得到一個java.lang.Integer類型的值。如果這個參數沒有被設置,連接操作將不會超時(無限大的超時時間)。
  • 'http.connection.stalecheck':決定了是否使用舊的連接檢查。當在一個連接之上執行一個請求而服務器端的連接已經關閉時,關閉舊的連接檢查可能導致在獲得一個I/O錯誤風險時顯著的性能提升(對於每一個請求,檢查時間可以達到30毫秒)。這個參數期望得到一個java.lang.Boolean類型的值。出於性能的關鍵操作,檢查應該被關閉。如果這個參數沒有被設置,那麼舊的連接將會在每個請求執行之前執行。
  • 'http.connection.max-line-length':決定了最大請求行長度的限制。如果設置爲一個正數,任何HTTP請求行超過這個限制將會引發java.io.IOException異常。負數或零將會關閉這個檢查。這個參數期望得到一個java.lang.Integer類型的值。如果這個參數沒有被設置,那麼就不強制進行限制了。
  • 'http.connection.max-header-count':決定了允許的最大HTTP頭部信息數量。如果設置爲一個正數,從數據流中獲得的HTTP頭部信息數量超過這個限制就會引發java.io.IOException異常。負數或零將會關閉這個檢查。這個參數期望得到一個java.lang.Integer類型的值。如果這個參數沒有被設置,那麼就不
  • 強制進行限制了。
  • 'http.connection.max-status-line-garbage':決定了在期望得到HTTP響應狀態行之前可忽略請求行的最大數量。使用HTTP/1.1持久性連接,這個問題產生的破碎的腳本將會返回一個錯誤的Content-Length(有比指定的字節更多的發送)。不幸的是,在某些情況下,這個不能在錯誤響應後來偵測,只能在下一次之前。所以HttpClient必須以這種方式跳過那些多餘的行。這個參數期望得到一個java.lang.Integer類型的值。0是不允許在狀態行之前的所有垃圾/空行。使用java.lang.Integer#MAX_VALUE來設置不限制的數字。如果這個參數沒有被設置那就假設是不限制的。

2.2 持久連接

從一個主機向另外一個建立連接的過程是相當複雜的,而且包含了兩個終端之間的很多包的交換,它是相當費時的。連接握手的開銷是很重要的,特別是對小量的HTTP報文。如果打開的連接可以被重用來執行多次請求,那麼就可以達到很高的數據吞吐量。

HTTP/1.1強調HTTP連接默認情況可以被重用於多次請求。HTTP/1.0兼容的終端也可以使用相似的機制來明確地交流它們的偏好來保證連接處於活動狀態,也使用它來處理多個請求。HTTP代理也可以保持空閒連接處於一段時間的活動狀態,防止對相同目標主機的一個連接也許對隨後的請求需要。保持連接活動的能力通常被稱作持久性連接。HttpClient完全支持持久性連接。

2.3 HTTP連接路由

HttpClient能夠直接或通過路由建立連接到目標主機,這會涉及多箇中間連接,也被稱爲跳。HttpClient區分路由和普通連接,通道和分層。通道連接到目標主機的多箇中間代理的使用也稱作是代理鏈。

普通路由由連接到目標或僅第一次的代理來創建。通道路由通過代理鏈到目標連接到第一通道來建立。沒有代理的路由不是通道的,分層路由通過已存在連接的分層協議來建立。協議僅僅可以在到目標的通道上或在沒有代理的直接連接上分層。

2.3.1 路由計算

RouteInfo接口代表關於最終涉及一個或多箇中間步驟或跳的目標主機路由的信息。HttpRoute是RouteInfo的具體實現,這是不能改變的(是不變的)。HttpTracker是可變的RouteInfo實現,由HttpClient在內部使用來跟蹤到最大路由目標的剩餘跳數。HttpTracker可以在成功執行向路由目標的下一跳之後更新。HttpRouteDirector是一個幫助類,可以用來計算路由中的下一跳。這個類由HttpClient在內部使用。

HttpRoutePlanner是一個代表計算到基於執行上下文到給定目標完整路由策略的接口。HttpClient附帶兩個默認的HttpRoutePlanner實現。ProxySelectorRoutePlanner是基於java.net.ProxySelector的。默認情況下,它會從系統屬性中或從運行應用程序的瀏覽器中選取JVM的代理設置。DefaultHttpRoutePlanner實現既不使用任何Java系統屬性,也不使用系統或瀏覽器的代理設置。它只基於HTTP如下面描述的參數計算路由。

2.3.2 安全HTTP連接

如果信息在兩個不能由非認證的第三方進行讀取或修改的終端之間傳輸,HTTP連接可以被認爲是安全的。SSL/TLS協議是用來保證HTTP傳輸安全使用最廣泛的技術。而其它加密技術也可以被使用。通常來說,HTTP傳輸是在SSL/TLS加密連接之上分層的。

2.4 HTTP路由參數

這些參數可以影響路由計算:
  • 'http.route.default-proxy':定義可以被不使用JRE設置的默認路由規劃者使用的代理主機。這個參數期望得到一個HttpHost類型的值。如果這個參數沒有被設置,那麼就會嘗試直接連接到目標。
  • 'http.route.local-address':定義一個本地地址由所有默認路由規劃者來使用。有多個網絡接口的機器中,這個參數可以被用於從連接源中選擇網絡接口。這個參數期望得到一個java.net.InetAddress類型的值。如果這個參數沒有被設置,將會自動使用本地地址。
  • 'http.route.forced-route':定義一個由所有默認路由規劃者使用的強制路由。代替了計算路由,給定的強制路由將會被返回,儘管它指向一個完全不同的目標主機。這個參數期望得到一個HttpRoute類型的值。如果這個參數沒有被設置,那麼就使用默認的規則建立連接到目標服務器。

2.5 套接字工廠

LayeredSocketFactory是SocketFactory接口的擴展。分層的套接字工廠可HTTP連接內部使用java.net.Socket對象來處理數據在線路上的傳輸。它們依賴SocketFactory接口來創建,初始化和連接套接字。這會使得HttpClient的用戶可以提供在運行時指定套接字初始化代碼的應用程序。PlainSocketFactory是創建和初始化普通的(不加密的)套接字的默認工廠。

創建套接字的過程和連接到主機的過程是不成對的,所以套接字在連接操作封鎖時可以被關閉。

PlainSocketFactory sf = PlainSocketFactory.getSocketFactory();
Socket socket = sf.createSocket();
HttpParams params = new BasicHttpParams();
params.setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, 1000L);
sf.connectSocket(socket, "locahost", 8080, null, -1, params);

2.5.1 安全套接字分層

LayeredSocketFactory是SocketFactory接口的擴展。分層的套接字工廠可以創建在已經存在的普通套接字之上的分層套接字。套接字分層主要通過代理來創建安全的套接字。HttpClient附帶實現了SSL/TLS分層的SSLSocketFactory。請注意HttpClient不使用任何自定義加密功能。它完全依賴於標準的Java密碼學(JCE)和安全套接字(JSEE)擴展。

2.5.2 SSL/TLS的定製

HttpClient使用SSLSocketFactory來創建SSL連接。SSLSocketFactory允許高度定製。它可以使用javax.net.ssl.SSLContext的實例作爲參數,並使用它來創建定製SSL連接。

TrustManager easyTrustManager = new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain,
String authType) throws CertificateException {
// 哦,這很簡單!
}
@Override
public void checkServerTrusted(X509Certificate[] chain,
String authType) throws CertificateException {
//哦,這很簡單!
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
};
SSLContext sslcontext = SSLContext.getInstance("TLS");
sslcontext.init(null, new TrustManager[] { easyTrustManager }, null);
SSLSocketFactory sf = new SSLSocketFactory(sslcontext);
SSLSocket socket = (SSLSocket) sf.createSocket();
socket.setEnabledCipherSuites(new String[] { "SSL_RSA_WITH_RC4_128_MD5" });
HttpParams params = new BasicHttpParams();
params.setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, 1000L);
sf.connectSocket(socket, "locahost", 443, null, -1, params);
SSLSocketFactory的定製暗示出一定程度SSL/TLS協議概念的熟悉,這個詳細的解釋超出了本文檔的範圍。請參考Java的安全套接字擴展[http://java.sun.com/j2se/1.5.0/docs/guide/
security/jsse/JSSERefGuide.html],這是javax.net.ssl.SSLContext和相關工具的詳細描述。

2.5.3 主機名驗證

除了信任驗證和客戶端認證在SSL/TLS協議級上進行,一旦連接建立之後,HttpClient能可選地驗證目標主機名匹配存儲在服務器的X.509認證中的名字。這個認證可以提供額外的服務器信任材料的真實保證。X509主機名驗證接口代表了主機名驗證的策略。HttpClient附帶了3個X509主機名驗證器。很重要的一點是:主機名驗證不應該混淆SSL信任驗證。
  • StrictHostnameVerifier:嚴格的主機名驗證在Sun Java 1.4,Sun Java 5和Sun Java 6中是相同的。而且也非常接近IE6。這個實現似乎是兼容RFC 2818處理通配符的。主機名必須匹配第一個CN或任意的subject-alt。在CN和其它任意的subject-alt中可能會出現通配符。
  • BrowserCompatHostnameVerifier:主機名驗證器和Curl和Firefox的工作方式是相同的。主機名必須匹配第一個CN或任意的subject-alt。在CN和其它任意的subject-alt中可能會出現通配符。BrowserCompatHostnameVerifier和StrictHostnameVerifier的唯一不同是使用BrowserCompatHostnameVerifier匹配所有子域的通配符(比如”*.foo.com”),包括”a.b.foo.com”。
  • AllowAllHostnameVerifier:這個主機名驗證器基本上是關閉主機名驗證的。這個實現是一個空操作,而且不會拋出javax.net.ssl.SSLException異常。

每一個默認的HttpClient使用BrowserCompatHostnameVerifier的實現。如果需要的話,它可以指定不同的主機名驗證器實現。

SSLSocketFactory sf = new SSLSocketFactory(SSLContext.getInstance("TLS"));
sf.setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);

2.6 協議模式

Scheme類代表了一個協議模式,比如“http”或“https”同時包含一些協議屬性,比如默認端口,用來爲給定協議創建java.net.Socket實例的套接字工廠。SchemeRegistry類用來維持一組Scheme,當去通過請求URI建立連接時,HttpClient可以從中選擇:

Scheme http = new Scheme("http", PlainSocketFactory.getSocketFactory(), 80);
SSLSocketFactory sf = new SSLSocketFactory(SSLContext.getInstance("TLS"));
sf.setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);
Scheme https = new Scheme("https", sf, 443);
SchemeRegistry sr = new SchemeRegistry();
sr.register(http);
sr.register(https);

2.7 HttpClient代理配置

儘管HttpClient瞭解複雜的路由模式和代理鏈,它僅支持簡單直接的或開箱的跳式代理連接。

告訴HttpClient通過代理去連接到目標主機的最簡單方式是通過設置默認的代理參數:

DefaultHttpClient httpclient = new DefaultHttpClient();
HttpHost proxy = new HttpHost("someproxy", 8080);
httpclient.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy);

也可以構建HttpClient使用標準的JRE代理選擇器來獲得代理信息:

DefaultHttpClient httpclient = new DefaultHttpClient();
ProxySelectorRoutePlanner routePlanner = new ProxySelectorRoutePlanner(
httpclient.getConnectionManager().getSchemeRegistry(),
ProxySelector.getDefault());
httpclient.setRoutePlanner(routePlanner);

另外一種選擇,可以提供一個定製的RoutePlanner實現來獲得HTTP路由計算處理上的複雜的控制:

DefaultHttpClient httpclient = new DefaultHttpClient();
httpclient.setRoutePlanner(new HttpRoutePlanner() {
public HttpRoute determineRoute(HttpHost target,
HttpRequest request,
HttpContext context) throws HttpException {
return new HttpRoute(target, null, new HttpHost("someproxy", 8080),
"https".equalsIgnoreCase(target.getSchemeName()));
}
});

2.8 HTTP連接管理器

2.8.1 連接操作器

連接操作是客戶端的低層套接字或可以通過外部實體,通常稱爲連接操作的被操作的狀態的連接。OperatedClientConnection接口擴展了HttpClientConnection接口而且定義了額外的控制連接套接字的方法。ClientConnectionOperator接口代表了創建實例和更新那些對象低層套接字的策略。實現類最有可能利用SocketFactory來創建java.net.Socket實例。ClientConnectionOperator接口可以讓HttpClient的用戶提供一個連接操作的定製策略和提供可選實現OperatedClientConnection接口的能力。

2.8.2 管理連接和連接管理器

HTTP連接是複雜的,有狀態的,線程不安全的對象需要正確的管理以便正確地執行功能。HTTP連接在同一時間僅僅只能由一個執行線程來使用。HttpClient採用一個特殊實體來管理訪問HTTP連接,這被稱爲HTTP連接管理器,代表了ClientConnectionManager接口。一個HTTP連接管理器的目的是作爲工廠服務於新的HTTP連接,管理持久連接和同步訪問持久連接來確保同一時間僅有一個線程可以訪問一個連接。

內部的HTTP連接管理器和OperatedClientConnection實例一起工作,但是它們爲服務消耗器ManagedClientConnection提供實例。ManagedClientConnection扮演連接之上管理狀態控制所有I/O操作的OperatedClientConnection實例的包裝器。它也抽象套接字操作,提供打開和更新去創建路由套接字便利的方法。ManagedClientConnection實例瞭解產生它們到連接管理器的鏈接,而且基於這個事實,當不再被使用時,它們必須返回到管理器。ManagedClientConnection類也實現了ConnectionReleaseTrigger接口,可以被用來觸發釋放連接返回給管理器。一旦釋放連接操作被觸發了,被包裝的連接從ManagedClientConnection包裝器中脫離,OperatedClientConnection實例被返回給管理器。儘管服務消耗器仍然持有ManagedClientConnection實例的引用,它也不再去執行任何I/O操作或有意無意地改變的OperatedClientConnection狀態。

這裏有一個從連接管理器中獲取連接的示例:

HttpParams params = new BasicHttpParams();
Scheme http = new Scheme("http", PlainSocketFactory.getSocketFactory(), 80);
SchemeRegistry sr = new SchemeRegistry();
sr.register(http);
ClientConnectionManager connMrg = new SingleClientConnManager(params, sr);
// 請求新連接。這可能是一個很長的過程。
ClientConnectionRequest connRequest = connMrg.requestConnection(
new HttpRoute(new HttpHost("localhost", 80)), null);
// 等待連接10秒
ManagedClientConnection conn = connRequest.getConnection(10, TimeUnit.SECONDS);
try {
// 用連接在做有用的事情。當完成時釋放連接。
conn.releaseConnection();
} catch (IOException ex) {
// 在I/O error之上終止連接。
conn.abortConnection();
throw ex;
}

如果需要,連接請求可以通過調用來ClientConnectionRequest#abortRequest()方法過早地中斷。這會解鎖在ClientConnectionRequest#getConnection()方法中被阻止的線程。

一旦響應內容被完全消耗後,BasicManagedEntity包裝器類可以用來保證自動釋放低層的連接。HttpClient內部使用這個機制來實現透明地對所有從HttpClient#execute()方法中獲得響應釋放連接:

ClientConnectionRequest connRequest = connMrg.requestConnection(
new HttpRoute(new HttpHost("localhost", 80)), null);
ManagedClientConnection conn = connRequest.getConnection(10, TimeUnit.SECONDS);
try {
BasicHttpRequest request = new BasicHttpRequest("GET", "/");
conn.sendRequestHeader(request);
HttpResponse response = conn.receiveResponseHeader();
conn.receiveResponseEntity(response);
HttpEntity entity = response.getEntity();
if (entity != null) {
BasicManagedEntity managedEntity = new BasicManagedEntity(entity, conn, true);
// 替換實體
response.setEntity(managedEntity);
}
// 使用響應對象做有用的事情。當響應內容被消耗後這個連接將會自動釋放。
} catch (IOException ex) {
//在I/O error之上終止連接。
conn.abortConnection();
throw ex;
}

2.8.3 簡單連接管理器

SingleClientConnManager是一個簡單的連接管理器,在同一時間它僅僅維護一個連接。儘管這個類是線程安全的,但它應該被用於一個執行線程。SingleClientConnManager對於同一路由的後續請求會盡量重用連接。而如果持久連接的路由不匹配連接請求的話,它也會關閉存在的連接之後對給定路由再打開一個新的。如果連接已經被分配,將會拋出java.lang.IllegalStateException異常。

對於每個默認連接,HttpClient使用SingleClientConnManager。

2.8.4 連接池管理器

ThreadSafeClientConnManager是一個複雜的實現來管理客戶端連接池,它也可以從多個執行線程中服務連接請求。對每個基本的路由,連接都是池管理的。對於路由的請求,管理器在池中有可用的持久性連接,將被從池中租賃連接服務,而不是創建一個新的連接。

ThreadSafeClientConnManager維護每個基本路由的最大連接限制。每個默認的實現對每個給定路由將會創建不超過兩個的併發連接,而總共也不會超過20個連接。對於很多真實的應用程序,這個限制也證明很大的制約,特別是他們在服務中使用HTTP作爲傳輸協議。連接限制,也可以使用HTTP參數來進行調整。

這個示例展示了連接池參數是如何來調整的:

HttpParams params = new BasicHttpParams();
// 增加最大連接到200
ConnManagerParams.setMaxTotalConnections(params, 200);
// 增加每個路由的默認最大連接到20
ConnPerRouteBean connPerRoute = new ConnPerRouteBean(20);
// 對localhost:80增加最大連接到50
HttpHost localhost = new HttpHost("locahost", 80);
connPerRoute.setMaxForRoute(new HttpRoute(localhost), 50);
ConnManagerParams.setMaxConnectionsPerRoute(params, connPerRoute);
SchemeRegistry schemeRegistry = new SchemeRegistry();
schemeRegistry.register(
new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
schemeRegistry.register(
new Scheme("https", SSLSocketFactory.getSocketFactory(), 443));
ClientConnectionManager cm = new ThreadSafeClientConnManager(params, schemeRegistry);
HttpClient httpClient = new DefaultHttpClient(cm, params);

2.8.5 連接管理器關閉

當一個HttpClient實例不再需要時,而且即將走出使用範圍,那麼關閉連接管理器來保證由管理器保持活動的所有連接被關閉,由連接分配的系統資源被釋放是很重要的。

DefaultHttpClient httpclient = new DefaultHttpClient();
HttpGet httpget = new HttpGet("http://www.google.com/");
HttpResponse response = httpclient.execute(httpget);
HttpEntity entity = response.getEntity();
System.out.println(response.getStatusLine());
if (entity != null) {
entity.consumeContent();
}
httpclient.getConnectionManager().shutdown();

2.9 連接管理參數

這些是可以用於定製標準HTTP連接管理器實現的參數:
  • 'http.conn-manager.timeout':定義了當從ClientConnectionManager中檢索ManagedClientConnection實例時使用的毫秒級的超時時間。這個參數期望得到一個java.lang.Long類型的值。如果這個參數沒有被設置,連接請求就不會超時(無限大的超時時間)。
  • 'http.conn-manager.max-per-route':定義了每個路由連接的最大數量。這個限制由客戶端連接管理器來解釋,而且應用於獨立的管理器實例。這個參數期望得到一個ConnPerRoute類型的值。
  • 'http.conn-manager.max-total':定義了總共連接的最大數目。這個限制由客戶端連接管理器來解釋,而且應用於獨立的管理器實例。這個參數期望得到一個java.lang.Integer類型的值。

2.10 多線程執行請求

當配備連接池管理器時,比如ThreadSafeClientConnManager,HttpClient可以同時被用來執行多個請求,使用多線程執行。

ThreadSafeClientConnManager將會分配基於它的配置的連接。如果對於給定路由的所有連接都被租出了,那麼連接的請求將會阻塞,直到一個連接被釋放回連接池。它可以通過設置'http.conn-manager.timeout'爲一個正數來保證連接管理器不會在連接請求執行時無限期的被阻塞。如果連接請求不能在給定的時間週期內被響應,將會拋出ConnectionPoolTimeoutException異常。

HttpParams params = new BasicHttpParams();
SchemeRegistry schemeRegistry = new SchemeRegistry();
schemeRegistry.register(
new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
ClientConnectionManager cm = new ThreadSafeClientConnManager(params, schemeRegistry);
HttpClient httpClient = new DefaultHttpClient(cm, params);
// 執行GET方法的URI
String[] urisToGet = {
"http://www.domain1.com/",
"http://www.domain2.com/",
"http://www.domain3.com/",
"http://www.domain4.com/"
};
// 爲每個URI創建一個線程
GetThread[] threads = new GetThread[urisToGet.length];
for (int i = 0; i < threads.length; i++) {
HttpGet httpget = new HttpGet(urisToGet[i]);
threads[i] = new GetThread(httpClient, httpget);
}
// 開始執行線程
for (int j = 0; j < threads.length; j++) {
threads[j].start();
}
// 合併線程
for (int j = 0; j < threads.length; j++) {
threads[j].join();
}
 
static class GetThread extends Thread {
private final HttpClient httpClient;
private final HttpContext context;
private final HttpGet httpget;
public GetThread(HttpClient httpClient, HttpGet httpget) {
this.httpClient = httpClient;
this.context = new BasicHttpContext();
this.httpget = httpget;
}
@Override
public void run() {
try {
HttpResponse response = this.httpClient.execute(this.httpget, this.context);
HttpEntity entity = response.getEntity();
if (entity != null) {
// 對實體做些有用的事情...
// 保證連接能釋放回管理器
entity.consumeContent();
}
} catch (Exception ex) {
this.httpget.abort();
}
}
}

2.11 連接收回策略

一個經典的阻塞I/O模型的主要缺點是網絡套接字僅當I/O操作阻塞時纔可以響應I/O事件。當一個連接被釋放返回管理器時,它可以被保持活動狀態而卻不能監控套接字的狀態和響應任何I/O事件。如果連接在服務器端關閉,那麼客戶端連接也不能去偵測連接狀態中的變化和關閉本端的套接字去作出適當響應。

HttpClient通過測試連接是否是過時的來嘗試去減輕這個問題,這已經不再有效了,因爲它已經在服務器端關閉了,之前使用執行HTTP請求的連接。過時的連接檢查也並不是100%的穩定,反而對每次請求執行還要增加10到30毫秒的開銷。唯一可行的而不涉及到每個對空閒連接的套接字模型線程解決方案,是使用專用的監控線程來收回因爲長時間不活動而被認爲是過期的連接。監控線程可以週期地調用ClientConnectionManager#closeExpiredConnections()方法來關閉所有過期的連接,從連接池中收回關閉的連接。它也可以選擇性調用ClientConnectionManager#closeIdleConnections()方法來關閉所有已經空閒超過給定時間週期的連接。

public static class IdleConnectionMonitorThread extends Thread {
private final ClientConnectionManager connMgr;
private volatile boolean shutdown;
public IdleConnectionMonitorThread(ClientConnectionManager connMgr) {
super();
this.connMgr = connMgr;
}
@Override
public void run() {
try {
while (!shutdown) {
synchronized (this) {
wait(5000);
// 關閉過期連接
connMgr.closeExpiredConnections();
// 可選地,關閉空閒超過30秒的連接
connMgr.closeIdleConnections(30, TimeUnit.SECONDS);
}
}
} catch (InterruptedException ex) {
// 終止
}
}
public void shutdown() {
shutdown = true;
synchronized (this) {
notifyAll();
}
}
}

2.12 連接保持活動的策略

HTTP規範沒有確定一個持久連接可能或應該保持活動多長時間。一些HTTP服務器使用非標準的頭部信息Keep-Alive來告訴客戶端它們想在服務器端保持連接活動的週期秒數。如果這個信息可用,HttClient就會利用這個它。如果頭部信息Keep-Alive在響應中不存在,HttpClient假設連接無限期的保持活動。然而許多現實中的HTTP服務器配置了在特定不活動週期之後丟掉持久連接來保存系統資源,往往這是不通知客戶端的。如果默認的策略證明是過於樂觀的,那麼就會有人想提供一個定製的保持活動策略。

DefaultHttpClient httpclient = new DefaultHttpClient();
httpclient.setKeepAliveStrategy(new ConnectionKeepAliveStrategy() {
public long getKeepAliveDuration(HttpResponse response, HttpContext context) {
// 兌現'keep-alive'頭部信息
HeaderElementIterator it = new BasicHeaderElementIterator(
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) {
}
}
}
HttpHost target = (HttpHost) context.getAttribute(
ExecutionContext.HTTP_TARGET_HOST);
if ("www.naughty-server.com".equalsIgnoreCase(target.getHostName())) {
// 只保持活動5秒
return 5 * 1000;
} else {
// 否則保持活動30秒
return 30 * 1000;
}
}
}); 
發佈了24 篇原創文章 · 獲贊 75 · 訪問量 36萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章