如何查找可用端口?

222

我想启动一个监听端口的服务器。我可以明确指定端口并且它可以工作。但是我希望以自动方式查找可用端口。在这方面,我有两个问题。

  1. 应该在哪个端口范围内搜索可用端口号?(我使用了12345、12346和12347端口,它们都可以正常使用)。

  2. 如何确定给定的端口是否已被其他软件占用?


如果端口被其他软件占用,代码将抛出一个IOException。 - Cezar Terzian
19个回答

315

如果您不介意使用的端口号,可以在ServerSocket构造函数中指定端口0,它将监听任何可用的端口。

ServerSocket s = new ServerSocket(0);
System.out.println("listening on port: " + s.getLocalPort());

如果你想使用一组特定的端口,那么最简单的方法可能是遍历这些端口,直到找到可用的端口为止。代码示例如下:

public ServerSocket create(int[] ports) throws IOException {
    for (int port : ports) {
        try {
            return new ServerSocket(port);
        } catch (IOException ex) {
            continue; // try next port
        }
    }

    // if the program gets here, no port in the range was found
    throw new IOException("no free port found");
}

可以这样使用:

try {
    ServerSocket s = create(new int[] { 3843, 4584, 4843 });
    System.out.println("listening on port: " + s.getLocalPort());
} catch (IOException ex) {
    System.err.println("no available ports");
}

3
在使用new ServerSocket(0)时,务必小心关闭它!此代码基于http://javasourcecode.org/html/open-source/eclipse/eclipse-3.5.2/org/eclipse/jdt/launching/SocketUtil.java.html,在我的https://gist.github.com/3429822中略作修改。 - vorburger
12
以那种方式进行操作容易出现竞态条件。最好的方法是立即侦听服务器套接字,而不是打开它以查找空闲端口,然后再关闭它,然后在空闲端口上再次打开它(这样做存在一定几率有其他进程正在监听该端口)。 - Graham Edgecombe
8
同意,但这要取决于具体的用例:在我的情况下,我需要找到一个空闲的端口号并将其传递给某个API(比如嵌入式Jetty启动器用于测试)- 相应的API需要一个套接字号码,而不是已经打开的服务器套接字。所以这取决于具体情况。 - vorburger
4
合理的 API 应该接受零作为一个有效的监听端口号,然后告诉你实际监听的端口号。然而,并不是所有的 API 都是合理的:很多程序会明确测试传递的端口号是否为 0 并拒绝它(比如 ssh -D 127.0.0.0:0 ...?不行!),这真的很令人沮丧。我们不得不修改相当数量的库/程序才能让它们对我们有用。 - Joker_vD
2
@vorburger 在使用任何端口时,都应该注意关闭它。在这方面,new ServerSocket(0)并没有什么不同。你的第一个链接中根本没有任何关于这个的内容。你的第二个链接只是展示了一种糟糕的做法。一旦你构建了ServerSocket,你应该使用它,而不是关闭它并尝试重用相同的端口号。所有这些都是完全浪费时间,同时也容易受到时间窗口问题的影响。 - user207421
显示剩余3条评论

61

如果在ServerSocket的构造函数中将端口号设为0,则会自动分配一个端口。


是的,我认为这是最优雅的解决方案,但由于某些原因,在我的情况下它不起作用。但我仍然需要找出到底哪里出了问题。 - Roman
1
@Roman:为什么它不起作用?更新你的问题以包括这个(否则人们会继续建议它),并解释为什么这个解决方案对你失败了。 - FrustratedWithFormsDesigner
8
我怎样从客户端找到这个端口?;) - ed22

45
从Java 1.7开始,您可以像这样使用try-with-resources:
  private Integer findRandomOpenPortOnAllLocalInterfaces() throws IOException {
    try (
        ServerSocket socket = new ServerSocket(0);
    ) {
      return socket.getLocalPort();

    }
  }

如果您需要在特定接口上查找开放端口,请查看ServerSocket文档中的替代构造函数。 警告:使用此方法返回的端口号的任何代码都会受到竞争条件的影响 - 不同的进程/线程可能会在我们关闭ServerSocket实例后立即绑定到相同的端口。

1
如果您不设置SO_REUSEADDR,这可能无法正常工作。而且存在竞争条件,但很难修复。 - David Ehrmann
如果您随后尝试使用相同的端口号打开另一个 ServerSocket,则此方法可能无法正常工作,因为该端口号可能已被占用。为什么不直接返回实际的ServerSocket而不是关闭它仍然是个谜。 - user207421
7
有时第三方代码会接受端口但不接受套接字本身。 - Captain Man
@CaptainMan 当然可以,但在这些情况下,端口肯定是预先分配的。动态分配的端口必须由其他机制(如命名服务或广播/多播)向客户端提供。整个问题在这里都是错误的。 - user207421
@user207421 你不能更改第三方代码以接受ServerSocket而不是int作为端口。 - Captain Man

37

根据维基百科的说法,如果您不需要一个“众所周知”的端口,您应该使用端口4915265535

据我所知,确定端口是否正在使用的唯一方法是尝试打开它。


14
由于维基百科并非绝对真实/事实的来源,因此我认为注明一下是有用的:该维基百科页面的引用来自“Internet Assigned Numbers Authority (IANA)”的“Service Name and Transport Protocol Port Number Registry”页面,基于RFC 6335-第6节(在这种情况下是一个可靠的参考!: )。 - OzgurH

28

如果您需要使用范围,请使用:

public int nextFreePort(int from, int to) {
    int port = randPort(from, to);
    while (true) {
        if (isLocalPortFree(port)) {
            return port;
        } else {
            port = ThreadLocalRandom.current().nextInt(from, to);
        }
    }
}

private boolean isLocalPortFree(int port) {
    try {
        new ServerSocket(port).close();
        return true;
    } catch (IOException e) {
        return false;
    }
}

我在想如何检查一个端口,并从中解除绑定。谢谢 SergeyB! - Vlad Ilie
9
如果在区间[from,to)内的所有端口都被占用,此代码将无限循环(或至少要等到其中一个端口空闲)。如果使用顺序扫描而不是随机选择范围内的端口,则可以避免这种可能性(当您在未找到空闲端口的情况下到达范围末尾时,只需抛出异常)。如果确实需要随机选择端口,则需要使用集合来跟踪您已经尝试过的端口,并在该集合的大小等于from时引发错误。 - Simon Kissane
端口0要简单得多,不需要在成功的情况下创建两个“ServerSockets”,也不会遭受此代码的时间窗口问题。 - user207421

24

5
对于那些好奇的人,SpringUtils.findAvailableTcpPort()在幕后做的与其他答案中推荐的完全相同:选择一个端口,然后尝试 new ServerSocket(port) - Alexey Berezkin
3
很遗憾,SocketUtils类现在已经被弃用,并将在Spring 6.0中被移除。 - pTak
TestSocketUtils在Spring 6中仍然存在,但显然是在spring-test模块中。 - undefined

11
Eclipse SDK包含一个类SocketUtil,可以满足你的需求。你可以查看git的源代码

2
看Eclipse的代码,他们做的和Graham Edgecombe的回答一样。 - KevinL

8

我最近发布了一个小型库,专门用于测试。 Maven 依赖:

<dependency>
    <groupId>me.alexpanov</groupId>
    <artifactId>free-port-finder</artifactId>
    <version>1.0</version>
</dependency>

安装完成后,可以通过以下方式获取免费端口号:

int port = FreePortFinder.findFreeLocalPort();

8
请看ServerSocket

创建一个服务器套接字并绑定到指定端口。端口为0会在任意空闲端口上创建套接字。


8
这个对于我来说在Java 6上有效。
    ServerSocket serverSocket = new ServerSocket(0);
    System.out.println("listening on port " + serverSocket.getLocalPort());

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