如果需要,您可以跳过故事。仍然有些人可能感兴趣。
我有一个Java嵌入式的ZooKeeper服务器。在单元测试中,我以动态方式为测试服务器分配端口。在分配端口之前,我先打开ServerSocket
,然后将其关闭,以检查端口是否未使用。
[有时会发生,在单元测试中,我启动服务器时会得到BindException
。(不可能是我将相同的端口分配给两个服务器,因为我也使用文件锁进行互斥。 )。事实证明,原因是,对于端口检查,我打开了端口,然后将其关闭,然后在操作系统级别等待了一段时间,直到可以重新打开端口为止。
但是有一个选项(StandardSocketOptions.SO_REUSEADDR)可以告诉Java套接字,可以重用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);
}
但是,我的test证明它不起作用。我得到BindException(在Linux JDK 1.7.0_60下)。
检查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;
}
}
[不幸的是,Net.useExclusiveBind()
永远不会在Linux下返回true,如果您检查源in the hopefully similar OpenJDK,则取决于Net.isExclusiveBindAvailable()
,在Linux下为-1。
Java中除了打开没有SO_REUSEADDR的ServerSocket
并检查是否获得BindException
之外,是否有其他方法可以等到端口真正被本机关闭?当然,这不是解决方案,因为此后我必须再次关闭该ServerSocket。为什么在Java中没有什么比以阻塞模式关闭套接字(仅当套接字确实在操作系统级别关闭时才返回)更复杂?
您做了一个逻辑错误,如果您在Java中关闭套接字,操作系统层上的文件描述符将被关闭。但是你的问题是因为你接受在套接字上,可以通过等待连接来阻止端口。因此,您需要先关闭它们。最好创建一个客户端套接字,然后检查是否可以将其绑定到所选端口。
恐怕您对sun.nio.ch.ServerSocketChannelImpl.setOption(SocketOption<T>, T)
实现的分析不正确。
“ exclusive bind”东西仅在Windows上使用,与其他平台无关。您可以在sources of the sun.nio.ch.Net class中进行检查。具体来说,请参见isExclusiveBindAvailable()
的注释。
[当排他绑定不可用时,ServerSocketChannelImpl.setOption
仅调用Net.setSocketOption
,最后将调用该本地setsockopt
函数。
BTW,服务器端套接字可能不处于TIME_WAIT状态,但是也处于FIN_WAIT_1或FIN_WAIT_2(等待客户端确认关闭)。此in-depth description of the TCP state machine可能会有所帮助。