如何等待直到TCP端口在Java中真正(本地)关闭?

问题描述 投票:13回答:2

我为什么要问它?

如果需要,您可以跳过故事。仍然有些人可能感兴趣。

我有一个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 linux sockets tcp apache-zookeeper
2个回答
0
投票

您做了一个逻辑错误,如果您在Java中关闭套接字,操作系统层上的文件描述符将被关闭。但是你的问题是因为你接受在套接字上,可以通过等待连接来阻止端口。因此,您需要先关闭它们。最好创建一个客户端套接字,然后检查是否可以将其绑定到所选端口。


0
投票

恐怕您对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可能会有所帮助。

© www.soinside.com 2019 - 2024. All rights reserved.