You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何排查占用端口的进程?解决CI测试BindException问题

端口占用排查:lsof无输出的原因及跨平台解决方法

问题场景

在CI环境运行测试时,偶尔会遇到java.net.BindException: Address already in use导致失败,尝试用lsof -i :<port>排查端口占用进程时出现异常:

  • 本地Mac上自行用ServerSocket占用的端口,执行代码能正常输出进程信息:
    try (ServerSocket serverSocket = new ServerSocket(0)) {
      int port = serverSocket.getLocalPort();
      Process process = Runtime.getRuntime().exec("lsof -i :" + port);
      Scanner scanner = new Scanner(process.getInputStream(), UTF_8);
      while (scanner.hasNextLine()) {
        System.out.println(scanner.nextLine());
      }
    }
    
    输出示例:
    COMMAND   PID            USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
    java    91497 fred.flintstone   66u  IPv6 0x1cde46a19e75cdc7      0t0  TCP *:60562 (LISTEN)
    
  • 但遍历所有端口,捕获BindException后执行lsof却无进程信息输出:
    for (int port = 1; port <= 65535; port++) {
      try (ServerSocket serverSocket = new ServerSocket(port)) {
    
      } catch (BindException e) {
        System.out.println("Bind exception caught for port " + port);
        Process process = Runtime.getRuntime().exec("lsof -i :" + port);
        Scanner scanner = new Scanner(process.getInputStream(), UTF_8);
        while (scanner.hasNextLine()) {
          System.out.println(scanner.nextLine());
        }
      }
    }
    
    仅输出:
    Bind exception caught for port 53
    Bind exception caught for port 5432
    Bind exception caught for port 55555
    Bind exception caught for port 57506
    ...
    

原因分析

  1. 权限不足:系统端口(如1024以下的端口)通常由root权限的进程占用,普通用户执行lsof无法读取这些进程的信息,必须添加sudo提升权限。
  2. 端口状态不匹配:BindException不仅会在端口处于LISTEN状态时抛出,还会在端口处于TIME_WAIT/FIN_WAIT等连接关闭后的状态时触发。默认lsof -i :<port>只显示活跃的LISTEN/ESTABLISHED连接,这类过渡状态的端口无法被默认参数查到。
  3. lsof平台兼容性:不同系统的lsof默认行为有差异,比如Linux下若不指定协议或状态,可能漏查部分端口的占用信息。

跨平台解决方案

命令行工具替代

1. 通用查询命令(兼容Mac/Linux)

  • 查看所有端口的占用进程(需sudo权限):
    sudo lsof -i -P -n | grep LISTEN
    
  • 精准查询某端口的所有状态:
    sudo lsof -i :<port> -s TCP:ALL
    

2. Linux专用高效命令

Linux下推荐使用ss(比lsof更轻量,无需root也能查看大部分进程):

ss -tulpn | grep :<port>

3. Mac专用补充命令

用netstat补充查询过渡状态的端口:

netstat -anv | grep <port>

Java代码实现跨平台排查

修改代码适配权限和平台差异,同时处理进程流避免阻塞:

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;

public class PortChecker {
    public static void main(String[] args) {
        for (int port = 1; port <= 65535; port++) {
            try (var ignored = new java.net.ServerSocket(port)) {
                // 端口可用,跳过
            } catch (java.net.BindException e) {
                System.out.println("端口 " + port + " 被占用,进程信息:");
                String os = System.getProperty("os.name").toLowerCase();
                String command;

                // 根据系统选择适配命令
                if (os.contains("linux")) {
                    command = "ss -tulpn | grep :" + port;
                } else if (os.contains("mac")) {
                    // Mac需sudo才能查系统进程,CI环境需配置免sudo权限
                    command = "sudo lsof -i :" + port + " -s TCP:ALL";
                } else {
                    command = "lsof -i :" + port;
                }

                try {
                    Process process = Runtime.getRuntime().exec(command);
                    // 同时读取输入流和错误流,防止进程因输出缓冲区满而阻塞
                    readStream(process.getInputStream());
                    readStream(process.getErrorStream());
                    process.waitFor();
                } catch (IOException | InterruptedException ex) {
                    ex.printStackTrace();
                }
                System.out.println("---");
            } catch (IOException ex) {
                // 忽略其他IO异常
            }
        }
    }

    private static void readStream(InputStream inputStream) {
        Scanner scanner = new Scanner(inputStream, StandardCharsets.UTF_8);
        while (scanner.hasNextLine()) {
            System.out.println(scanner.nextLine());
        }
        scanner.close();
    }
}

CI环境注意事项

  1. 权限配置:在CI环境中,确保执行命令的用户拥有sudo免密码权限,或使用Linux的ss命令(无需root即可查看大部分进程)。
  2. 工具预装:确认CI镜像中已安装lsof(Mac)或ss(Linux)工具。
  3. 状态过滤:若要排查TIME_WAIT导致的绑定失败,需在命令中明确指定过滤该状态。

内容的提问来源于stack exchange,提问作者cpp beginner

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 11:44:51