文章16 | 阅读 6556 | 点赞0
写了HttpClient工具类后,有人一直在问我怎么启用http连接池,其实我没考虑过这个问题
。不过闲暇的时候,突然间想起了这个问题,就想把这个问题搞一搞。
之前没用过,但是理解起来应该不算难。作为一个Coder,就算没用过http连接池,但是肯定用过数据库连接池。二者的功能是类似的,就是把建立链接和断开链接的时间节省下来。众所周知,http建立链接是需要3次握手,了解的深入一些的,还知道断开链接需要4次握手。这些操作都是自动完成的,如果我们把这些建立和断开链接的时间节省掉,对于大批量的http请求(如爬虫)是很有用的。
关于HttpClient如何启用连接池,可以参考这篇文章:http://www.cnblogs.com/likaitai/p/5431246.html。介绍了如何通过连接池获取链接,以及在不用连接时,如果处理不会导致链接直接关闭。
说了这么多,下面切入正题:HttpClient工具类如何启用http连接池?其实只需要修改创建链接方法即可:
之前在工具类的核心方法execute方法里获取httpclient对象,调用的是create(String url)方法。返回的是默认的一个HttpClient对象。现在要启用连接池,必须修改此方法。配置连接池的类是HCB,而execute方法接受的参数是HttpConfig参数,所以,首先要在HttpConfig里添加一个HCB对象。然后修改create方法。具体如下:
/**
* HCB对象,用于创建HttpClient对象
*/
private HCB hcb;
public HCB hcb() {
return hcb;
}
/**
* HCB对象,用于自动从连接池中获得HttpClient对象<br>
* <font color="red"><b>请调用hcb.pool方法设置连接池</b></font>
* @throws HttpProcessException
*/
public HttpConfig hcb(HCB hcb) throws HttpProcessException {
this.hcb = hcb;
return this;
}
/**
* 判定是否开启连接池、及url是http还是https <br>
* 如果已开启连接池,则自动调用build方法,从连接池中获取client对象<br>
* 否则,直接返回相应的默认client对象<br>
*
* @throws HttpProcessException
*/
private static void create(HttpConfig config) throws HttpProcessException {
if(config.hcb()!=null && config.hcb().isSetPool){ //如果设置了hcb对象,且配置了连接池,则直接从连接池取
if(config.url().toLowerCase().startsWith("https://")){
config.client(config.hcb().ssl().build());
}else{
config.client(config.hcb().build());
}
}else{
if(config.client()==null){//如果为空,设为默认client对象
if(config.url().toLowerCase().startsWith("https://")){
config.client(client4HTTPS);
}else{
config.client(client4HTTP);
}
}
}
}
至于关闭方面,fmt2String以及fmt2Stream方法中,在EntityUtils.toString和EntityUtils.consume方法中已经close了instream,释放了资源。最后调用close(HttpClient)即执行client.close()方法。这样就不会直接关闭链接了,会被连接池自动回收再次使用。
最后分享一个测试类,分组测试Get请求、Down操作,在开启和关闭Http线程池完成请求的耗时情况。代码如下:
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.apache.http.Header;
import com.tgb.ccl.http.common.HttpConfig;
import com.tgb.ccl.http.common.HttpHeader;
import com.tgb.ccl.http.exception.HttpProcessException;
import com.tgb.ccl.http.httpclient.HttpClientUtil;
import com.tgb.ccl.http.httpclient.builder.HCB;
/**
* 测试启用http连接池
*
* @author arron
* @date 2016年11月7日 下午1:08:07
* @version 1.0
*/
public class TestHttpPool {
// 设置header信息
private static final Header[] headers = HttpHeader.custom().userAgent("Mozilla/5.0").from("http://blog.csdn.net/newest.html").build();
// URL列表数组,GET请求
private static final String[] urls = {
"http://blog.csdn.net/xiaoxian8023/article/details/49883113",
"http://blog.csdn.net/xiaoxian8023/article/details/49909359",
"http://blog.csdn.net/xiaoxian8023/article/details/49910127",
"http://blog.csdn.net/xiaoxian8023/article/details/49910885",
"http://blog.csdn.net/xiaoxian8023/article/details/51606865",
};
// 图片URL列表数组,Down操作
private static final String[] imgurls ={
"http://ss.bdimg.com/static/superman/img/logo/logo_white_fe6da1ec.png",
"https://scontent-hkg3-1.xx.fbcdn.net/hphotos-xaf1/t39.2365-6/11057093_824152007634067_1766252919_n.png"
};
private static StringBuffer buf1=new StringBuffer();
private static StringBuffer buf2=new StringBuffer();
//多线程get请求
public static void testMultiGet(HttpConfig cfg, int count) throws HttpProcessException{
try {
int pagecount = urls.length;
ExecutorService executors = Executors.newFixedThreadPool(pagecount);
CountDownLatch countDownLatch = new CountDownLatch(count);
//启动线程抓取
for(int i = 0; i< count;i++){
executors.execute(new GetRunnable(countDownLatch,cfg.headers(headers).url(urls[i%pagecount])));
}
countDownLatch.await();
executors.shutdown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//多线程下载
public static void testMultiDown(HttpConfig cfg, int count) throws HttpProcessException{
try {
int pagecount = imgurls.length;
ExecutorService executors = Executors.newFixedThreadPool(pagecount);
CountDownLatch countDownLatch = new CountDownLatch(count);
//启动线程抓取
for(int i = 0; i< count;i++){
executors.execute(new GetRunnable(countDownLatch, cfg.url(imgurls[i%2]), new FileOutputStream(new File("d://aaa//"+(i+1)+".png"))));
}
countDownLatch.await();
executors.shutdown();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
static class GetRunnable implements Runnable {
private CountDownLatch countDownLatch;
private HttpConfig config = null;
private FileOutputStream out = null;
public GetRunnable(CountDownLatch countDownLatch,HttpConfig config){
this(countDownLatch, config, null);
}
public GetRunnable(CountDownLatch countDownLatch,HttpConfig config,FileOutputStream out){
this.countDownLatch = countDownLatch;
this.config = config;
this.out = out;
}
@Override
public void run() {
try {
config.out(out);
if(config.out()==null){
String response = null;
response = HttpClientUtil.get(config);
System.out.println(Thread.currentThread().getName()+"--获取内容长度:"+response.length());
response = null;
}else{
HttpClientUtil.down(config);
try {
config.out().flush();
config.out().close();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"--下载完毕");
}
} catch (HttpProcessException e) {
e.printStackTrace();
} finally {
countDownLatch.countDown();
}
}
}
/**
* 测试不启用http连接池,get100次,down20次的执行时间
* @throws HttpProcessException
*/
private static void testNoPool(int getCount, int downCount) throws HttpProcessException {
long start = System.currentTimeMillis();
if(getCount>0){
HttpConfig cfg1 = HttpConfig.custom().client(HCB.custom().build()).headers(headers);
testMultiGet(cfg1, getCount);
}
if(downCount>0){
HttpConfig cfg2 = HttpConfig.custom().client(HCB.custom().build());
testMultiDown(cfg2, downCount);
}
System.out.println("-----所有线程已完成!------");
long end = System.currentTimeMillis();
System.out.println("总耗时(毫秒): -> " + (end - start));
buf1.append("\t").append((end-start));
}
/**
* 测试启用http连接池,get100次,down20次的执行时间
* @throws HttpProcessException
*/
private static void testByPool(int getCount, int downCount) throws HttpProcessException {
long start = System.currentTimeMillis();
HCB hcb= HCB.custom().timeout(10000).pool(10, 10).ssl();
if(getCount>0){
HttpConfig cfg3 = HttpConfig.custom().hcb(hcb);
testMultiGet(cfg3, getCount);
}
if(downCount>0){
HttpConfig cfg4 = HttpConfig.custom().hcb(hcb);
testMultiDown(cfg4, downCount);
}
System.out.println("-----所有线程已完成!------");
long end = System.currentTimeMillis();
System.out.println("总耗时(毫秒): -> " + (end - start));
buf2.append("\t").append((end-start));
}
public static void main(String[] args) throws Exception {
File file = new File("d://aaa");
if(!file.exists() && file.isDirectory()){
file.mkdir();
}
//-------------------------------------------
// 以下2个方法
// 分别测试 (get次数,down次数)
// {100,0},{200,0},{500,0},{1000,0}
// {0,10},{0,20},{0,50},{0,100}
// {100,10},{200,20},{500,50},{1000,100}
//-------------------------------------------
int[][] times1 = {{100,0} ,{ 200,0 },{ 500,0 },{ 1000,0}};
int[][] times2 = {{0,10},{0,20},{0,50},{0,100}};
int[][] times3 = {{100,10},{200,20},{500,50},{1000,100}};
List<int[][]> list = Arrays.asList(times1,times2,times3);
int n=5;
int t=0;
//测试未启用http连接池,
for (int[][] time : list) {
buf1.append("\n");
for (int i = 0; i < time.length; i++) {
for (int j = 0; j < n; j++) {
testNoPool(time[i][0],time[i][1]);
Thread.sleep(100);
System.gc();
Thread.sleep(100);
}
buf1.append("\n");
}
}
t=0;
//测试启用http连接池
for (int[][] time : list) {
buf2.append("\n");
for (int i = 0; i < time.length; i++) {
for (int j = 0; j < n; j++) {
testByPool(time[i][0],time[i][1]);
Thread.sleep(100);
System.gc();
Thread.sleep(100);
}
buf2.append("\n");
}
t++;
}
//把结果打印到Console中
String[] results1 = buf1.toString().split("\n");
String[] results2 = buf2.toString().split("\n");
for (int i = 0; i < results1.length; i++) {
System.out.println(results1[i]);
System.out.println(results2[i]);
}
}
}
测试结果如下:| 操作 | 请求次数 | 是否启用Pool | 第1次 | 第2次 | 第3次 | 第4次 | 第5次 | 平均时间 | 启用后的效率 |
| GET | 100 | 无 | 4801 | 4807 | 4853 | 4810 | 4522 | 4758.6 | 52.89% |
| 是 | 2146 | 1989 | 2302 | 2355 | 2416 | 2241.6 |
| 200 | 无 | 9222 | 9519 | 9085 | 9196 | 8908 | 9186 | 43.15% |
| 是 | 4992 | 4711 | 4863 | 7001 | 4545 | 5222.4 |
| 500 | 无 | 23727 | 23082 | 23762 | 23427 | 23117 | 23423 | 45.88% |
| 是 | 12146 | 12557 | 12581 | 13121 | 12979 | 12676.8 |
| 1000 | 无 | 47518 | 72445 | 45028 | 52860 | 55764 | 54723 | 48.62% |
| 是 | 25073 | 25067 | 39550 | 26014 | 24888 | 28118.4 |
| Down | 10 | 无 | 10605 | 8273 | 9440 | 7774 | 8740 | 8966.4 | 4.37% |
| 是 | 10415 | 7249 | 7331 | 8554 | 9325 | 8574.8 |
| 20 | 无 | 17306 | 18455 | 18811 | 19294 | 15430 | 17859.2 | 2.67% |
| 是 | 17234 | 16028 | 18152 | 17530 | 17971 | 17383 |
| 50 | 无 | 46873 | 41528 | 51085 | 49900 | 40666 | 46010.4 | -2.93% |
| 是 | 44941 | 50376 | 46759 | 43774 | 50951 | 47360.2 |
| 100 | 无 | 89909 | 93065 | 98297 | 88440 | 92010 | 92344.2 | -0.93% |
| 是 | 91420 | 96388 | 94635 | 88424 | 95152 | 93203.8 |
| GET,Down | 100,10 | 无 | 15913 | 13465 | 14167 | 15607 | 11566 | 14143.6 | 27.42% |
| 是 | 11805 | 10800 | 8322 | 10735 | 9668 | 10266 |
| 200,20 | 无 | 26579 | 28744 | 27791 | 29712 | 32360 | 29037.2 | 25.76% |
| 是 | 20891 | 24664 | 19319 | 19511 | 23394 | 21555.8 |
| 500,50 | 无 | 71462 | 72694 | 74285 | 76207 | 72574 | 73444.4 | 13.46% |
| 是 | 57137 | 75860 | 63288 | 62309 | 59192 | 63557.2 |
| 1,000,100 | 无 | 147264 | 149527 | 143251 | 143865 | 139723 | 144726 | 16.14% |
| 是 | 118305 | 124517 | 122511 | 116823 | 124673 | 121365.8 |
| 测试使用时间(不含暂停和GC时间)、平均效率 | 79.1789833 | 23.04% |
通过测试结果可以看出来,Get请求效果明显,启用后性能要提升50%左右。而Down操作,则反而有所下降。这是为什么呢?其实很简单。连接池是节省了握手的次数,但是握手所消耗的时间,跟一个Down操作相比,肯定要小很多。所以Down操作消耗的时间已经超过了节省握手的时间了,也就起不到优化的作用了,所以要根据实际情况使用连接池。
最新的完整代码请到GitHub上进行下载:https://github.com/Arronlong/httpclientUtil 。
)
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://longxuan.blog.csdn.net/article/details/53064210
内容来源于网络,如有侵权,请联系作者删除!