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

lsof(を実行することにより)次 のようなCLOSE_WAIT状態のソケットが多数見つかりました。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

この問題はJava5でも発生しました。解決策は、プールされた接続マネージャーを使用してApacheHttpClientに切り替えることです。

HTTP用のSunのURLハンドラーのキープアライブ実装は非常にバグがあります。アイドル状態の接続を閉じるためのメンテナンススレッドはありません。

キープアライブのもう1つの大きな問題は、応答を削除する必要があることです。そうしないと、接続も孤立します。ほとんどの人はエラーストリームを正しく処理しません。エラー応答を正しく読み取る方法の例については、この質問に対する私の回答を参照してください。

HttpURLConnection.getResponseCode()は、2回目の呼び出しで-1を返します

于 2009-12-20T20:40:38.727 に答える
4

ここから:

現在の実装では、応答本文はバッファリングされません。つまり、その接続を再利用するには、アプリケーションが応答本文の読み取りを終了するか、close()を呼び出して、応答本文の残りの部分を破棄する必要があります。さらに、現在の実装では、接続をクリーンアップするときにブロック読み取りを試行しません。つまり、応答本体全体が使用できない場合、接続は再利用されません。

私はこれをあなたの解決策が機能するはずであるかのように読みましたが、あなたも自由に電話をかけて閉じることができ、接続はまだ再利用されます。

于 2009-12-20T20:33:12.537 に答える
4

disownによって引用された参照は、本当に助けになったものでした。

Apache HttpClientの方が優れていることはわかっていますが、それには別のjarが必要であり、このコードをアプレットで使用する可能性があります。

電話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 に答える