我有SwingWorker
获取网址列表(可以是几百个)。在doInBackground()
方法中,它循环显示这些网址,每次CloseableHttpClient
创建,由PoolingHttpClientConnectionManager
和HttpGet
管理。
然后,client
将执行httpGet
并将内容(图像)写入文件(如果可能!并非所有URL都有效,有些URL返回404
)。
这适用于大约100个请求,直到达到connectionManager的maxTotal
(或defaultMaxPerRoute
)。然后一切都停止,客户端的执行停止,没有抛出异常。
所以我想,我将maxTotal
和defaultMaxPerRoute
设置为1000
并将其检出。当我尝试下载1500张图片时,它可以工作,但它只是感觉错了!我想重用客户端,而不是在池中有1000个客户端。
套接字或ConnectionTimeouts不起作用,调试不会告诉我发生了什么,并且在HttpClients
没有工作的情况下创建新的PoolingHttpClientConnectionManager
。关闭客户端和/或结果也不起作用。
我应该如何管理客户端或设置池以确保我的SwingWorker
甚至可以下载数千张图片?
我会尝试将SwingWorker
代码分解为重要部分:
(几乎忘了,它实际上是一个循环的对象列表,每个对象有3个URL)
// this is how I init the connectionManager (outside the swing worker)
this.connectionManager = new PoolingHttpClientConnectionManager();
this.connectionManager.setMaxTotal(100);
this.connectionManager.setDefaultMaxPerRoute(100);
// this is where the images are downloaded in the swing worker
@Override
protected Void doInBackground() throws Exception{
for(MyUrls myUrls : myUrlsList){
client = HttpClients.custom().setConnectionManager(connectionManager).build();
for(MyImage image : myUrls.getImageList()){
File outputFile = null;
HttpEntity entity = null;
switch(image.getImageSize()){
case 1:
HttpGet httpGet = new HttpGet(image.getUrl()));
httpGet.setConfig(RequestConfig.custom().setSocketTimeout(1000).setConnectTimeout(1000).build()); // doesn't change anything
response = client.execute(httpGet);
if(response.getStatusLine().getStatusCode() >= 200 && response.getStatusLine().getStatusCode() < 300){
entity = response.getEntity();
if(entity != null){
String contentType = entity.getContentType().getValue();
String extension = "." + (contentType.contains("/") ? contentType.substring(contentType.indexOf("/") + 1) : "jpg");
outputFile = new File(image.getName()+extension);
}
}else{
System.err.println("download of "+image.getName()+extension+" failed: " + response.getStatusLine().getStatusCode());
}
break;
case 2:
// the other cases are pretty much the same
}
if(entity != null && outputFile != null){
try(InputStream inputStream = entity.getContent(); FileOutputStream outputStream = new FileOutputStream(outputFile)){
byte[] buffer = new byte[1024];
int bytesRead;
while((bytesRead = inputStream.read(buffer)) != -1){
outputStream.write(buffer, 0, bytesRead);
}
}
}
}
}
}
答案 0 :(得分:4)
您可能正在泄漏连接,因为您只有在可以正确转储到文件输出流的OK响应(在200到300范围内)时才能正确处理它们。
处置实体(&#34;内容&#34; resposne)是释放连接对象的唯一方法。
为此,您可以调用EntityUtils.consume(responseEntity)
)和/或关闭响应对象(调用response.close()
)和/或关闭或读取实体的流(调用{{ 1}})在每个请求/响应周期的stream.close()
子句中。
您的代码实际上消耗了响应的流,但仅限于某些情况(主要是:请求成功时),因此正确使用API,但不是其他(例如404响应),从不消费也不释放。
您也可以使用finally
API变体,它将为您提供所有这些 - 我建议您使用它。
如果你不总是这样做,HTTPClient认为连接是活动的,它永远不会被释放,也不会被重用。在某些时候,您将最终得到一个空连接池或系统错误(例如打开太多文件)。
请注意,它不仅适用于200 OK响应,而且每次通话都是如此(即使404通常有响应机构,如果您不使用该机身,则连接将不会释放)。
我建议您参考&#34;基础知识&#34;用户指南的一部分:https://hc.apache.org/httpcomponents-client-ga/tutorial/html/fundamentals.html
以下是最简单形式的请求执行过程示例:
ResponseHandler
非常重要的部分是最后关闭中的close()。
1.1.5。确保释放低水平资源
为了确保正确释放系统资源,必须关闭与实体关联的内容流或响应本身
并且:
1.1.6。消费实体内容
使用实体内容的推荐方法是使用HttpEntity#getContent()或HttpEntity#writeTo(OutputStream)方法。 HttpClient还附带了EntityUtils类,它公开了几种静态方法,以便更容易地从实体中读取内容或信息。可以使用此类中的方法检索字符串/字节数组中的整个内容主体,而不是直接读取java.io.InputStream。但是,强烈建议不要使用EntityUtils,除非响应实体来自受信任的HTTP服务器,并且已知其长度有限。
关于响应处理程序变体:
1.1.8。响应处理程序
处理响应的最简单和最方便的方法是使用ResponseHandler接口,该接口包含handleResponse(HttpResponse响应)方法。这种方法完全避免了用户担心连接管理。
您还可以阅读: Why did the author use EntityUtils.consume(httpEntity);?
拥有多线程连接管理器的关键是,您可以拥有一个(且只有一个)客户端,并为您的整个应用程序共享它。您不需要为每个请求构建一个客户端,并让它们共享一个连接管理器。虽然你可以。
如果您使用缓存,cookie,身份验证,单个客户端可以提供帮助...在为每个请求创建新客户端时,无法使用。
1.2.1。 HttpClient线程安全性
HttpClient实现应该是线程安全的。建议将此类的同一实例重用于多个请求执行。