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

新手上路,请多包涵

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

 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?

原文由 Rohit Malish 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 833
2 个回答

如果 65536 个端口中的每一个都需要 200 毫秒(在最坏的情况下,防火墙会阻止所有内容,从而使您对每个端口都超时),数学很简单:您需要 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)");
}

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

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

Then, change Boolean to ScanResult in the first snippet, and return new ScanResult(port, true) or new ScanResult(port, false) instead of just truefalse

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

原文由 Bruno Reis 发布,翻译遵循 CC BY-SA 3.0 许可协议

代码示例的灵感来自“Bruno Reis”

 class PortScanner {

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

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

public static class ScanResult {
    private int port;

    private boolean isOpen;

    public ScanResult(int port, boolean isOpen) {
        super();
        this.port = port;
        this.isOpen = isOpen;
    }

    public int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }

    public boolean isOpen() {
        return isOpen;
    }

    public void setOpen(boolean isOpen) {
        this.isOpen = isOpen;
    }

}
}

原文由 Jack 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题