如何排查占用端口的进程?解决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 ...
原因分析
- 权限不足:系统端口(如1024以下的端口)通常由root权限的进程占用,普通用户执行
lsof无法读取这些进程的信息,必须添加sudo提升权限。 - 端口状态不匹配:
BindException不仅会在端口处于LISTEN状态时抛出,还会在端口处于TIME_WAIT/FIN_WAIT等连接关闭后的状态时触发。默认lsof -i :<port>只显示活跃的LISTEN/ESTABLISHED连接,这类过渡状态的端口无法被默认参数查到。 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环境注意事项
- 权限配置:在CI环境中,确保执行命令的用户拥有
sudo免密码权限,或使用Linux的ss命令(无需root即可查看大部分进程)。 - 工具预装:确认CI镜像中已安装
lsof(Mac)或ss(Linux)工具。 - 状态过滤:若要排查
TIME_WAIT导致的绑定失败,需在命令中明确指定过滤该状态。
内容的提问来源于stack exchange,提问作者cpp beginner
相关产品推荐
相关产品推荐

