Apache HttpClient4.5(二)

本文介绍了HttpClient中两种HTTP连接管理器:BasicHttpClientConnectionManager和PoolingHttpClientConnectionManager。前者仅能供单一线程使用,后者则通过连接池支持多线程并发访问。文章还探讨了连接管理器的关闭方式以及如何避免无效连接。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转载自:http://www.yeetrack.com/?p=782

       Http连接是复杂,有状态的,线程不安全的对象,所以它必须被妥善管理。一个Http连接在同一时间只能被一个线程访问。HttpClient使用一个叫做Http连接管理器的特殊实体类来管理Http连接,这个实体类要实现HttpClientConnectionManager接口。
       Http连接管理器的作用是在新建http连接时,作为工厂类;管理持久http连接的生命周期;同步持久连接(确保线程安全,即一个http连接同一时间只能被一个线程访问)。

       BasicHttpClientConnectionManager是个简单的连接管理器,它一次只能管理一个连接。尽管这个类是线程安全的,它在同一时间也只能被一个线程使用。BasicHttpClientConnectionManager会尽量重用旧的连接来发送后续的请求,并且使用相同的路由。如果后续请求的路由和旧连接中的路由不匹配,BasicHttpClientConnectionManager就会关闭当前连接,使用请求中的路由重新建立连接。如果当前的连接正在被占用,会抛出java.lang.IllegalStateException异常。
static void req(CloseableHttpClient httpClient) {
    HttpGet httpGet = new HttpGet("http://www.baidu.com");
    CloseableHttpResponse response = null;
    try {
        System.out.println(Thread.currentThread().getName() + " ---0");
        response = httpClient.execute(httpGet);
        TimeUnit.SECONDS.sleep(5);
        System.out.println(Thread.currentThread().getName() + " ---1");
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        try {
            if (response != null) response.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
BasicHttpClientConnectionManager connMrg = new BasicHttpClientConnectionManager();

CloseableHttpClient httpClient = HttpClients.custom()
        .setConnectionManager(connMrg)
        .build();

Thread a = new Thread(() -> req(httpClient));
a.setName("a");
Thread b = new Thread(() -> req(httpClient));
b.setName("b");

a.start();
b.start();
       运行结果:
a ---0
b ---0
java.lang.IllegalStateException: Connection is still allocated
…

    

       PoolingHttpClientConnectionManager是个更复杂的类,它管理着连接池,可以同时为很多线程提供http连接请求。Connections are pooled on a per route basis.当请求一个新的连接时,如果连接池有有可用的持久连接,连接管理器就会使用其中的一个,而不是再创建一个新的连接。

       该ConnectionManager是HttpClient默认的连接管理器,即不设置setConnectionManager()时HttpClient使用的是PoolingHttpClientConnectionManager,如源码:
@SuppressWarnings("resource")
final PoolingHttpClientConnectionManager poolingmgr = new PoolingHttpClientConnectionManager(
        RegistryBuilder.<ConnectionSocketFactory>create()
            .register("http", PlainConnectionSocketFactory.getSocketFactory())
            .register("https", sslSocketFactoryCopy)
            .build(),
        null,
        null,
        dnsResolver,
        connTimeToLive,
        connTimeToLiveTimeUnit != null ? connTimeToLiveTimeUnit : TimeUnit.MILLISECONDS);
if (defaultSocketConfig != null) {
    poolingmgr.setDefaultSocketConfig(defaultSocketConfig);
}
if (defaultConnectionConfig != null) {
    poolingmgr.setDefaultConnectionConfig(defaultConnectionConfig);
}
if (systemProperties) {
    String s = System.getProperty("http.keepAlive", "true");
    if ("true".equalsIgnoreCase(s)) {
        s = System.getProperty("http.maxConnections", "5");
        final int max = Integer.parseInt(s);
        poolingmgr.setDefaultMaxPerRoute(max);
        poolingmgr.setMaxTotal(2 * max);
    }
}
if (maxConnTotal > 0) {
    poolingmgr.setMaxTotal(maxConnTotal);
}
if (maxConnPerRoute > 0) {
    poolingmgr.setDefaultMaxPerRoute(maxConnPerRoute);
}
       当然可以根据具体的需求自定义这个管理器:
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
// 将最大连接数增加到200
cm.setMaxTotal(200);
// 将每个路由基础的连接增加到20
cm.setDefaultMaxPerRoute(20);
//将目标主机的最大连接数增加到50
HttpHost localhost = new HttpHost("www.yeetrack.com", 80);
cm.setMaxPerRoute(new HttpRoute(localhost), 50);

CloseableHttpClient httpClient = HttpClients.custom()
        .setConnectionManager(cm)
        .build();


       当一个HttpClient的实例不在使用,或者已经脱离它的作用范围,我们需要关掉它的连接管理器,来关闭掉所有的连接,释放掉这些连接占用的系统资源。

CloseableHttpClient httpClient = <...>
httpClient.close();

       当连接被管理器收回后,这个连接仍然存活,但是却无法监控socket的状态,也无法对I/O事件做出反馈。如果连接被服务器端关闭了,客户端监测不到连接的状态变化,也就无法根据连接状态的变化,关闭本地的socket。
       HttpClient为了缓解这一问题造成的影响,会在使用某个连接前,监测这个连接是否已经过时,如果服务器端关闭了连接,那么连接就会失效。这种过时检查并不是100%有效,并且会给每个请求增加10到30毫秒额外开销。唯一一个可行的解决办法,是建立一个监控线程,来专门回收由于长时间不活动而被判定为失效的连接。这个监控线程可以周期性的调用ClientConnectionManager类的closeExpiredConnections()方法来关闭过期的连接,回收连接池中被关闭的连接。它也可以选择性的调用ClientConnectionManager类的closeIdleConnections()方法来关闭一段时间内不活动的连接。
public static class IdleConnectionMonitorThread extends Thread {

	private final HttpClientConnectionManager connMgr;
	private volatile boolean shutdown;

	public IdleConnectionMonitorThread(HttpClientConnectionManager 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) {
			// terminate
		}
	}

	public void shutdown() {
		shutdown = true;
		synchronized (this) {
			notifyAll();
		}
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值