使用 Java 扫描端口的最快方法

2022-09-01 07:16:31

我做了一个非常简单的端口扫描仪,但它运行得太慢了,所以我正在寻找一种方法来使它扫描得更快。这是我的代码:

public boolean portIsOpen(String ip, int port, int timeout) {
    try {
        Socket socket = new Socket();
        socket.connect(new InetSocketAddress(ip, port), timeout);
        socket.close();
        return true;
    } catch (Exception ex) {
        return false;
    }
}

此代码测试特定端口是否在特定 IP 上打开。对于超时,我使用了最小值,因为当我降低时,它没有足够的时间来测试端口。200

它运行良好,但从0到65535扫描需要太多时间。有没有其他方法可以在不到5分钟的时间内从0扫描到65535?


答案 1

如果你需要65536个端口中每个200ms(在最坏的情况下,防火墙会阻止一切,从而使你达到每个端口的超时),数学很简单:你需要13k秒,或者大约3个半小时。

您有 2 个(非排他性)选项来加快速度:

  • 减少超时
  • 将代码化

由于操作是 I/O 绑定的(与 CPU 绑定相反,也就是说,您花时间等待 I/O,而不是等待一些巨大的计算完成),因此您可以使用许多线程。尝试从 20 开始。他们会将3个半小时分配给他们,因此最大预期时间约为10分钟。请记住,这将给另一方带来压力,即扫描的主机将看到具有“不合理”或“奇怪”模式的巨大网络活动,使扫描非常容易检测到。

最简单的方法(即,只需最少的更改)是使用ExecutorService和Future API:

public static Future<Boolean> portIsOpen(final ExecutorService es, final String ip, final int port, final int timeout) {
  return es.submit(new Callable<Boolean>() {
      @Override public Boolean call() {
        try {
          Socket socket = new Socket();
          socket.connect(new InetSocketAddress(ip, port), timeout);
          socket.close();
          return true;
        } catch (Exception ex) {
          return false;
        }
      }
   });
}

然后,您可以执行如下操作:

public static void main(final String... args) {
  final ExecutorService es = Executors.newFixedThreadPool(20);
  final String ip = "127.0.0.1";
  final int timeout = 200;
  final List<Future<Boolean>> futures = new ArrayList<>();
  for (int port = 1; port <= 65535; port++) {
    futures.add(portIsOpen(es, ip, port, timeout));
  }
  es.shutdown();
  int openPorts = 0;
  for (final Future<Boolean> f : futures) {
    if (f.get()) {
      openPorts++;
    }
  }
  System.out.println("There are " + openPorts + " open ports on host " + ip + " (probed with a timeout of " + timeout + "ms)");
}

如果您需要知道哪些端口是打开的(而不仅仅是有多少个,如上面的示例所示),则需要将函数的返回类型更改为 ,where 将保存端口和扫描结果,如下所示:Future<SomethingElse>SomethingElse

public final class ScanResult {
  private final int port;
  private final boolean isOpen;
  // constructor
  // getters
}

然后,在第一个代码段中更改为,并返回 或 代替BooleanScanResultnew ScanResult(port, true)new ScanResult(port, false)truefalse

编辑:实际上,我刚刚注意到:在这种特殊情况下,您不需要ScanResult类来保存结果+端口,并且仍然知道哪个端口是打开的。由于您将期货添加到有序的 List 中,并且稍后以与添加它们相同的顺序处理它们,因此您可以有一个计数器,该计数器将在每次迭代时递增,以了解您正在处理的端口。但是,嘿,这只是完整和精确。永远不要尝试这样做,这很可怕,我最羞愧的是我想到了这一点......使用 ScanResult 对象要干净得多,代码更易于阅读和维护,并且允许您稍后使用,例如,使用来改进扫描仪。CompletionService


答案 2

除了并行化扫描之外,您还可以使用更高级的端口扫描技术,例如此处介绍的(TCP SYN 和 TCP FIN 扫描):http://nmap.org/nmap_doc.html。可以在此处找到实现的VB代码:http://h.ackack.net/spoon-worlds-fastest-port-scanner.html

但是,为了使用这些技术,您需要使用原始 TCP/IP 套接字。为此,您应该使用 RockSaw 库。


推荐