PoolingHttpClientConnectionManager是通过租用连接和收回链接的方式来实现的。解决了http请求的多线程问题。
依赖:
org.apache.httpcomponents
httpclient
实现
首先需要初始化连接池。设定链接数量等,定义清理连接池的方法,并定时调用这个方法清理无效或者长期未使用的链接。
public class HttpClientTest {
//全局参数
private static PoolingHttpClientConnectionManager connectionManager = null;
//设置请求参数
private RequestConfig config;
private CloseableHttpClient client;
//单例模式创建
private void init(){
synchronized (HttpClientTest.class) {
if (client == null) {
connectionManager = new PoolingHttpClientConnectionManager();
// http请求线程池,最大连接数
int requestMaxNum = 5000;
ConnectionConfig connConfig = ConnectionConfig.custom().setCharset(Charset.forName("utf-8")).build();
SocketConfig socketConfig = SocketConfig.custom().setSoTimeout(5000).build();
connectionManager.setDefaultConnectionConfig(connConfig);
connectionManager.setDefaultSocketConfig(socketConfig);
// 连接池最大生成连接数
connectionManager.setMaxTotal(requestMaxNum);
// 默认设置route最大连接数
connectionManager.setDefaultMaxPerRoute(requestMaxNum);
//设置请求参数
config = RequestConfig.custom().setConnectTimeout(5000) //连接超时时间
.setConnectionRequestTimeout(500) //从线程池中获取线程超时时间
.setSocketTimeout(5000) //设置数据超时时间
.build();
// 创建builder
HttpClientBuilder builder = HttpClients.custom();
//管理器是共享的,它的生命周期将由调用者管理,并且不会关闭
//否则可能出现Connection pool shut down异常
builder.setConnectionManager(connectionManager).setConnectionManagerShared(true);
// 长连接策略
builder.setKeepAliveStrategy(new DefaultConnectionKeepAliveStrategy());
// 创建httpClient
client = builder.setDefaultRequestConfig(config).setRetryHandler(new MyRetryHandle()).build();
}
}
}
/**
* 从池子中获取连接
* @return CloseableHttpClient
*/
private CloseableHttpClient getClientFromHttpPool() {
if(client == null) {
init();
}
return client;
}
}
定时回收链接
// 启动定时器,定时回收过期的连接
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
// 关闭过期的链接
connectionManager.closeExpiredConnections();
// 选择关闭 空闲30秒的链接
connectionManager.closeIdleConnections(30, TimeUnit.SECONDS);
}
}, 10 * 1000, 5 * 1000);
实现两个方法,执行httpqing请求。
public String getTest(String url) {
CloseableHttpClient client = getClientFromHttpPool();
HttpGet method = new HttpGet(url);
//设置请求头
//method.setRequestHeader();
long start = System.currentTimeMillis();
BufferedReader reader = null;
InputStream inputStream = null;
StringBuilder result = new StringBuilder("");
try {
long startTime = System.currentTimeMillis();
CloseableHttpResponse response = client.execute(method);
if(response.getStatusLine().getStatusCode() == 200){
HttpEntity httpEntity = response.getEntity();
inputStream = httpEntity.getContent();
//解析方式1
reader = new BufferedReader(new InputStreamReader(inputStream));
StringBuilder sb = new StringBuilder();
String line = null;
while ((line = reader.readLine()) != null) {
sb.append(line).append("\n");
}
String ret = sb.toString();
System.out.println(ret);
//解析方式二
byte[] bytes = new byte[1024];
int length = 0;
while(-1 != (length = inputStream.read(bytes))) {
result.append(new String(bytes, 0, length));
}
}else {
System.out.println("请求失败,返回" + response.getStatusLine().getStatusCode());
}
System.out.println("用时:" + (System.currentTimeMillis() - startTime));
return result.toString();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(inputStream != null){
try {
inputStream.close();
}catch (Exception e){
}
}
if(reader != null){
try {
reader.close();
}catch (Exception e){
e.printStackTrace();
}
}
//释放连接
// httpClient必须releaseConnection,但不是abort。因为releaseconnection是归还连接到到连接池,而abort是直接抛弃这个连接,而且占用连接池的数目。
method.releaseConnection();
}
System.out.println("end..Duration MS:" + (System.currentTimeMillis() - start));
return null;
}
public void postTest(String url, String param) {
CloseableHttpClient client = getClientFromHttpPool();
HttpPost method = new HttpPost(url);
//设置请求头
//method.setRequestHeader();
//设置请求参数--可以用各种方式传入参数
try {
method.setEntity(new StringEntity(param));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
long start = System.currentTimeMillis();
BufferedReader reader = null;
InputStream inputStream = null;
try {
CloseableHttpResponse response = client.execute(method);
if(response.getStatusLine().getStatusCode() == 200){
HttpEntity httpEntity = response.getEntity();
inputStream = httpEntity.getContent();
//解析方式1
reader = new BufferedReader(new InputStreamReader(inputStream));
StringBuilder sb = new StringBuilder();
String line = null;
while ((line = reader.readLine()) != null) {
sb.append(line).append("\n");
}
String ret = sb.toString();
System.out.println(ret);
//解析方式二
byte[] bytes = new byte[1024];
int length = 0;
StringBuilder result = new StringBuilder("");
while(-1 != (length = inputStream.read(bytes))) {
result.append(new String(bytes, 0, length));
}
System.out.println(result);
}else {
System.out.println("请求失败,返回" + response.getStatusLine().getStatusCode());
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if(inputStream != null){
try {
inputStream.close();
}catch (Exception e){
}
}
if(reader != null){
try {
reader.close();
}catch (Exception e){
}
}
//关闭池子
/* if(client != null){
try {
client.close();
} catch (IOException e) {
e.printStackTrace();
}
}*/
//释放连接
method.releaseConnection();
}
System.out.println("end..Duration MS:" + (System.currentTimeMillis() - start));
}
重试策略:
加入重试策略:
// 创建httpClient
return builder.setDefaultRequestConfig(config).setRetryHandler(new MyRetryHandle()).build();
/**
* 请求连接池失败重试策略
*/
public class MyRetryHandle implements HttpRequestRetryHandler {
Logger logger = LoggerFactory.getLogger(MyRetryHandle.class);
//请求失败时,进行请求重试
@Override
public boolean retryRequest(IOException e, int i, HttpContext httpContext) {
if (i > 3) {
//重试超过3次,放弃请求
logger.error("retry has more than 3 time, give up request");
return false;
}
if (e instanceof NoHttpResponseException) {
//服务器没有响应,可能是服务器断开了连接,应该重试
logger.error("receive no response from server, retry");
return true;
}
if (e instanceof SSLHandshakeException) {
// SSL握手异常
logger.error("SSL hand shake exception");
return false;
}
if (e instanceof InterruptedIOException) {
//超时
logger.error("InterruptedIOException");
return false;
}
if (e instanceof UnknownHostException) {
// 服务器不可达
logger.error("server host unknown");
return false;
}
if (e instanceof ConnectTimeoutException) {
// 连接超时
logger.error("Connection Time out");
return false;
}
if (e instanceof SSLException) {
logger.error("SSLException");
return false;
}
HttpClientContext context = HttpClientContext.adapt(httpContext);
HttpRequest request = context.getRequest();
if (!(request instanceof HttpEntityEnclosingRequest)) {
//如果请求不是关闭连接的请求
return true;
}
return false;
}
}
三、原理及注意事项
连接池中连接都是在发起请求的时候建立,并且都是长连接
HaoMaiClient.java中的in.close();作用就是将用完的连接释放,下次请求可以复用,这里特别注意的是,如果不使用in.close();而仅仅使用response.close();结果就是连接会被关闭,并且不能被复用,这样就失去了采用连接池的意义。
连接池释放连接的时候,并不会直接对TCP连接的状态有任何改变,只是维护了两个Set,leased和avaliabled,leased代表被占用的连接集合,avaliabled代表可用的连接的集合,释放连接的时候仅仅是将连接从leased中remove掉了,并把连接放到avaliabled集合中
http://hc.apache.org/ 文档以及源码
https://blog.csdn.net/u014133299/article/details/80676147
https://segmentfault.com/a/1190000012343705
https://blog.csdn.net/szwandcj/article/details/51291967