为什么我要问这个问题?
如果您想的话,可以跳过故事部分。但是有些人可能会感兴趣。
我在Java中嵌入了一个ZooKeeper服务器。在单元测试中,我动态地为测试服务器分配端口。在分配端口之前,我通过打开ServerSocket
并关闭它来检查它是否未使用。
在单元测试中,有时候会出现BindException
,当我启动我的服务器时(我不可能将相同的端口分配给两个服务器,因为我还使用文件锁进行互斥)。原因是对于端口检查,我打开端口,然后关闭它,并等待一段时间,直到端口可以重新打开。
然而,Java套接字有一个选项(StandardSocketOptions.SO_REUSEADDR),可以告诉它在TIMED_WAIT状态下可以重用旧套接字。在检查了ZooKeeper代码之后,发现它实际上被设置为true(参见org.apache.zookeeper.server.NIOServerCnxnFactory.configure(InetSocketAddress, int)):
@Override
public void configure(InetSocketAddress addr, int maxcc) throws IOException {
configureSaslLogin();
thread = new Thread(this, "NIOServerCxn.Factory:" + addr);
thread.setDaemon(true);
maxClientCnxns = maxcc;
this.ss = ServerSocketChannel.open();
ss.socket().setReuseAddress(true);
LOG.info("binding to port " + addr);
ss.socket().bind(addr);
ss.configureBlocking(false);
ss.register(selector, SelectionKey.OP_ACCEPT);
}
我的测试结果表明它不起作用。我在Linux JDK 1.7.0_60下遇到了BindException错误。
经过检查ServerSocketChannel实现(JDK 1.7.0_60)后,我意识到这在Linux下永远不会起作用。请参见sun.nio.ch.ServerSocketChannelImpl.setOption(SocketOption<T>, T)
:
public <T> ServerSocketChannel setOption(SocketOption<T> paramSocketOption, T paramT) throws IOException
{
if (paramSocketOption == null)
throw new NullPointerException();
if (!(supportedOptions().contains(paramSocketOption)))
throw new UnsupportedOperationException("'" + paramSocketOption + "' not supported");
synchronized (this.stateLock) {
if (!(isOpen()))
throw new ClosedChannelException();
if ((paramSocketOption == StandardSocketOptions.SO_REUSEADDR) && (Net.useExclusiveBind()))
{
this.isReuseAddress = ((Boolean)paramT).booleanValue();
}
else {
Net.setSocketOption(this.fd, Net.UNSPEC, paramSocketOption, paramT);
}
return this;
}
}
很遗憾,在Linux下,Net.useExclusiveBind()
永远不会返回true。如果您查看其源代码(在类似OpenJDK中),它依赖于Net.isExclusiveBindAvailable()
,而在Linux下,该值为-1。
你有解决方法吗?
除了打开一个没有SO_REUSEADDR选项的ServerSocket
并检查是否收到BindException
以外,Java中是否有一种等待端口真正本地关闭的方法?当然,这不是一个解决方案,因为我还必须再次关闭该ServerSocket。
为什么Java中没有像阻塞模式下关闭套接字那样的东西,只有在套接字在操作系统级别上真正关闭时才返回?
open
,bind
,listen
和accept
,然后再close
吗?在您的特定情况下,我认为SO_REUSEADDR
本身并不是坏事。因为很少有任何流浪未确认的数据会滞留。 - gabhijitss.setReuseAddress(false);
,所以自然会失败(所有套接字都必须调用setReuseAddress(true);
,而不仅仅是其中一个)。尽管你引用的sun.nio.ch.ServerSocketChannelImpl.setOption
代码仍然调用了Net.setSocketOption(this.fd, Net.UNSPEC, paramSocketOption, paramT);
,这应该正确设置了 SO_REUSEADDR - 你不需要让 Net.useExclusiveBind() 返回 true。你的单元测试需要调用ss.setReuseAddress(true);
,并且你肯定应该关闭你已经接受的套接字。 - nos