我有一个客户端 - 服务器分层架构,客户端向服务器发出类似RPC的请求。我正在使用Tomcat来托管servlet,并使用Apache HttpClient向它发出请求。
我的代码是这样的:
private static final HttpConnectionManager CONN_MGR = new MultiThreadedHttpConnectionManager();
final GetMethod get = new GetMethod();
final HttpClient httpClient = new HttpClient(CONN_MGR);
get.getParams().setCookiePolicy(CookiePolicy.IGNORE_COOKIES);
get.getParams().setParameter(HttpMethodParams.USER_AGENT, USER_AGENT);
get.setQueryString(encodedParams);
int responseCode;
try {
responseCode = httpClient.executeMethod(get);
} catch (final IOException e) {
...
}
if (responseCode != 200)
throw new Exception(...);
String responseHTML;
try {
responseHTML = get.getResponseBodyAsString(100*1024*1024);
} catch (final IOException e) {
...
}
return responseHTML;
它在轻负载的环境中运行良好,但是当我每秒发出数百个请求时,我开始看到这个 -
Caused by: java.net.BindException: Address already in use
at java.net.PlainSocketImpl.socketBind(Native Method)
at java.net.AbstractPlainSocketImpl.bind(AbstractPlainSocketImpl.java:336)
at java.net.Socket.bind(Socket.java:588)
at java.net.Socket.<init>(Socket.java:387)
at java.net.Socket.<init>(Socket.java:263)
at org.apache.commons.httpclient.protocol.DefaultProtocolSocketFactory.createSocket(DefaultProtocolSocketFactory.java:80)
at org.apache.commons.httpclient.protocol.DefaultProtocolSocketFactory.createSocket(DefaultProtocolSocketFactory.java:122)
at org.apache.commons.httpclient.HttpConnection.open(HttpConnection.java:707)
at org.apache.commons.httpclient.HttpMethodDirector.executeWithRetry(HttpMethodDirector.java:387)
at org.apache.commons.httpclient.HttpMethodDirector.executeMethod(HttpMethodDirector.java:171)
at org.apache.commons.httpclient.HttpClient.executeMethod(HttpClient.java:397)
at org.apache.commons.httpclient.HttpClient.executeMethod(HttpClient.java:323)
有关如何解决此问题的任何想法?我猜这与客户试图重用短暂的客户端端口有关,但为什么会发生这种情况/如何解决? 谢谢!
答案 0 :(得分:2)
可以找到您遇到的问题的非常好的讨论here。在Tomcat端,默认情况下它将使用SO_REUSEADDR选项,这将允许服务器重用TIME_WAIT中的套接字。此外,Apache http客户端默认使用keep-alives,并尝试重用连接。
您的问题似乎是由于未在HttpClient上调用 releaseConnection 引起的。这是连接重复使用所必需的。否则,连接将保持打开,直到垃圾收集器到来并关闭它,或者服务器断开保持活动状态。在这两种情况下,它都不会返回池中。
答案 1 :(得分:1)
每秒有数百个连接,并且不知道你的连接保持打开多长时间,做他们的事情,关闭,并获得回收,我怀疑这只是你将要遇到的问题。您可以做的一件事就是捕获try块中的BindException
,使用它来执行在bind-unsuccessful情况下需要做的任何事情,并将整个调用包装在依赖于while
的循环中指示绑定是否成功的标志。在我的头顶:
boolean hasBound = false;
while (!hasBound) {
try {
hasBound = true;
responseCode = httpClient.executeMethod(get);
} catch (BindException e) {
// do anything you want in the bound-unsuccessful case
} catch (final IOException e) {
...
}
}
更新问题:一个奇怪的问题:MultiThreadedHttpConnectionManager
允许的最大总连接数和每个主机连接数是多少?在你的代码中,那就是:
CONN_MGR.getParams().getDefaultMaxConnectionsPerHost();
CONN_MGR.getParams().getMaxTotalConnections();
答案 2 :(得分:0)
因此,您发出的请求多于允许打开TCP / IP端口的请求。我不做HttpClient,所以我不能详细讨论这个问题,但理论上对于这个特殊问题有三种解决方案:
答案 3 :(得分:0)
所以事实证明问题是其他HttpClient实例之一意外地没有使用我实例化的MultiThreadedHttpConnectionManager,所以我实际上根本没有速率限制。修复此问题修复了抛出的异常。
感谢所有的建议!
答案 4 :(得分:0)
即使我们调用HttpClientUtils.closeQuietly(client);但是在你的代码中,如果试图从HttpResponse实体读取内容,比如InputStream contentStream = HttpResponse.getEntity()。getContent(),那么你应该关闭输入流,然后才能正确关闭HttpClient连接。