兩個主機創建鏈接的過程是很複雜的一個過程,涉及到多個數據包的交換,而且也很耗時間。Http鏈接須要的三次握手開銷很大,這一開銷對於比較小的http消息來講更大。可是若是咱們直接使用已經創建好的http鏈接,這樣花費就比較小,吞吐率更大。
傳統的HttpURLConnection並不支持鏈接池,若是要實現鏈接池的機制,還須要本身來管理鏈接對象。對於網絡請求這種底層相對複雜的操做,我的覺得若是有可用的其餘方案,也沒有必要本身去管理鏈接對象。html
除了HttpURLConnection,你們確定還知道HttpClient。通常狀況下,普通使用HttpClient已經能知足咱們的需求,不過有時候,在咱們須要高併發大量的請求網絡的時候,仍是用「鏈接池」這樣的概念能提高吞吐量。java
咱們來看下怎麼使用 org.apache.httpcomponents.httpclient(版本4.4)提供的鏈接池來實現咱們的高併發網絡請求。apache
使用到的jar包:
org\apache\httpcomponents\httpclient\4.4-beta1\httpclient-4.4-beta1.jar
org\apache\httpcomponents\httpclient-cache\4.4-beta1\httpclient-cache-4.4-beta1.jar
org\apache\httpcomponents\httpcore\4.4-beta1\httpcore-4.4-beta1.jarjson
下面代碼實例中主要使用到 PoolingHttpClientConnectionManager數組
package com.hvgroup.zhuhai10086.jms.utils;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.io.UnsupportedEncodingException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.NameValuePair;
import org.apache.http.NoHttpResponseException;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;
/** * HttpClient工具類 * * @return * @author SHANHY * @create 2015年12月18日 */
public class HttpClientUtil {
static final int timeOut = 10 * 1000;
private static CloseableHttpClient httpClient = null;
private final static Object syncLock = new Object();
private static void config(HttpRequestBase httpRequestBase) {
// 設置Header等
// httpRequestBase.setHeader("User-Agent", "Mozilla/5.0");
// httpRequestBase
// .setHeader("Accept",
// "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
// httpRequestBase.setHeader("Accept-Language",
// "zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3");// "en-US,en;q=0.5");
// httpRequestBase.setHeader("Accept-Charset",
// "ISO-8859-1,utf-8,gbk,gb2312;q=0.7,*;q=0.7");
// 配置請求的超時設置
RequestConfig requestConfig = RequestConfig.custom()
.setConnectionRequestTimeout(timeOut)
.setConnectTimeout(timeOut).setSocketTimeout(timeOut).build();
httpRequestBase.setConfig(requestConfig);
}
/** * 獲取HttpClient對象 * * @return * @author SHANHY * @create 2015年12月18日 */
public static CloseableHttpClient getHttpClient(String url) {
String hostname = url.split("/")[2];
int port = 80;
if (hostname.contains(":")) {
String[] arr = hostname.split(":");
hostname = arr[0];
port = Integer.parseInt(arr[1]);
}
if (httpClient == null) {
synchronized (syncLock) {
if (httpClient == null) {
httpClient = createHttpClient(200, 40, 100, hostname, port);
}
}
}
return httpClient;
}
/** * 建立HttpClient對象 * * @return * @author SHANHY * @create 2015年12月18日 */
public static CloseableHttpClient createHttpClient(int maxTotal,
int maxPerRoute, int maxRoute, String hostname, int port) {
ConnectionSocketFactory plainsf = PlainConnectionSocketFactory
.getSocketFactory();
LayeredConnectionSocketFactory sslsf = SSLConnectionSocketFactory
.getSocketFactory();
Registry<ConnectionSocketFactory> registry = RegistryBuilder
.<ConnectionSocketFactory> create().register("http", plainsf)
.register("https", sslsf).build();
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(
registry);
// 將最大鏈接數增長
cm.setMaxTotal(maxTotal);
// 將每一個路由基礎的鏈接增長
cm.setDefaultMaxPerRoute(maxPerRoute);
HttpHost httpHost = new HttpHost(hostname, port);
// 將目標主機的最大鏈接數增長
cm.setMaxPerRoute(new HttpRoute(httpHost), maxRoute);
// 請求重試處理
HttpRequestRetryHandler httpRequestRetryHandler = new HttpRequestRetryHandler() {
public boolean retryRequest(IOException exception,
int executionCount, HttpContext context) {
if (executionCount >= 5) {// 若是已經重試了5次,就放棄
return false;
}
if (exception instanceof NoHttpResponseException) {// 若是服務器丟掉了鏈接,那麼就重試
return true;
}
if (exception instanceof SSLHandshakeException) {// 不要重試SSL握手異常
return false;
}
if (exception instanceof InterruptedIOException) {// 超時
return false;
}
if (exception instanceof UnknownHostException) {// 目標服務器不可達
return false;
}
if (exception instanceof ConnectTimeoutException) {// 鏈接被拒絕
return false;
}
if (exception instanceof SSLException) {// SSL握手異常
return false;
}
HttpClientContext clientContext = HttpClientContext
.adapt(context);
HttpRequest request = clientContext.getRequest();
// 若是請求是冪等的,就再次嘗試
if (!(request instanceof HttpEntityEnclosingRequest)) {
return true;
}
return false;
}
};
CloseableHttpClient httpClient = HttpClients.custom()
.setConnectionManager(cm)
.setRetryHandler(httpRequestRetryHandler).build();
return httpClient;
}
private static void setPostParams(HttpPost httpost,
Map<String, Object> params) {
List<NameValuePair> nvps = new ArrayList<NameValuePair>();
Set<String> keySet = params.keySet();
for (String key : keySet) {
nvps.add(new BasicNameValuePair(key, params.get(key).toString()));
}
try {
httpost.setEntity(new UrlEncodedFormEntity(nvps, "UTF-8"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
/** * GET請求URL獲取內容 * * @param url * @return * @author SHANHY * @throws IOException * @create 2015年12月18日 */
public static String post(String url, Map<String, Object> params) throws IOException {
HttpPost httppost = new HttpPost(url);
config(httppost);
setPostParams(httppost, params);
CloseableHttpResponse response = null;
try {
response = getHttpClient(url).execute(httppost,
HttpClientContext.create());
HttpEntity entity = response.getEntity();
String result = EntityUtils.toString(entity, "utf-8");
EntityUtils.consume(entity);
return result;
} catch (Exception e) {
// e.printStackTrace();
throw e;
} finally {
try {
if (response != null)
response.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/** * GET請求URL獲取內容 * * @param url * @return * @author SHANHY * @create 2015年12月18日 */
public static String get(String url) {
HttpGet httpget = new HttpGet(url);
config(httpget);
CloseableHttpResponse response = null;
try {
response = getHttpClient(url).execute(httpget,
HttpClientContext.create());
HttpEntity entity = response.getEntity();
String result = EntityUtils.toString(entity, "utf-8");
EntityUtils.consume(entity);
return result;
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (response != null)
response.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
public static void main(String[] args) {
// URL列表數組
String[] urisToGet = {
"http://blog.csdn.net/catoop/article/details/38849497",
"http://blog.csdn.net/catoop/article/details/38849497",
"http://blog.csdn.net/catoop/article/details/38849497",
"http://blog.csdn.net/catoop/article/details/38849497",
"http://blog.csdn.net/catoop/article/details/38849497",
"http://blog.csdn.net/catoop/article/details/38849497",
"http://blog.csdn.net/catoop/article/details/38849497",
"http://blog.csdn.net/catoop/article/details/38849497",
"http://blog.csdn.net/catoop/article/details/38849497",
"http://blog.csdn.net/catoop/article/details/38849497",
"http://blog.csdn.net/catoop/article/details/38849497",
"http://blog.csdn.net/catoop/article/details/38849497",
"http://blog.csdn.net/catoop/article/details/38849497",
"http://blog.csdn.net/catoop/article/details/38849497",
"http://blog.csdn.net/catoop/article/details/38849497",
"http://blog.csdn.net/catoop/article/details/38849497",
"http://blog.csdn.net/catoop/article/details/38849497",
"http://blog.csdn.net/catoop/article/details/38849497",
"http://blog.csdn.net/catoop/article/details/38849497",
"http://blog.csdn.net/catoop/article/details/38849497",
"http://blog.csdn.net/catoop/article/details/38849497",
"http://blog.csdn.net/catoop/article/details/38849497",
"http://blog.csdn.net/catoop/article/details/38849497",
"http://blog.csdn.net/catoop/article/details/38849497" };
long start = System.currentTimeMillis();
try {
int pagecount = urisToGet.length;
ExecutorService executors = Executors.newFixedThreadPool(pagecount);
CountDownLatch countDownLatch = new CountDownLatch(pagecount);
for (int i = 0; i < pagecount; i++) {
HttpGet httpget = new HttpGet(urisToGet[i]);
config(httpget);
// 啓動線程抓取
executors
.execute(new GetRunnable(urisToGet[i], countDownLatch));
}
countDownLatch.await();
executors.shutdown();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("線程" + Thread.currentThread().getName() + ","
+ System.currentTimeMillis() + ", 全部線程已完成,開始進入下一步!");
}
long end = System.currentTimeMillis();
System.out.println("consume -> " + (end - start));
}
static class GetRunnable implements Runnable {
private CountDownLatch countDownLatch;
private String url;
public GetRunnable(String url, CountDownLatch countDownLatch) {
this.url = url;
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
try {
System.out.println(HttpClientUtil.get(url));
} finally {
countDownLatch.countDown();
}
}
}
}
Post使用方法服務器
// 其中 params 爲 Map<String, Object> params
String ret = HttpClientUtil.post(url, params);
jsonRet = new JSONObject(ret);
一開始我是使用傳統的 HttpURLConnection 來作網絡請求的,查了不少資料,有很多說 HttpURLConnection 效率高的。但是通過我修改實現方法後,HttpClient 鏈接池版本的網絡請求相對比較穩定。這也說明,咱們並不請盡信他人解說,凡事仍是要尋找適合本身的方法,真正的解決本身的問題,纔是王道。restful
===========================================網絡
在使用 HttpURLConnection 的時候,大併發對外作網絡請求的時候,前期請求耗時還好,後面耗時愈來愈高。下面是我以前的實現代碼:併發
@Deprecated
protected JSONObject callRestfulOld(String url, Map<String, Object> params)
{
String temp;
String ret="";
JSONObject jsonRet=null;
String sign = generateSign("POST", url, params);// 對參數進行加密簽名
if(sign.isEmpty()) return new JSONObject("{\"ret_code\":-1,\"err_msg\":\"generateSign error\"}");
params.put("sign", sign);
try{
URL u = new URL(url);
HttpURLConnection conn = (HttpURLConnection)u.openConnection();
conn.setRequestMethod("POST");
conn.setConnectTimeout(10000);
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setUseCaches(false);
StringBuffer param = new StringBuffer();
for (String key: params.keySet())
{
param.append(key).append("=").append(URLEncoder.encode(params.get(key).toString(), "UTF-8")).append("&");
}
conn.getOutputStream().write(param.toString().getBytes("UTF-8"));
//System.out.println(param);
conn.getOutputStream().flush();
conn.getOutputStream().close();
InputStreamReader isr = new InputStreamReader(conn.getInputStream());
BufferedReader br = new BufferedReader(isr);
while((temp = br.readLine()) != null){
ret += temp;
}
br.close();
isr.close();
conn.disconnect();
//System.out.println(ret);
jsonRet = new JSONObject(ret);
} catch(java.net.SocketTimeoutException e) {
//e.printStackTrace();
jsonRet = new JSONObject("{\"ret_code\":-1,\"err_msg\":\"call restful timeout\"}");
} catch(Exception e) {
//e.printStackTrace();
jsonRet = new JSONObject("{\"ret_code\":-1,\"err_msg\":\"call restful error\"}");
}
return jsonRet;
}