为什么两个 Java 进程可以绑定到 macOS 中的同一个套接字?

Why can two Java processes bind to the same socket in macOS?

我有一些 Java 代码正在生成套接字绑定。很难提供一个最小的示例,因为这是 Web 框架的一部分,但它在某些时候有效地进行了此检查。

  private static boolean portInUse(int port) {
    // try to bind to this port, if it succeeds the port is not in use
    try (ServerSocket socket = new ServerSocket(port)) {
      socket.setReuseAddress(true);
      return false;
    } catch (IOException e) {
      return true;
    }
  }

我可以看到,如果我 运行 两个具有相同端口的不同 Java 进程,它们都属于第一个条件和 return false,因此两者能够绑定到同一个端口。我已经通读了 some related socket questions and explanations like this one,但它们似乎让我觉得这对于我指定的选项来说是不可能的。查看 setReuseAddress 的实现,它似乎只在套接字上设置 SO_REUSEADDR

我可以看到一个进程在调试器中以 ServerSocket[addr=0.0.0.0/0.0.0.0,localport=56674] 之类的套接字结束。如果我 运行 类似 sudo lsof -n -i | grep -e LISTEN -e ESTABLISHED | grep 56674 我可以看到两个进程绑定到同一个端口:

java      68863    natdempk 1256u  IPv4 0xbbac93fff9a6e677      0t0  TCP *:56674 (LISTEN)
java      68998    natdempk  985u  IPv6 0xbbac93fff2f84daf      0t0  TCP *:56674 (LISTEN)

我还可以看到 gRPC 和 Node 等其他一些项目在问题跟踪器中提到了在他们的服务器上观察到的这种行为,但他们从未解释为什么这是可能的。 不同的进程如何绑定到 macOS 上的同一个套接字?

我正在 运行ning macOS 11.6.3 (20G415) 如果有帮助的话。如果有人有任何我应该在这里添加的内容,也很乐意提供更多调试信息。

它们没有绑定到同一个端口。一个绑定到 IPv6 之上的 TCP,另一个绑定到 IPv4 之上的 TCP。

稍微扩展 Java 细节:Java 中的 new ServerSocket(port) 使用 InetAddress.anyLocalAddress() 因为没有传入 InetAddressInetAddress.anyLocalAddress() 可以 return IPv4 或 IPv6 地址,这意味着尽管传入相同的端口,但不能保证绑定到 JVM 的值相同。