如何确保我的HttpClient 4.1不会泄漏套接字?

时间:2011-01-18 12:45:36

标签: java http sockets httpclient

我的服务器使用来自内部Web服务的数据来构建其响应,基于每个请求。我正在使用Apache HttpClient 4.1来发出请求。每个初始请求将导致对Web服务的大约30个请求。其中,4 - 8最终将插入CLOSE_WAIT中的插座,这些插座永远不会被释放。最终这些卡住的套接字超过了我的ulimit,我的进程耗尽了文件描述符。

我不想只提高我的ulimit(1024),因为这只会掩盖问题。

我转移到HttpClient的原因是java.net.HttpUrlConnection的行为方式相同。

我已尝试根据请求移动到SingleClientConnManager,并在其上调用client.getConnectionManager()。shutdown(),但套接字仍然卡住了。

我是否应该尝试解决此问题,以便在没有正在运行的请求时最终得到0个开放套接字,或者我应该专注于请求持久性和池化?

为清楚起见,我提供了一些可能相关的细节:

操作系统:Ubuntu 10.10

JRE:1.6.0_22

语言:Scala 2.8

示例代码:

val cleaner = Executors.newScheduledThreadPool(1) 
private val client = {
    val ssl_ctx = SSLContext.getInstance("TLS")
    val managers = Array[TrustManager](TrustingTrustManager)
    ssl_ctx.init(null, managers, new java.security.SecureRandom())
    val sslSf = new org.apache.http.conn.ssl.SSLSocketFactory(ssl_ctx, SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER)
    val schemeRegistry = new SchemeRegistry()
    schemeRegistry.register(new Scheme("https", 443, sslSf))
    val connection = new ThreadSafeClientConnManager(schemeRegistry)
    object clean extends Runnable{ 
        override def run = {
            connection.closeExpiredConnections
            connection.closeIdleConnections(30, SECONDS)
        }
    }
    cleaner.scheduleAtFixedRate(clean,10,10,SECONDS)
    val httpClient = new DefaultHttpClient(connection)
    httpClient.getCredentialsProvider().setCredentials(new AuthScope(AuthScope.ANY), new UsernamePasswordCredentials(username,password))
    httpClient
}
val get = new HttpGet(uri)
val entity = client.execute(get).getEntity
val stream = entity.getContent
val justForTheExample = IOUtils.toString(stream)
stream.close()

测试:netstat -a | grep {myInternalWebServiceName} | grep CLOSE_WAIT

(列出我的进程处于CLOSE_WAIT状态的套接字)

发表评论讨论:

此代码现在演示了正确的用法。

3 个答案:

答案 0 :(得分:9)

需要主动逐出连接池中的过期/空闲连接,因为阻塞I / O模型连接无法响应I / O事件,除非正在读取/写入它们至。有关详细信息,请参阅

http://hc.apache.org/httpcomponents-client-dev/tutorial/html/connmgmt.html#d4e631

答案 1 :(得分:2)

我将oleg的答案标记为正确,因为它突出了关于HttpClient连接池的一个重要用法点。

回答我特定的原始问题,“我应该尝试解决0个未使用的套接字还是尝试最大化池化?”

现在池化解决方案已经到位且工作正常,应用程序吞吐量增加了约150%。我将此归因于不必重新协商SSL和多次握手,而是根据HTTP 1.1重用持久连接。

绝对有必要按预期使用池,而不是试图在每个请求等之后调用ThreadSafeClientConnManager.shutdown()。另一方面,如果你调用任意主机而不是按照我的方式重用路由,你可能很容易发现有必要做那种hackery,因为JVM可能会让你对CLOSE_WAIT指定套接字的长寿命感到惊讶。你不经常收集垃圾。

答案 2 :(得分:2)

我遇到了同样的问题,并使用此处的建议解决了问题:here。作者接触了一些TCP基础知识:

  

当TCP连接即将关闭时,双方将协商其最终确定。把它想象成以文明的方式违约。双方签署了这份文件,这一切都很好。在极客谈话中,这是通过FIN / ACK消息完成的。甲方发送FIN消息表明它要关闭套接字。乙方发送一条确认收到该消息的ACK并正在考虑该请求。然后乙方清理并向甲方发送一个FIN。甲方回应确认,每个人都走开了。

     

问题来了   当B没有发送它的FIN。 A有点等待它。它有   发起了最后的序列,正在等待另一方   做同样的事。

然后他提到RFC 2616, 14.10建议设置一个http标头来解决这个问题:

postMethod.addHeader("Connection", "close");

老实说,我真的不知道设置此标头的含义。但它确实阻止了CLOSE_WAIT在我的单元测试中发生。