1. 概述

使用Java进行端口扫描是一种枚举目标机器上开放或活动端口的方法。主要目标是列出当前运行的应用程序和服务。

在这个教程中,我们将解释如何开发一个简单的Java应用程序来进行端口扫描,以便我们可以用来扫描主机上的开放端口。

2. 计算机端口是什么?

计算机端口是一个逻辑实体,它使得可以将特定服务与连接关联起来。此外,端口由1到65535之间的整数标识。按照惯例,前1024个端口被保留给标准服务,如:

  • 端口20:FTP
  • 端口23:Telnet
  • 端口25:SMTP
  • 端口80:HTTP

端口扫描的思路是创建一个TCP套接字,并尝试连接到特定端口。如果连接成功建立,我们就标记这个端口为开放;否则,标记为关闭。

然而,逐一尝试连接65535个端口可能需要大约200毫秒,这听起来时间不长,但对单个主机的所有端口进行逐一扫描可能会花费相当多的时间

为了解决性能问题,我们将采用多线程(multithreaded)方法。这相比顺序连接每个端口,能大大提高扫描速度。

3. 实现

为了实现我们的程序,我们创建一个带有两个参数的函数portScan()

  • ip:要扫描的IP地址,对于localhost来说,相当于127.0.0.1
  • nbrPortMaxToScan:要扫描的最大端口号数量,如果我们想扫描所有端口,这个数字等于65535

3.1. 实现

让我们看看portScan()方法的实现:

public void runPortScan(String ip, int nbrPortMaxToScan) throws IOException {
        ConcurrentLinkedQueue openPorts = new ConcurrentLinkedQueue<>();
        ExecutorService executorService = Executors.newFixedThreadPool(poolSize);
        AtomicInteger port = new AtomicInteger(0);
        while (port.get() < nbrPortMaxToScan) {
                final int currentPort = port.getAndIncrement();
                executorService.submit(() -> {
                        try {
                                Socket socket = new Socket();
                                socket.connect(new InetSocketAddress(ip, currentPort), timeOut);
                                socket.close();
                                openPorts.add(currentPort);
                                System.out.println(ip + " ,port open: " + currentPort);
                        } catch (IOException e) {
                                System.err.println(e);
                        }
                });
        }
        executorService.shutdown();
        try {
                executorService.awaitTermination(10, TimeUnit.MINUTES);
        } catch (InterruptedException e) {
                throw new RuntimeException(e);
        }
        List openPortList = new ArrayList<>();
        System.out.println("openPortsQueue: " + openPorts.size());
        while (!openPorts.isEmpty()) {
                openPortList.add(openPorts.poll());
        }
        openPortList.forEach(p -> System.out.println("port " + p + " is open"));
}

该方法返回一个包含所有开放端口的列表。为此,我们创建一个新的Socket对象,作为两个主机之间连接的桥梁。如果连接成功建立,我们就假设端口是开放的,然后继续下一行。相反,如果连接失败,我们就假设端口是关闭的,并抛出一个SocketTimeoutException异常,然后进入异常处理块。

3.2. 多线程

为了优化扫描目标机器所有65535个端口所需的时间,我们将并行运行我们的方法。我们使用ExecutorService,它封装了一个线程池和一个任务队列,用于执行任务。池中的所有线程仍在运行。

服务检查队列中是否有待处理的任务,如果有,就取出任务并执行。任务执行完毕后,线程再次等待服务从队列中分配新的任务。

此外,我们使用一个具有10个线程的FixedThreadPool,这意味着程序最多将并行运行10个线程。我们可以根据我们的机器配置和能力调整线程池大小。

4. 总结

在这篇简短的教程中,我们解释了如何使用Socket和多线程方法开发一个简单的Java端口扫描应用程序

如往常一样,代码片段可在GitHub上找到。