Windows下如何让多个JVM进程绑定同一Socket端点?
如何让Windows允许多个JVM进程绑定同一Socket地址?
我编写了一个支持多进程绑定同一端点的服务器程序,代码如下:
try (var executor = Executors.newVirtualThreadPerTaskExecutor(); var server = new ServerSocket()) { { try { server.setOption(StandardSocketOptions.SO_REUSEADDR, Boolean.TRUE); // 抛出IOException } catch (final UnsupportedOperationException uoe) { log.error("failed to set {}", StandardSocketOptions.SO_REUSEADDR, uoe); } try { server.setOption(StandardSocketOptions.SO_REUSEPORT, Boolean.TRUE); // 抛出IOException } catch (final UnsupportedOperationException uoe) { log.error("failed to set {}", StandardSocketOptions.SO_REUSEPORT, uoe); } server.setReuseAddress(true); // 抛出SocketException } { assert !server.isBound(); server.bind(_Constants.SERVER_ENDPOINT_TO_BIND); // 抛出IOException log.info("bound to {}", server.getLocalSocketAddress()); assert server.isBound(); } { __Utils.readQuitAndClose(true, server); } while (!server.isClosed()) { final var client = server.accept(); // 抛出IOException executor.submit(() -> { try { log.debug("accepted from {}", client.getRemoteSocketAddress()); for (int r; (r = client.getInputStream().read()) != -1 && !server.isClosed(); ) { // 抛出IOException log.debug("discarding {} received from {}", String.format("0x%1$02X", r), client.getRemoteSocketAddress()); } } finally { client.close(); } return null; }); } }
该程序在macOS上可以正常支持多进程运行,但在Windows环境下无法实现。启动第二个进程时会出现如下错误:
21:36:02.588 [ main] ERROR - failed to set SO_REUSEPORT java.lang.UnsupportedOperationException: 'SO_REUSEPORT' not supported at java.base/sun.nio.ch.ServerSocketChannelImpl.setOption(ServerSocketChannelImpl.java:219) at com.github.onacit.rfc863.Rfc863Tcp2Server.main(Rfc863Tcp2Server.java:25) Exception in thread "main" java.net.BindException: Address already in use: bind at java.base/sun.nio.ch.Net.bind0(Native Method) at java.base/sun.nio.ch.Net.bind(Net.java:565) at java.base/sun.nio.ch.ServerSocketChannelImpl.netBind(ServerSocketChannelImpl.java:344) at java.base/sun.nio.ch.ServerSocketChannelImpl.bind(ServerSocketChannelImpl.java:301) at java.base/java.nio.channels.ServerSocketChannel.bind(ServerSocketChannel.java:224) at com.github.onacit.rfc863.Rfc863Tcp2Server.main(Rfc863Tcp2Server.java:33) Process finished with exit code 1
解决方法
理解Windows下的
SO_REUSEADDR行为
在Windows中,SO_REUSEADDR允许绑定到处于TIME_WAIT状态的端口,但默认不允许多个进程绑定同一未被占用的端口。不过,当所有绑定该端口的进程都设置了SO_REUSEADDR,并且使用相同的socket选项配置时,Windows允许这种共享。调整代码中的Socket选项设置逻辑
当前代码中同时通过setOption和setReuseAddress设置SO_REUSEADDR,可能导致配置冲突。建议统一使用setReuseAddress(true),并且必须在绑定端口之前完成设置。另外,直接移除对SO_REUSEPORT的设置,因为Windows的Java实现不支持该选项,它是类Unix系统特有的。修改后的代码示例
try (var executor = Executors.newVirtualThreadPerTaskExecutor(); var server = new ServerSocket()) { // 绑定端口前先设置SO_REUSEADDR server.setReuseAddress(true); assert !server.isBound(); server.bind(_Constants.SERVER_ENDPOINT_TO_BIND); log.info("bound to {}", server.getLocalSocketAddress()); assert server.isBound(); __Utils.readQuitAndClose(true, server); while (!server.isClosed()) { final var client = server.accept(); executor.submit(() -> { try { log.debug("accepted from {}", client.getRemoteSocketAddress()); for (int r; (r = client.getInputStream().read()) != -1 && !server.isClosed(); ) { log.debug("discarding {} received from {}", String.format("0x%1$02X", r), client.getRemoteSocketAddress()); } } finally { client.close(); } return null; }); } }额外注意事项
所有需要共享该端口的JVM进程,都必须设置setReuseAddress(true),且设置操作要在绑定端口前完成。Windows下这种端口共享模式下,客户端连接会被随机分配到其中一个绑定的进程,效果类似macOS下SO_REUSEPORT的负载均衡,但底层实现机制不同。
内容的提问来源于stack exchange,提问作者Jin Kwon
相关产品推荐
相关产品推荐

