CompletableFuture以非阻塞的方式执行任务,并在任务完成后进行结果处理
采用CompletableFuture实现异步并行检查,相比传统的线程池+Future组合,它能更简洁地实现"等待所有任务完成+汇总结果"的逻辑,代码更优雅且易于维护。
二、核心代码实现
下面是完整的端口检查工具类实现,包含单个端口检查和多端口并发检查两个核心方法:
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
/**
* 并发端口检查工具类
*/
public class PortChecker {
// 自定义线程池:根据端口检查的特性,使用固定大小线程池
private static final Executor executor = Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors() * 2,
r -> {
Thread thread = new Thread(r);
thread.setName("port-checker-thread-" + thread.getId());
thread.setDaemon(true); // 守护线程,避免影响主程序退出
return thread;
}
);
/**
* 并发检查多个端口是否开放
*
* @param ip 要检查的IP地址
* @param ports 要检查的端口列表,逗号分隔(如"80,8080,443")
* @return 如果所有端口都无法连接,则返回true;如果任何一个端口可以连接,则返回false
* @throws BusinessException 当存在开放端口时抛出业务异常
*/
public boolean validatePorts(String ip, String ports) {
// 1. 解析端口列表
String[] portArray = ports.split(",");
if (portArray.length == 0) {
throw new IllegalArgumentException("端口列表不能为空");
}
// 2. 为每个端口创建异步检查任务
List<CompletableFuture<Boolean>> futures = Arrays.stream(portArray)
.map(portStr -> {
try {
int port = Integer.parseInt(portStr.trim());
// 验证端口有效性
if (port < 1 || port > 65535) {
throw new IllegalArgumentException("无效端口号: " + port);
}
return checkPort(ip, port);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("端口格式错误: " + portStr, e);
}
})
.collect(Collectors.toList());
// 3. 等待所有异步任务完成
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
// 4. 汇总所有任务结果
List<Boolean> results = futures.stream()
.map(CompletableFuture::join)
.collect(Collectors.toList());
// 5. 判断结果:只要有一个端口开放(返回false),则校验失败
if (results.contains(Boolean.FALSE)) {
throw new BusinessException(StrUtil.format("IP:{} 存在开放端口,校验失败", ip));
}
return true;
}
/**
* 检查单个端口是否开放
*
* @param ip IP地址
* @param port 端口号
* @return 端口未开放或连接超时返回true;端口开放返回false
*/
private CompletableFuture<Boolean> checkPort(String ip, int port) {
return CompletableFuture.supplyAsync(() -> {
try (Socket socket = new Socket()) {
// 连接超时设置为1秒(根据实际需求调整)
socket.connect(new InetSocketAddress(ip, port), 1000);
// 连接成功:端口开放,返回false
System.out.printf("IP:%s 端口:%d 开放%n", ip, port);
return false;
} catch (IOException e) {
// IO异常:端口未开放或连接超时,返回true
System.out.printf("IP:%s 端口:%d 未开放(原因:%s)%n", ip, port, e.getMessage());
return true;
} catch (Exception e) {
// 其他异常:视为校验通过,返回true
System.err.printf("IP:%s 端口:%d 检查异常:%s%n", ip, port, e.getMessage());
return true;
}
}, executor);
}
}
// 业务异常类定义
class BusinessException extends RuntimeException {
public BusinessException(String message) {
super(message);
}
}
// 字符串工具类(简化版,实际项目可使用Hutool等工具库)
class StrUtil {
public static String format(String template, Object... params) {
return String.format(template, params);
}
}
三、代码解析:核心技术点详解
1. 并发任务创建:CompletableFuture.supplyAsync
return CompletableFuture.supplyAsync(() -> {
// 端口检查逻辑
}, executor);
supplyAsync用于创建有返回值的异步任务,第一个参数是Supplier函数式接口,封装端口检查的核心逻辑- 第二个参数指定自定义线程池,避免使用默认的
ForkJoinPool,更好地控制资源占用 - 每个端口检查任务独立运行,实现真正的并行处理
2. 单个端口检查逻辑
端口检查的核心逻辑通过Socket连接实现:
try (Socket socket = new Socket()) {
socket.connect(new InetSocketAddress(ip, port), 1000);
return false; // 连接成功:端口开放
} catch (IOException e) {
return true; // 连接失败:端口未开放
}
- 使用try-with-resources语法自动关闭Socket资源,避免资源泄漏
socket.connect()方法指定超时时间(1秒),防止长时间阻塞- 异常处理设计:
- IO异常(如Connection refused、超时)视为端口未开放
- 其他异常(如InterruptedException)也视为校验通过(可根据业务调整)
3. 多任务协调:CompletableFuture.allOf
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
allOf接收多个CompletableFuture,返回一个新的CompletableFuture- 新的CompletableFuture会在所有输入任务完成后才完成
join()方法阻塞等待所有任务完成(与get()类似,但抛出未检查异常)
4. 结果汇总与判断
List<Boolean> results = futures.stream()
.map(CompletableFuture::join)
.collect(Collectors.toList());
if (results.contains(Boolean.FALSE)) {
throw new BusinessException("存在开放端口,校验失败");
}
- 调用每个Future的
join()方法获取结果(此时所有任务已完成,不会阻塞) - 结果判断逻辑:只要有一个端口开放(返回false),则整体校验失败
- 通过业务异常传递失败信息,便于上层处理
5. 性能对比:并发vs串行
假设检查10个端口,每个端口检查耗时1秒:
- 串行检查:总耗时约10秒(10个任务依次执行)
- 并发检查:总耗时约1秒(10个任务并行执行)
并发方式在多端口检查场景下性能优势明显,端口数量越多,优势越显著。
五、使用示例
public class PortCheckerDemo {
public static void main(String[] args) {
PortChecker checker = new PortChecker();
String ip = "127.0.0.1";
String ports = "80,8080,443,3306";
try {
boolean result = checker.validatePorts(ip, ports);
if (result) {
System.out.println("所有端口均未开放,校验通过");
}
} catch (BusinessException e) {
System.err.println("校验失败:" + e.getMessage());
} catch (IllegalArgumentException e) {
System.err.println("参数错误:" + e.getMessage());
}
}
}

被折叠的 条评论
为什么被折叠?



