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

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;
    }

}

问题分析与修复方案

核心问题点

  1. PortScanCallable创建新Node而非复用原实例:扫描到的端口添加到了新对象,IP扫描生成的原Node未更新,表格数据源无法同步
  2. 结果收集逻辑错误:仅判断最后一个Future是否完成,重复调用future.get()会阻塞线程,且未合并结果到原Node
  3. process方法未更新表格:仅打印端口,未将结果同步到视图或数据模型
  4. 线程池关闭逻辑混乱: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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 00:02:06