6

我们经常使用 HttpURLConnection API 向同一个提供者调用 REST API(一种聚合用例)。我们希望保持 5 个连接池始终对提供者主机开放(始终使用相同的 IP)。

什么是正确的解决方案?这是我们尝试过的:


System.setProperty("http.maxConnections", 5);  // set globally only once
...
// everytime we need a connection, we use the following
HttpURLConnection conn = (HttpURLConnection) (new URL(url)).openConnection();
conn.setRequestMethod("GET");
conn.setDoInput(true);
conn.setDoOutput(false);
conn.setUseCaches(true);
...
BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
...

此时我们读取输入流,直到 BufferedReader 不再返回字节。如果我们想重用与提供者的底层连接,那么在那之后我们该怎么做?我们的印象是,如果输入流被完全读取,连接就会被添加回池中。

它已经以这种方式工作了几个星期,但今天它停止工作产生这个异常:java.net.SocketException: Too many open files

我们发现许多处于 CLOSE_WAIT 状态的套接字(通过运行lsof): java 1814 root 97u IPv6 844702 TCP colinux:58517->123.123.254.205:www (CLOSE_WAIT)

conn.getInputStream().close() 或 conn.disconnect() 不会完全关闭连接并将其从池中删除吗?

4

3 回答 3

5

我们在 Java 5 上也遇到了这个问题,我们的解决方案是切换到带有池连接管理器的 Apache HttpClient。

Sun 的 HTTP URL 处理程序的 keepalive 实现有很多错误。没有关闭空闲连接的维护线程。

keepalive 的另一个更大问题是您需要删除响应。否则,连接也将成为孤立的。大多数人没有正确处理错误流。有关如何正确阅读错误响应的示例,请参阅我对这个问题的回答,

HttpURLConnection.getResponseCode() 在第二次调用时返回 -1

于 2009-12-20T20:40:38.727 回答
4

disown 引用的参考文献确实有帮助。

我们知道 Apache HttpClient 更好,但这需要另一个 jar,我们可能会在 applet 中使用此代码。

打电话HttpURLConnection.connect()是不必要的。我不确定它是否会阻止连接重用,但我们把它拿出来了。关闭流是安全的,但调用disconnect()连接会阻止重用。此外,设置也有sun.net.http.errorstream.enableBuffering=true帮助。

这是我们最终使用的:


System.setProperty("http.maxConnections", String.valueOf(CONST.CONNECTION_LIMIT));
System.setProperty("sun.net.http.errorstream.enableBuffering", "true");

...

int responseCode = -1;
HttpURLConnection conn = null;
BufferedReader reader = null;
try {
 conn = (HttpURLConnection) (new URL(url)).openConnection();
 conn.setRequestProperty("Accept-Encoding", "gzip");

 // this blocks until the connection responds
 InputStream in = new GZIPInputStream(conn.getInputStream());

 reader = new BufferedReader(new InputStreamReader(in));
 StringBuffer sb = new StringBuffer();
 char[] buff = new char[CONST.HTTP_BUFFER_SIZE];
 int cnt;

 while((cnt = reader.read(buff)) > 0) sb.append(buff, 0, cnt);

 reader.close();

 responseCode = conn.getResponseCode();
 if(responseCode != HttpURLConnection.HTTP_OK) throw new IOException("abnormal HTTP response code:"+responseCode);

 return sb.toString();

} catch(IOException e) {
    // consume error stream, otherwise, connection won't be reused
    if(conn != null) {
     try {
         InputStream in = ((HttpURLConnection)conn).getErrorStream();
         in.close();
         if(reader != null) reader.close();
     } catch(IOException ex) {
         log.fine(ex);
     }
    }

    // log exception    
    String rc = (responseCode == -1) ? "unknown" : ""+responseCode;
    log.severe("Error for HttpUtil.httpGet("+url+")\nServer returned an HTTP response code of '"+rc+"'");
    log.severe(e);
}
于 2010-01-06T00:07:37.127 回答
4

这里

当前实现不缓冲响应正文。这意味着应用程序必须完成读取响应正文或调用 close() 以放弃响应正文的其余部分,以便重用该连接。此外,当前的实现在清理连接时不会尝试块读取,这意味着如果整个响应体不可用,则不会重用连接。

我读到这个好像你的解决方案应该可以工作,但是你也可以自由地调用 close 并且连接仍然会被重用。

于 2009-12-20T20:33:12.537 回答