HttpClient有一個對連接初始化和終止,還有在活動連接上I/O操作的完整控制。而連接操作的很多方面可以使用一些參數(shù)來控制。
2.1 連接參數(shù)
這些參數(shù)可以影響連接操作:
- 'http.socket.timeout':定義了套接字的毫秒級超時時間(SO_TIMEOUT),這就是等待數(shù)據(jù),換句話說,在兩個連續(xù)的數(shù)據(jù)包之間最大的閑置時間。如果超時時間是0就解釋為是一個無限大的超時時間。這個參數(shù)期望得到一個java.lang.Integer類型的值。如果這個參數(shù)沒有被設(shè)置,那么讀取操作就不會超時(無限大的超時時間)。
- 'http.tcp.nodelay':決定了是否使用Nagle算法。Nagle算法視圖通過最小化發(fā)送的分組數(shù)量來節(jié)省帶寬。當(dāng)應(yīng)用程序希望降低網(wǎng)絡(luò)延遲并提高性能時,它們可以關(guān)閉Nagle算法(也就是開啟TCP_NODELAY)。數(shù)據(jù)將會更早發(fā)送,增加了帶寬消耗的成文。這個參數(shù)期望得到一個java.lang.Boolean類型的值。如果這個參數(shù)沒有被設(shè)置,那么TCP_NODELAY就會開啟(無延遲)。
- 'http.socket.buffer-size':決定了內(nèi)部套接字緩沖使用的大小,來緩沖數(shù)據(jù)同時接收/傳輸HTTP報文。這個參數(shù)期望得到一個java.lang.Integer類型的值。如果這個參數(shù)沒有被設(shè)置,那么HttpClient將會分配8192字節(jié)的套接字緩存。
- 'http.socket.linger':使用指定的秒數(shù)拖延時間來設(shè)置SO_LINGER。最大的連接超時值是平臺指定的。值0暗示了這個選項是關(guān)閉的。值-1暗示了使用了JRE默認的。這個設(shè)置僅僅影響套接字關(guān)閉操作。如果這個參數(shù)沒有被設(shè)置,那么就假設(shè)值為-1(JRE默認)。
- 'http.connection.timeout':決定了直到連接建立時的毫秒級超時時間。超時時間的值為0解釋為一個無限大的時間。這個參數(shù)期望得到一個java.lang.Integer類型的值。如果這個參數(shù)沒有被設(shè)置,連接操作將不會超時(無限大的超時時間)。
- 'http.connection.stalecheck':決定了是否使用舊的連接檢查。當(dāng)在一個連接之上執(zhí)行一個請求而服務(wù)器端的連接已經(jīng)關(guān)閉時,關(guān)閉舊的連接檢查可能導(dǎo)致在獲得一個I/O錯誤風(fēng)險時顯著的性能提升(對于每一個請求,檢查時間可以達到30毫秒)。這個參數(shù)期望得到一個java.lang.Boolean類型的值。出于性能的關(guān)鍵操作,檢查應(yīng)該被關(guān)閉。如果這個參數(shù)沒有被設(shè)置,那么舊的連接將會在每個請求執(zhí)行之前執(zhí)行。
- 'http.connection.max-line-length':決定了最大請求行長度的限制。如果設(shè)置為一個正數(shù),任何HTTP請求行超過這個限制將會引發(fā)java.io.IOException異常。負數(shù)或零將會關(guān)閉這個檢查。這個參數(shù)期望得到一個java.lang.Integer類型的值。如果這個參數(shù)沒有被設(shè)置,那么就不強制進行限制了。
- 'http.connection.max-header-count':決定了允許的最大HTTP頭部信息數(shù)量。如果設(shè)置為一個正數(shù),從數(shù)據(jù)流中獲得的HTTP頭部信息數(shù)量超過這個限制就會引發(fā)java.io.IOException異常。負數(shù)或零將會關(guān)閉這個檢查。這個參數(shù)期望得到一個java.lang.Integer類型的值。如果這個參數(shù)沒有被設(shè)置,那么就不
- 強制進行限制了。
- 'http.connection.max-status-line-garbage':決定了在期望得到HTTP響應(yīng)狀態(tài)行之前可忽略請求行的最大數(shù)量。使用HTTP/1.1持久性連接,這個問題產(chǎn)生的破碎的腳本將會返回一個錯誤的Content-Length(有比指定的字節(jié)更多的發(fā)送)。不幸的是,在某些情況下,這個不能在錯誤響應(yīng)后來偵測,只能在下一次之前。所以HttpClient必須以這種方式跳過那些多余的行。這個參數(shù)期望得到一個java.lang.Integer類型的值。0是不允許在狀態(tài)行之前的所有垃圾/空行。使用java.lang.Integer#MAX_VALUE來設(shè)置不限制的數(shù)字。如果這個參數(shù)沒有被設(shè)置那就假設(shè)是不限制的。
2.2 持久連接
從一個主機向另外一個建立連接的過程是相當(dāng)復(fù)雜的,而且包含了兩個終端之間的很多包的交換,它是相當(dāng)費時的。連接握手的開銷是很重要的,特別是對小量的HTTP報文。如果打開的連接可以被重用來執(zhí)行多次請求,那么就可以達到很高的數(shù)據(jù)吞吐量。
HTTP/1.1強調(diào)HTTP連接默認情況可以被重用于多次請求。HTTP/1.0兼容的終端也可以使用相似的機制來明確地交流它們的偏好來保證連接處于活動狀態(tài),也使用它來處理多個請求。HTTP代理也可以保持空閑連接處于一段時間的活動狀態(tài),防止對相同目標(biāo)主機的一個連接也許對隨后的請求需要。保持連接活動的能力通常被稱作持久性連接。HttpClient完全支持持久性連接。
2.3 HTTP連接路由
HttpClient能夠直接或通過路由建立連接到目標(biāo)主機,這會涉及多個中間連接,也被稱為跳。HttpClient區(qū)分路由和普通連接,通道和分層。通道連接到目標(biāo)主機的多個中間代理的使用也稱作是代理鏈。
普通路由由連接到目標(biāo)或僅第一次的代理來創(chuàng)建。通道路由通過代理鏈到目標(biāo)連接到第一通道來建立。沒有代理的路由不是通道的,分層路由通過已存在連接的分層協(xié)議來建立。協(xié)議僅僅可以在到目標(biāo)的通道上或在沒有代理的直接連接上分層。
2.3.1 路由計算
RouteInfo接口代表關(guān)于最終涉及一個或多個中間步驟或跳的目標(biāo)主機路由的信息。HttpRoute是RouteInfo的具體實現(xiàn),這是不能改變的(是不變的)。HttpTracker是可變的RouteInfo實現(xiàn),由HttpClient在內(nèi)部使用來跟蹤到最大路由目標(biāo)的剩余跳數(shù)。HttpTracker可以在成功執(zhí)行向路由目標(biāo)的下一跳之后更新。HttpRouteDirector是一個幫助類,可以用來計算路由中的下一跳。這個類由HttpClient在內(nèi)部使用。
HttpRoutePlanner是一個代表計算到基于執(zhí)行上下文到給定目標(biāo)完整路由策略的接口。HttpClient附帶兩個默認的HttpRoutePlanner實現(xiàn)。ProxySelectorRoutePlanner是基于java.net.ProxySelector的。默認情況下,它會從系統(tǒng)屬性中或從運行應(yīng)用程序的瀏覽器中選取JVM的代理設(shè)置。DefaultHttpRoutePlanner實現(xiàn)既不使用任何Java系統(tǒng)屬性,也不使用系統(tǒng)或瀏覽器的代理設(shè)置。它只基于HTTP如下面描述的參數(shù)計算路由。
2.3.2 安全HTTP連接
如果信息在兩個不能由非認證的第三方進行讀取或修改的終端之間傳輸,HTTP連接可以被認為是安全的。SSL/TLS協(xié)議是用來保證HTTP傳輸安全使用最廣泛的技術(shù)。而其它加密技術(shù)也可以被使用。通常來說,HTTP傳輸是在SSL/TLS加密連接之上分層的。
2.4 HTTP路由參數(shù)
- 'http.route.default-proxy':定義可以被不使用JRE設(shè)置的默認路由規(guī)劃者使用的代理主機。這個參數(shù)期望得到一個HttpHost類型的值。如果這個參數(shù)沒有被設(shè)置,那么就會嘗試直接連接到目標(biāo)。
- 'http.route.local-address':定義一個本地地址由所有默認路由規(guī)劃者來使用。有多個網(wǎng)絡(luò)接口的機器中,這個參數(shù)可以被用于從連接源中選擇網(wǎng)絡(luò)接口。這個參數(shù)期望得到一個java.net.InetAddress類型的值。如果這個參數(shù)沒有被設(shè)置,將會自動使用本地地址。
- 'http.route.forced-route':定義一個由所有默認路由規(guī)劃者使用的強制路由。代替了計算路由,給定的強制路由將會被返回,盡管它指向一個完全不同的目標(biāo)主機。這個參數(shù)期望得到一個HttpRoute類型的值。如果這個參數(shù)沒有被設(shè)置,那么就使用默認的規(guī)則建立連接到目標(biāo)服務(wù)器。
2.5 套接字工廠
LayeredSocketFactory是SocketFactory接口的擴展。分層的套接字工廠可HTTP連接內(nèi)部使用java.net.Socket對象來處理數(shù)據(jù)在線路上的傳輸。它們依賴SocketFactory接口來創(chuàng)建,初始化和連接套接字。這會使得HttpClient的用戶可以提供在運行時指定套接字初始化代碼的應(yīng)用程序。PlainSocketFactory是創(chuàng)建和初始化普通的(不加密的)套接字的默認工廠。
創(chuàng)建套接字的過程和連接到主機的過程是不成對的,所以套接字在連接操作封鎖時可以被關(guān)閉。
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接口的擴展。分層的套接字工廠可以創(chuàng)建在已經(jīng)存在的普通套接字之上的分層套接字。套接字分層主要通過代理來創(chuàng)建安全的套接字。HttpClient附帶實現(xiàn)了SSL/TLS分層的SSLSocketFactory。請注意HttpClient不使用任何自定義加密功能。它完全依賴于標(biāo)準(zhǔn)的Java密碼學(xué)(JCE)和安全套接字(JSEE)擴展。
2.5.2 SSL/TLS的定制
HttpClient使用SSLSocketFactory來創(chuàng)建SSL連接。SSLSocketFactory允許高度定制。它可以使用javax.net.ssl.SSLContext的實例作為參數(shù),并使用它來創(chuàng)建定制SSL連接。
TrustManager easyTrustManager = new X509TrustManager() {@Overridepublic void checkClientTrusted(X509Certificate[] chain,String authType) throws CertificateException {// 哦,這很簡單!}@Overridepublic void checkServerTrusted(X509Certificate[] chain,String authType) throws CertificateException {//哦,這很簡單!}@Overridepublic 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);
2.5.3 主機名驗證
- StrictHostnameVerifier:嚴(yán)格的主機名驗證在Sun Java 1.4,Sun Java 5和Sun Java 6中是相同的。而且也非常接近IE6。這個實現(xiàn)似乎是兼容RFC 2818處理通配符的。主機名必須匹配第一個CN或任意的subject-alt。在CN和其它任意的subject-alt中可能會出現(xiàn)通配符。
- BrowserCompatHostnameVerifier:主機名驗證器和Curl和Firefox的工作方式是相同的。主機名必須匹配第一個CN或任意的subject-alt。在CN和其它任意的subject-alt中可能會出現(xiàn)通配符。BrowserCompatHostnameVerifier和StrictHostnameVerifier的唯一不同是使用BrowserCompatHostnameVerifier匹配所有子域的通配符(比如”*.foo.com”),包括”a.b.foo.com”。
- AllowAllHostnameVerifier:這個主機名驗證器基本上是關(guān)閉主機名驗證的。這個實現(xiàn)是一個空操作,而且不會拋出javax.net.ssl.SSLException異常。
每一個默認的HttpClient使用BrowserCompatHostnameVerifier的實現(xiàn)。如果需要的話,它可以指定不同的主機名驗證器實現(xiàn)。
SSLSocketFactory sf = new SSLSocketFactory(SSLContext.getInstance("TLS"));sf.setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);
2.6 協(xié)議模式
Scheme類代表了一個協(xié)議模式,比如“http”或“https”同時包含一些協(xié)議屬性,比如默認端口,用來為給定協(xié)議創(chuàng)建java.net.Socket實例的套接字工廠。SchemeRegistry類用來維持一組Scheme,當(dāng)去通過請求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了解復(fù)雜的路由模式和代理鏈,它僅支持簡單直接的或開箱的跳式代理連接。
告訴HttpClient通過代理去連接到目標(biāo)主機的最簡單方式是通過設(shè)置默認的代理參數(shù):
DefaultHttpClient httpclient = new DefaultHttpClient();HttpHost proxy = new HttpHost("someproxy", 8080);httpclient.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy);
也可以構(gòu)建HttpClient使用標(biāo)準(zhǔn)的JRE代理選擇器來獲得代理信息:
DefaultHttpClient httpclient = new DefaultHttpClient();ProxySelectorRoutePlanner routePlanner = new ProxySelectorRoutePlanner(httpclient.getConnectionManager().getSchemeRegistry(),ProxySelector.getDefault());httpclient.setRoutePlanner(routePlanner);
另外一種選擇,可以提供一個定制的RoutePlanner實現(xiàn)來獲得HTTP路由計算處理上的復(fù)雜的控制:
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 連接操作器
連接操作是客戶端的低層套接字或可以通過外部實體,通常稱為連接操作的被操作的狀態(tài)的連接。OperatedClientConnection接口擴展了HttpClientConnection接口而且定義了額外的控制連接套接字的方法。ClientConnectionOperator接口代表了創(chuàng)建實例和更新那些對象低層套接字的策略。實現(xiàn)類最有可能利用SocketFactory來創(chuàng)建java.net.Socket實例。ClientConnectionOperator接口可以讓HttpClient的用戶提供一個連接操作的定制策略和提供可選實現(xiàn)OperatedClientConnection接口的能力。
2.8.2 管理連接和連接管理器
HTTP連接是復(fù)雜的,有狀態(tài)的,線程不安全的對象需要正確的管理以便正確地執(zhí)行功能。HTTP連接在同一時間僅僅只能由一個執(zhí)行線程來使用。HttpClient采用一個特殊實體來管理訪問HTTP連接,這被稱為HTTP連接管理器,代表了ClientConnectionManager接口。一個HTTP連接管理器的目的是作為工廠服務(wù)于新的HTTP連接,管理持久連接和同步訪問持久連接來確保同一時間僅有一個線程可以訪問一個連接。
內(nèi)部的HTTP連接管理器和OperatedClientConnection實例一起工作,但是它們?yōu)榉?wù)消耗器ManagedClientConnection提供實例。ManagedClientConnection扮演連接之上管理狀態(tài)控制所有I/O操作的OperatedClientConnection實例的包裝器。它也抽象套接字操作,提供打開和更新去創(chuàng)建路由套接字便利的方法。ManagedClientConnection實例了解產(chǎn)生它們到連接管理器的鏈接,而且基于這個事實,當(dāng)不再被使用時,它們必須返回到管理器。ManagedClientConnection類也實現(xiàn)了ConnectionReleaseTrigger接口,可以被用來觸發(fā)釋放連接返回給管理器。一旦釋放連接操作被觸發(fā)了,被包裝的連接從ManagedClientConnection包裝器中脫離,OperatedClientConnection實例被返回給管理器。盡管服務(wù)消耗器仍然持有ManagedClientConnection實例的引用,它也不再去執(zhí)行任何I/O操作或有意無意地改變的OperatedClientConnection狀態(tài)。
這里有一個從連接管理器中獲取連接的示例:
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 {// 用連接在做有用的事情。當(dāng)完成時釋放連接。conn.releaseConnection();} catch (IOException ex) {// 在I/O error之上終止連接。conn.abortConnection();throw ex;}
如果需要,連接請求可以通過調(diào)用來ClientConnectionRequest#abortRequest()方法過早地中斷。這會解鎖在ClientConnectionRequest#getConnection()方法中被阻止的線程。
一旦響應(yīng)內(nèi)容被完全消耗后,BasicManagedEntity包裝器類可以用來保證自動釋放低層的連接。HttpClient內(nèi)部使用這個機制來實現(xiàn)透明地對所有從HttpClient#execute()方法中獲得響應(yīng)釋放連接:
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);}// 使用響應(yīng)對象做有用的事情。當(dāng)響應(yīng)內(nèi)容被消耗后這個連接將會自動釋放。} catch (IOException ex) {//在I/O error之上終止連接。conn.abortConnection();throw ex;}
2.8.3 簡單連接管理器
SingleClientConnManager是一個簡單的連接管理器,在同一時間它僅僅維護一個連接。盡管這個類是線程安全的,但它應(yīng)該被用于一個執(zhí)行線程。SingleClientConnManager對于同一路由的后續(xù)請求會盡量重用連接。而如果持久連接的路由不匹配連接請求的話,它也會關(guān)閉存在的連接之后對給定路由再打開一個新的。如果連接已經(jīng)被分配,將會拋出java.lang.IllegalStateException異常。
對于每個默認連接,HttpClient使用SingleClientConnManager。
2.8.4 連接池管理器
ThreadSafeClientConnManager是一個復(fù)雜的實現(xiàn)來管理客戶端連接池,它也可以從多個執(zhí)行線程中服務(wù)連接請求。對每個基本的路由,連接都是池管理的。對于路由的請求,管理器在池中有可用的持久性連接,將被從池中租賃連接服務(wù),而不是創(chuàng)建一個新的連接。
ThreadSafeClientConnManager維護每個基本路由的最大連接限制。每個默認的實現(xiàn)對每個給定路由將會創(chuàng)建不超過兩個的并發(fā)連接,而總共也不會超過20個連接。對于很多真實的應(yīng)用程序,這個限制也證明很大的制約,特別是他們在服務(wù)中使用HTTP作為傳輸協(xié)議。連接限制,也可以使用HTTP參數(shù)來進行調(diào)整。
這個示例展示了連接池參數(shù)是如何來調(diào)整的:
HttpParams params = new BasicHttpParams();// 增加最大連接到200ConnManagerParams.setMaxTotalConnections(params, 200);// 增加每個路由的默認最大連接到20ConnPerRouteBean connPerRoute = new ConnPerRouteBean(20);// 對localhost:80增加最大連接到50HttpHost 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 連接管理器關(guān)閉
當(dāng)一個HttpClient實例不再需要時,而且即將走出使用范圍,那么關(guān)閉連接管理器來保證由管理器保持活動的所有連接被關(guān)閉,由連接分配的系統(tǒng)資源被釋放是很重要的。
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 連接管理參數(shù)
- 'http.conn-manager.timeout':定義了當(dāng)從ClientConnectionManager中檢索ManagedClientConnection實例時使用的毫秒級的超時時間。這個參數(shù)期望得到一個java.lang.Long類型的值。如果這個參數(shù)沒有被設(shè)置,連接請求就不會超時(無限大的超時時間)。
- 'http.conn-manager.max-per-route':定義了每個路由連接的最大數(shù)量。這個限制由客戶端連接管理器來解釋,而且應(yīng)用于獨立的管理器實例。這個參數(shù)期望得到一個ConnPerRoute類型的值。
- 'http.conn-manager.max-total':定義了總共連接的最大數(shù)目。這個限制由客戶端連接管理器來解釋,而且應(yīng)用于獨立的管理器實例。這個參數(shù)期望得到一個java.lang.Integer類型的值。
2.10 多線程執(zhí)行請求
當(dāng)配備連接池管理器時,比如ThreadSafeClientConnManager,HttpClient可以同時被用來執(zhí)行多個請求,使用多線程執(zhí)行。
ThreadSafeClientConnManager將會分配基于它的配置的連接。如果對于給定路由的所有連接都被租出了,那么連接的請求將會阻塞,直到一個連接被釋放回連接池。它可以通過設(shè)置'http.conn-manager.timeout'為一個正數(shù)來保證連接管理器不會在連接請求執(zhí)行時無限期的被阻塞。如果連接請求不能在給定的時間周期內(nèi)被響應(yīng),將會拋出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);// 執(zhí)行GET方法的URIString[] urisToGet = {"http://www.domain1.com/","http://www.domain2.com/","http://www.domain3.com/","http://www.domain4.com/"};// 為每個URI創(chuàng)建一個線程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);}
// 開始執(zhí)行線程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;}@Overridepublic 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 連接收回策略
一個經(jīng)典的阻塞I/O模型的主要缺點是網(wǎng)絡(luò)套接字僅當(dāng)I/O操作阻塞時才可以響應(yīng)I/O事件。當(dāng)一個連接被釋放返回管理器時,它可以被保持活動狀態(tài)而卻不能監(jiān)控套接字的狀態(tài)和響應(yīng)任何I/O事件。如果連接在服務(wù)器端關(guān)閉,那么客戶端連接也不能去偵測連接狀態(tài)中的變化和關(guān)閉本端的套接字去作出適當(dāng)響應(yīng)。
HttpClient通過測試連接是否是過時的來嘗試去減輕這個問題,這已經(jīng)不再有效了,因為它已經(jīng)在服務(wù)器端關(guān)閉了,之前使用執(zhí)行HTTP請求的連接。過時的連接檢查也并不是100%的穩(wěn)定,反而對每次請求執(zhí)行還要增加10到30毫秒的開銷。唯一可行的而不涉及到每個對空閑連接的套接字模型線程解決方案,是使用專用的監(jiān)控線程來收回因為長時間不活動而被認為是過期的連接。監(jiān)控線程可以周期地調(diào)用ClientConnectionManager#closeExpiredConnections()方法來關(guān)閉所有過期的連接,從連接池中收回關(guān)閉的連接。它也可以選擇性調(diào)用ClientConnectionManager#closeIdleConnections()方法來關(guān)閉所有已經(jīng)空閑超過給定時間周期的連接。
public static class IdleConnectionMonitorThread extends Thread {private final ClientConnectionManager connMgr;private volatile boolean shutdown;public IdleConnectionMonitorThread(ClientConnectionManager connMgr) {super();this.connMgr = connMgr;}@Overridepublic void run() {try {while (!shutdown) {synchronized (this) {wait(5000);// 關(guān)閉過期連接connMgr.closeExpiredConnections();// 可選地,關(guān)閉空閑超過30秒的連接connMgr.closeIdleConnections(30, TimeUnit.SECONDS);}}} catch (InterruptedException ex) {// 終止}}public void shutdown() {shutdown = true;synchronized (this) {notifyAll();}}}
2.12 連接保持活動的策略
HTTP規(guī)范沒有確定一個持久連接可能或應(yīng)該保持活動多長時間。一些HTTP服務(wù)器使用非標(biāo)準(zhǔn)的頭部信息Keep-Alive來告訴客戶端它們想在服務(wù)器端保持連接活動的周期秒數(shù)。如果這個信息可用,HttClient就會利用這個它。如果頭部信息Keep-Alive在響應(yīng)中不存在,HttpClient假設(shè)連接無限期的保持活動。然而許多現(xiàn)實中的HTTP服務(wù)器配置了在特定不活動周期之后丟掉持久連接來保存系統(tǒng)資源,往往這是不通知客戶端的。如果默認的策略證明是過于樂觀的,那么就會有人想提供一個定制的保持活動策略。
轉(zhuǎn)載自:http://www.cnblogs.com/loveyakamoz/archive/2011/07/21/2112832.html