Java网络应用端口扫描结果更新JTable问题求助
Java Swing端口扫描结果无法更新JTable问题排查与修复
问题背景
在Eclipse中开发的Java网络应用,IP扫描功能正常,但端口扫描模块存在异常:
- IP扫描与端口扫描类均继承
SwingWorker,通过doInBackground()创建线程池并分块提交Callable任务 - 端口扫描能在终端输出开放端口,但无法将结果同步到对应
Node对象,也无法更新已存储IP扫描数据的JTable - 不确定是直接更新表格,还是先更新
Node再让表格自动同步;同时不清楚更新操作应放在PortScanMain还是启动扫描的IpScanMain类中
相关代码
PortScanMain类
/** * <h1>PortScanMain</h1> * this class is responsible for main logic of scanning * and detecting open ports in a host on the network * @author George * @version 1.0 */ public class PortScanMain extends SwingWorker<Void, Integer>{ final int MAX_PORT = 1024; public Scanner in; public String ip; public Future<Node> result = null; public int startPort = 0; final int TIME_OUT = 4000; private Node node; private JTable table; private int hostNum = 0; private DefaultTableModel model; public PortScanCallable pScan; final int NUM_OF_THREAD = MAX_PORT; //divided by 4 b/c 4 processors so 256 threads public ExecutorService servicePool = Executors.newFixedThreadPool(NUM_OF_THREAD); public List<Future<Node>> resultList = new ArrayList<>(); public int endPort = startPort + (MAX_PORT/NUM_OF_THREAD); /** * setter method to set ip address * @param ip */ public PortScanMain(Node node, JTable table, int num) { System.out.println("Scanning " + node.getIp()); this.node = node; this.table = table; this.hostNum = num; } @Override protected Void doInBackground() throws Exception { model = (DefaultTableModel) table.getModel(); for(int i = 0; i <= NUM_OF_THREAD/2; i++) { PortScanCallable portScan = new PortScanCallable(node.getIp(), startPort, endPort); //scan 2 ports @time result = servicePool.submit(portScan); resultList.add(result); startPort = endPort+1; endPort = startPort + (MAX_PORT / NUM_OF_THREAD); } try { Thread.sleep(TIME_OUT); } catch (InterruptedException e1) { e1.printStackTrace(); } //picking up results if(result.isDone()) { //print result System.out.println(ip + " Open ports "); for(Future<Node> future : resultList) { for(Integer port: future.get().openPorts) { try { if(future.get() != null) { publish(port); } } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } } } } return null; } @Override protected void process(List<Integer> chunks) { for(Integer port : chunks) { System.out.println(hostNum +" "+ ip +" "+ port); } } @Override protected void done() { table.setValueAt("22, 80, 443", hostNum, 4); } /** * this method will initiate the thread pool and send port ranges * to callable class to be scanned. then pause for 4 sec and * finally display results from the scanned ip adress * @throws InterruptedException * @throws ExecutionException */ public void startPortScanning() throws InterruptedException, ExecutionException { servicePool.shutdown(); try { servicePool.awaitTermination(3, TimeUnit.SECONDS); servicePool.shutdownNow(); } catch (InterruptedException e) { } } }
PortScanCallable类
public class PortScanCallable implements Callable<Node>{ List<Integer> portsResult; int startPort; int endPort; Node newNode; String ipAddress; int timeOut = 1000; /** * Constructor for PortScanCallable. * @param ipAddress * @param startPort * @param endPort */ public PortScanCallable(String ipAddress, int startPort, int endPort) { this.ipAddress = ipAddress; this.startPort = startPort; this.endPort = endPort; newNode = new Node(ipAddress); portsResult = new ArrayList<Integer>(); } /** * this method establishes a socket connection * to each port and if port is open, * then adds to result * @return newNode */ @Override public Node call() throws Exception { for (int i = startPort; i <= endPort; i++) { try { //establishing connection to every port Socket socket = new Socket(); SocketAddress address = new InetSocketAddress(ipAddress, i); socket.connect(address, timeOut); if (socket.isConnected()) { System.out.println("CONNECTED: " + i); socket.close(); newNode.openPorts.add(i); } } catch (UnknownHostException e) { //e.printStackTrace(); } catch (SocketTimeoutException e) { //e.printStackTrace(); } catch (ConnectException e) { //e.printStackTrace(); } catch (IOException e) { //e.printStackTrace(); } } return newNode; } }
问题分析与修复方案
核心问题点
PortScanCallable创建新Node而非复用原实例:扫描到的端口添加到了新对象,IP扫描生成的原Node未更新,表格数据源无法同步- 结果收集逻辑错误:仅判断最后一个
Future是否完成,重复调用future.get()会阻塞线程,且未合并结果到原Node process方法未更新表格:仅打印端口,未将结果同步到视图或数据模型- 线程池关闭逻辑混乱:
startPortScanning提前终止扫描任务,导致结果不完整
具体修复步骤
1. 修改PortScanCallable复用原Node
直接传入IP扫描时的Node实例,确保端口结果直接更新到原对象:
public class PortScanCallable implements Callable<Node> { int startPort; int endPort; Node targetNode; int timeOut = 1000; public PortScanCallable(Node targetNode, int startPort, int endPort) { this.targetNode = targetNode; this.startPort = startPort; this.endPort = endPort; } @Override public Node call() throws Exception { for (int i = startPort; i <= endPort; i++) { try { Socket socket = new Socket(); SocketAddress address = new InetSocketAddress(targetNode.getIp(), i); socket.connect(address, timeOut); if (socket.isConnected()) { System.out.println("CONNECTED: " + i); socket.close(); // 线程安全的方式添加端口 synchronized (targetNode.openPorts) { targetNode.openPorts.add(i); } } } catch (Exception e) { // 忽略异常,无需打印堆栈 } } return targetNode; } }
2. 修复PortScanMain的结果收集与更新逻辑
public class PortScanMain extends SwingWorker<Void, Node> { final int MAX_PORT = 1024; private Node node; private JTable table; private int hostNum = 0; private DefaultTableModel model; // 根据CPU核心数设置合理线程数,避免资源耗尽 final int NUM_OF_THREAD = Runtime.getRuntime().availableProcessors() * 4; public ExecutorService servicePool = Executors.newFixedThreadPool(NUM_OF_THREAD); public List<Future<Node>> resultList = new ArrayList<>(); public PortScanMain(Node node, JTable table, int num) { System.out.println("Scanning " + node.getIp()); this.node = node; this.table = table; this.hostNum = num; model = (DefaultTableModel) table.getModel(); } @Override protected Void doInBackground() throws Exception { int portsPerThread = MAX_PORT / NUM_OF_THREAD; int startPort = 0; for (int i = 0; i < NUM_OF_THREAD; i++) { // 处理最后一个线程的端口范围,避免遗漏 int endPort = (i == NUM_OF_THREAD - 1) ? MAX_PORT : startPort + portsPerThread - 1; PortScanCallable portScan = new PortScanCallable(node, startPort, endPort); resultList.add(servicePool.submit(portScan)); startPort = endPort + 1; } // 等待所有任务完成后关闭线程池 servicePool.shutdown(); servicePool.awaitTermination(10, TimeUnit.SECONDS); // 发布更新后的Node for (Future<Node> future : resultList) { if (!future.isCancelled()) { publish(future.get()); } } return null; } @Override protected void process(List<Node> chunks) { // 实时更新表格,将端口列表转为字符串 Node updatedNode = chunks.get(chunks.size() - 1); Collections.sort(updatedNode.openPorts); String portsStr = updatedNode.openPorts.stream() .map(String::valueOf) .collect(Collectors.joining(", ")); table.setValueAt(portsStr, hostNum, 4); } @Override protected void done() { // 处理任务异常情况 try { get(); } catch (Exception e) { e.printStackTrace(); } } }
3. 更新时机与位置选择
- 优先更新
Node再同步表格:遵循MVC原则,Node作为数据模型,修改后再更新视图(JTable) - 更新操作放在
PortScanMain中:该类直接持有Node和JTable实例,逻辑更清晰,无需跨类调用
4. 额外优化建议
- 给
Node的openPorts使用CopyOnWriteArrayList,避免多线程并发修改问题 - 移除
PortScanMain中无用字段(如ip、in等),简化代码 - 不要设置过大的线程池大小,避免系统资源耗尽
内容的提问来源于stack exchange,提问作者miatech
相关产品推荐
相关产品推荐

