我们正在使用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()是否会完全关闭连接并将其从池中删除?
答案 0 :(得分:4)
来自here:
当前实现不会缓冲响应正文。这意味着应用程序必须完成读取响应主体或调用close()以放弃响应主体的其余部分,以便重用该连接。此外,当前实现不会在清理连接时尝试块读取,这意味着如果整个响应主体不可用,则连接将不会被重用。
我读到这个,好像你的解决方案应该有效,但是你也可以自由地调用close,连接仍然会被重用。
答案 1 :(得分:4)
我们在Java 5上也遇到了这个问题,我们的解决方案是使用池化连接管理器切换到Apache HttpClient。
Sun的HTTP URL处理程序的keepalive实现非常错误。没有维护线程来关闭空闲连接。
keepalive的另一个更大问题是您需要删除回复。否则,连接也将成为孤立。大多数人不能正确处理错误流。有关如何正确读取错误响应的示例,请参阅我对此问题的回答
HttpURLConnection.getResponseCode() returns -1 on second invocation
答案 2 :(得分:3)
被提及引用的reference才是真正有用的。
我们知道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);
}