5

非常に大規模なプロジェクトの開発の過程で、多くの単体テストを蓄積してきました。これらのテストの多くは、サーバーの起動、これらのサーバーへの接続、およびサーバーとクライアントの終了を、通常は同じプロセスで行います。

ただし、これらのテストは、「アドレス 127.0.0.1:(ポート) のバインドに失敗しました」というエラーでランダムに失敗します。テストを再実行すると、通常、エラーは消えます。

さて、これは私たちのテストの問題だと思いましたが、Clojure で小さなテストを書くことにしました。

(ns test
  (:import [java.net Socket ServerSocket]))

(dotimes [n 10000] ; Run the test ten thousand times
  (let [server (ServerSocket. 10000) ; Start a server on port 10000
        client (Socket. "localhost" 10000) ; Start a client on port 10000
        p (.getLocalPort client)] ; Get the local port of the client
    (.close client) ; Close the client
    (.close server) ; Close the server
    (println "n = " n) ; Debug
    (println "p = " p) ; Debug
    (println "client = " client) ; Debug
    (println "server = " server) ; Debug
    (let [server (ServerSocket. p)] ; Start a server on the local port of the client we just closed
      (.close server) ; Close the server
      (println "client = " client) ; Debug
      (println "server = " server) ; Debug
    ))
  )

例外は、2 番目のサーバーを起動する行にランダムに表示されます。そのポートのクライアントがすでに閉じられているにもかかわらず、Java がローカル ポートを保持しているように見えます。

では、私の質問: Java がこれを行っているのはなぜでしょうか。

EDIT :誰かが、ソケットの reuseAddr を true に設定することを提案しました。私はこれを行いましたが、何も変わっていないので、以下のコードです。

(ns test
  (:import [java.net Socket ServerSocket InetSocketAddress]))

(dotimes [n 10000] ; Run the test ten thousand times
  (let [server (ServerSocket. )] ; Create a server socket
    (. server (setReuseAddress true)) ; Set the socket to reuse address
    (. server (bind (InetSocketAddress. 10000))) ; Bind the socket
    (let  [client (Socket. "localhost" 10000) ; Start a client on port 10000
           p (.getLocalPort client)] ; Get the client's local port
      (.close client) ; Close the client
      (.close server) ; Close the server
;      (. Thread (sleep 1000)) ; A sleep for testing
      (println "n = " n) ; Debug
      (println "p = " p) ; Debug
      (println "client = " client) ; Debug
      (println "server = " server) ; Debug
      (let [server (ServerSocket. )] ; Create a server socket
        (. server (setReuseAddress true)) ; Set the socket to reuse address
        (. server (bind (InetSocketAddress. p))) ; Bind the socket to the local port of the client we just had
        (.close server) ; Close the server
        (println "client = " client) ; Debug
        (println "server = " server) ; Debug
      )))
  )

また、10 ミリ秒または 100 ミリ秒のスリープでも問題が回避されないことにも気付きました。ただし、1000msec は (これまでのところ) それを防ぐことができました。

編集 2 : 誰かが私を SO_LINGER に入れましたが、ServerSockets でそれを設定する方法が見つかりません。誰でもそれについて何か考えがありますか?

EDIT 3 : SO_LINGER がデフォルトで無効になっていることが判明しました。他に何を見ることができますか?

更新: 10,000 程度のポートの範囲で動的ポート割り当てを使用することで、問題はほとんど解決されました。しかし、私はまだ人々が何を思い付くことができるかを見たいと思っています.

4

3 に答える 3

3

私は (あまりにも) Clojure 構文を使用していませんが、呼び出す必要がありますsocket.setReuseAddr(true)。これにより、TIME_WAIT 状態のソケットがある場合でも、プログラムはポートを再利用できます。

于 2012-08-27T14:45:21.373 に答える
2

テスト自体は無効です。この動作をテストすることは無意味であり、必要なアプリケーションの動作とは何の関係もありません。TCPスタックのコーナー条件を実行しているだけであり、アプリケーションが依存しようとすることはありません。アウトバウンド接続ポートであったポートでリスニングソケットを開くことは、TIME_WAITが原因でまったく成功しないか、どちらの端が最初にクローズを発行したかが不確実であるため、せいぜい半分の時間で成功すると予想されます

テストを削除します。残りの部分も有用なことは何もしません、

于 2012-08-27T22:05:04.353 に答える
1

setReuseAddress(true)サーバーソケットを試すことができます。

同じポートの別のソケットが閉じた後に TIME_WAIT 状態にある場合、このフラグはソケットがポートにバインドできるようにします。

于 2012-08-27T14:47:04.167 に答える