Sockets:使用Java发现端口可用性

144

我如何使用Java编程确定特定机器上端口的可用性?

也就是说,给定一个端口号,确定它是否已经被使用了吗?


2
虽然这个问题是关于如何知道给定端口是否已经在使用,但你可能会来到这里尝试找到一种获取空闲端口号的方法,而 https://dev59.com/8nE85IYBdhLWcg3wpFOu(附带我的 https://gist.github.com/3429822)提供了更好的解决方案。 - vorburger
为了什么目的?如果你只是想找一个空闲的套接字来监听,只需指定零即可。任何扫描技术都可能存在时间窗口问题:当你扫描时端口可能是空闲的,但当你去声明时它可能已经被占用了。 - user207421
10个回答

97

这是来自Apache camel项目的implementation

/**
 * Checks to see if a specific port is available.
 *
 * @param port the port to check for availability
 */
public static boolean available(int port) {
    if (port < MIN_PORT_NUMBER || port > MAX_PORT_NUMBER) {
        throw new IllegalArgumentException("Invalid start port: " + port);
    }

    ServerSocket ss = null;
    DatagramSocket ds = null;
    try {
        ss = new ServerSocket(port);
        ss.setReuseAddress(true);
        ds = new DatagramSocket(port);
        ds.setReuseAddress(true);
        return true;
    } catch (IOException e) {
    } finally {
        if (ds != null) {
            ds.close();
        }

        if (ss != null) {
            try {
                ss.close();
            } catch (IOException e) {
                /* should not be thrown */
            }
        }
    }

    return false;
}

他们也在检查DatagramSocket,以确定UDP和TCP端口是否可用。

5
如果你使用MINA,一个不错的变体是AvailablePortFinder.getNextAvailable(1024),它会帮你获取下一个非特权端口。 - Alain O'Dea
9
然而,这并不是万无一失的。当上述程序报告8000可用时,我曾受过在TCP 8000上运行nginx的影响。不知道为什么——我怀疑nginx做了一些诡异的事情(这是在OS X上)。对于我来说,解决方法是执行上述操作,并打开一个新的Socket("localhost", port),如果我们没有收到异常就返回false。你有什么想法吗? - Partly Cloudy
2
在Windows上尝试检测papercut SMTP服务器是否正在运行时遇到了相同的问题。打开一个端口连接而不是尝试绑定它(如Partly Clodys评论和Ivan的答案所建议的)似乎更可靠。 - stian
5
有趣。在套接字已经绑定之后调用setReuseAddress()方法是完全无意义的,而且完全违背了代码的目的。 - user207421
3
这段代码容易出现时间窗口问题。如果目标是创建一个监听某个端口的ServerSocket,那么它应该这样做,并返回ServerSocket。创建它,然后关闭它并返回它不能保证可以使用该端口创建下一个ServerSocket。对于DatagramSocket也是如此。 - user207421
显示剩余3条评论

48

对于Java 7,您可以使用try-with-resource以获得更简洁的代码:

private static boolean available(int port) throws IllegalStateException {
    try (Socket ignored = new Socket("localhost", port)) {
        return false;
    } catch (ConnectException e) {
        return true;
    } catch (IOException e) {
        throw new IllegalStateException("Error while trying to check open port", e);
    }
}

20
这并不是测试端口是否可用。它会测试端口是否处于LISTEN状态,IP地址是否可达等。 - user207421
1
此外,这个测试非常缓慢(每个端口近一秒钟)。 - JMax
当捕获到IOException时,它不应该返回false吗? - Baroudi Safwen
@BaroudiSafwen 这完全取决于异常的实际情况。对于 ConnectException: 'connection refused',是的,它应该返回 false。对于超时,它可能返回的任何内容都无效,因为实际答案是未知的。这就是为什么这种技术在这个目的上是无用的原因。 - user207421
如果传递的端口正在使用,则不会抛出异常,而是从_try_块的主体返回false。相反,如果端口未被使用,则会引发异常,在这种情况下,您希望该方法返回true。然而,正如JMax所评论的,这个测试很慢。 - hfontanez

42

看起来从Java 7开始,David Santamaria的回答不再可靠。 然而,似乎仍然可以使用Socket来可靠地测试连接。

private static boolean available(int port) {
    System.out.println("--------------Testing port " + port);
    Socket s = null;
    try {
        s = new Socket("localhost", port);

        // If the code makes it this far without an exception it means
        // something is using the port and has responded.
        System.out.println("--------------Port " + port + " is not available");
        return false;
    } catch (IOException e) {
        System.out.println("--------------Port " + port + " is available");
        return true;
    } finally {
        if( s != null){
            try {
                s.close();
            } catch (IOException e) {
                throw new RuntimeException("You should handle this error." , e);
            }
        }
    }
}

我想检查代理服务器是否可用,以便代表他进行调用。 - Dejell
1
@DavidSantamaria的答案从来没有起作用。与Java 7无关。 - user207421
如果已经从您的代码中返回了结果,那么它会进入finally语句吗? - gumuruh

38

如果你不太关心性能,你可以尝试使用ServerSocket类来监听一个端口。如果它抛出异常,很有可能是端口已经被占用了。

public static boolean isAvailable(int portNr) {
  boolean portFree;
  try (var ignored = new ServerSocket(portNr)) {
      portFree = true;
  } catch (IOException e) {
      portFree = false;
  }
  return portFree;
}

编辑:如果你只是想选择一个空闲端口,那么new ServerSocket(0)会为你找到一个。


1
我认为这并不是作者问题的预期范围内。 - Spencer Ruport
1
有没有一种方法可以不使用 try/catch 来完成它?我不介意为我的目的使用 任何 可用端口,但是迭代端口号并在每个端口上进行 try/catch 操作,直到找到一个空闲的端口有些浪费。难道没有“本地布尔 available(int)”方法吗,它只是检查是否可用吗? - Oren Shalev
30
新建ServerSocket(0)将自动选择一个空闲端口。 - Spencer Ruport
@HughPerkins 这是一个更好的第一选择。 - user207421
@Hosam Aly:Socket 实现了 Closable 接口。使用 try-catch-finally(带资源)将会为你调用 socket.close() 方法。 - primehunter
显示剩余6条评论

15
下面的解决方案受到Spring-core(Apache许可证)中SocketUtils实现的启发。
与使用Socket(...)的其他解决方案相比,它非常快速(在不到一秒钟的时间内测试1000个TCP端口)。
当然,它只能检测在所有接口上打开端口或明确在本地主机上运行的服务。
public static boolean isTcpPortAvailable(int port) {
    try (ServerSocket serverSocket = new ServerSocket()) {
        // setReuseAddress(false) is required only on macOS, 
        // otherwise the code will not work correctly on that platform          
        serverSocket.setReuseAddress(false);
        serverSocket.bind(new InetSocketAddress(InetAddress.getByName("localhost"), port), 1);
        return true;
    } catch (Exception ex) {
        return false;
    }
}       

谢谢,这对我在 macOS 11(Big Sur)上运行成功。 - AbstractVoid

5

对David Santamaria提供的答案进行了清理:

/**
 * Check to see if a port is available.
 *
 * @param port
 *            the port to check for availability.
 */
public static boolean isPortAvailable(int port) {
    try (var ss = new ServerSocket(port); var ds = new DatagramSocket(port)) {
        return true;
    } catch (IOException e) {
        return false;
    }
}

这仍然存在一个竞态条件,正如用户207421在评论中指出David Santamaria的回答中所说的那样(在此方法关闭ServerSocketDatagramSocket并返回后,某些内容可能会获取端口)。

如果您不关闭套接字,即使引用已被清除,也会占用套接字吗? - Hatzen
1
套接字在这里确实被关闭了——这是一个try-with-resources块。 - Luke Hutchison

4
尝试/捕获基于套接字的解决方案可能无法产生准确的结果(套接字地址为“localhost”,在某些情况下,端口可能被占用,而不是通过回环接口,至少在Windows上我看到这个测试失败,即该端口错误地声明为可用)。
有一个很酷的库叫做SIGAR,以下代码可以帮助你:
Sigar sigar = new Sigar();
int flags = NetFlags.CONN_TCP | NetFlags.CONN_SERVER | NetFlags.CONN_CLIENT;             NetConnection[] netConnectionList = sigar.getNetConnectionList(flags);
for (NetConnection netConnection : netConnectionList) {
   if ( netConnection.getLocalPort() == port )
        return false;
}
return true;

我收到了一个错误信息:"java.library.path中没有sigar-x86-winnt.dll"。不幸的是,这需要下载一些额外的dll文件,这在企业环境中并不可行(我无法控制CI系统)。真遗憾...还是谢谢。 - uthomas
@uthomas,我猜你仍然可以控制你的应用程序部署包,如果是这种情况,你总是可以在JAR文件附近提供Sigar本地运行时DLL/SOs,并通过简单的黑客手段来设置JAVA lib路径(即使在JVM启动后也可以影响它)。 - Shmil The Cat

1
在我的情况下,尝试连接端口有所帮助 - 如果服务已经存在,它会做出响应。
    try {
        log.debug("{}: Checking if port open by trying to connect as a client", portNumber);
        Socket sock = new Socket("localhost", portNumber);          
        sock.close();
        log.debug("{}: Someone responding on port - seems not open", portNumber);
        return false;
    } catch (Exception e) {         
        if (e.getMessage().contains("refused")) {
            return true;
    }
        log.error("Troubles checking if port is open", e);
        throw new RuntimeException(e);              
    }

1
Not what was asked for. - user207421

0
在我的情况下,我必须使用DatagramSocket类。
boolean isPortOccupied(int port) {
    DatagramSocket sock = null;
    try {
        sock = new DatagramSocket(port);
        sock.close();
        return false;
    } catch (BindException ignored) {
        return true;
    } catch (SocketException ex) {
        System.out.println(ex);
        return true;
    }
}

别忘了先导入

import java.net.DatagramSocket;
import java.net.BindException;
import java.net.SocketException;

这只适用于UDP端口。问题似乎涉及TCP端口。 - user207421

-2

我尝试过类似的东西,它对我来说非常有效。

            Socket Skt;
            String host = "localhost";
            int i = 8983; // port no.

                 try {
                    System.out.println("Looking for "+ i);
                    Skt = new Socket(host, i);
                    System.out.println("There is a Server on port "
                    + i + " of " + host);
                 }
                 catch (UnknownHostException e) {
                    System.out.println("Exception occured"+ e);

                 }
                 catch (IOException e) {
                     System.out.println("port is not used");

                 }

1
Not what was asked for. - user207421
还有一个套接字泄漏。 - user207421

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接