5

在一个非常大的项目的开发过程中,我们积累了大量的单元测试。很多这些测试通常在同一个过程中启动服务器、连接到这些服务器并关闭服务器和客户端。

但是,这些测试随机失败,并显示“无法绑定地址 127.0.0.1:(port)”。重新运行测试时,错误通常会消失。

现在,我们认为这是我们测试的问题,但我们决定用 Clojure 编写一个小测试,我将在下面发布(并为非 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
    ))
  )

异常随机出现在我们启动第二个服务器的行上。似乎 Java 正在保留本地端口 - 即使该端口上的客户端已经关闭。

所以,我的问题是:Java到底为什么要这样做,为什么看起来如此随机?

编辑:有人建议我将套接字的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 毫秒的睡眠并不能阻止这个问题。然而,1000 毫秒(到目前为止)已经设法阻止它。

编辑 2:有人让我使用 SO_LINGER - 但我找不到在 ServerSockets 上设置它的方法。有人对此有任何想法吗?

编辑 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 回答