JavaFx -- chapter09(网络扫描程序)

chapter09(网络扫描程序)

教学与实践目的:学会基本的网络扫描程序设计技术。

目标主机扫描是网络功防的基础和前提,扫描探测一台目标主机包括:确定该目标主机是否活动、目标主机的操作系统、正在使用哪些端口、对外提供了哪些服务、相关服务的软件版本等等,对这些内容的探测就是为了"对症下药",为攻防提供参考信息。

对主机的探测工具非常多,比如大名鼎鼎的nmap、netcat、superscan, 以及国内的x-scanner等等。我们自己动手做简单扫描软件或工具,用于加深对网络编程的理解。

知识点

  • new Socket()
  • Process类
  • ICMP报文

主机扫描(远程主机探测)

通过指定的IP地址范围,发现该范围中活跃的主机,例如指定 192.168.0.15-192.168.1.100 范围。

新建Java包,命名chapter09,创建主机扫描程序HostScannerFX.java, 窗口界面如图9.1所示,并在"主机扫描"按钮中设置主机探测关键代码,例如类似代码:

InetAddress addr = InetAddress.getByName(host);//host 为 IP 地址 
boolean status=addr.isReachable(timeOut);// timeOut 为等待时间(毫秒为单位,例如可以设置为500,如果网络环境拥塞或网速不够,适当增加该值,以免错将活跃主机测试为无效主机)

在这里插入图片描述

指定ip地址范围之间的遍历

更好的一种思路就是将ip地址转换为整数形式,一个网段就是0-255之间,刚好就是1个字节的范围,四个网段表示就需要四个字节。ip地址范围的遍历就转为在两个数字之间for循环遍历,循环体中将每一个数字转换回ip地址进行处理即可。所以关键就在于实现ip与数字之间的互相转换,基本原理阐述如下:

假设ip地址为 192.168.234.3: 每个网段都用二进制表示: 192(10) = 11000000 (2) ; 168(10) = 10101000 (2) ; 234(10) = 11101010 (2) ; 3(10) =00000011 (2) ;所以连在一起就是:11000000101010001110101000000011,对应的十进制数字就是3232295427。具体实现的算法分析:

在这里插入图片描述

这些操作需要用到java的位运算操作,例如左移<<右移>>位与&位或|;将上述转换后的数字转换回ip地址,其实就是上述过程的逆过程(在具体运算中要用到和0xff与的操作,用于将高位置0)。

另外要注意的是,四个字节刚好是int类型,但Java不像C语言,没有无符号整数,最高位是符号位,当最左边的ip地址部分从128开始就变成了负数 (例如上面的192.168.234.3,192转为二进制是11000000,在java中,最高位1是为符号位使用,1就表示是负数了,所以使用Java的int类型,转换后的结果就不是3232295427,而是-1062671869),为了转换的数字能够大小连续变化,可以换成long类型来存储ip转换的数字,就不会出现负数的情况。

public long ipToLong(String ip) {
    String[] ipArray = ip.split("\\.");
    long num = 0;
    for (int i = 0; i < ipArray.length; i++) {

        long valueOfSection = Long.parseLong(ipArray[i]);
        num = (valueOfSection << 8 * (3 - i)) | num;
    }
    return num;
}

/**
* 长整型转ip
*/
public String longToIp(long i) {
    //右移,并将高位置0 18
    return ((i >> 24) & 0xFF) + "." +
        ((i >> 16) & 0xFF) + "." +
        ((i >> 8) & 0xFF) + "." +
        (i & 0xFF);
}

注意:主机扫描是个耗时操作,为了避免程序失去响应,"主机扫描"按钮中的代码应该放在一个新线程中执行

在Java中执行其它应用程序

可以使用包括PING、TRACERT等命令来实现ICMP相关扫描等功能,当然还有其他有关的命令行程序,可以在我们的程序中实现执行其他外部 程序的功能,这样就可以把一些功能集成到本程序。在Java中执行其它应用程序,要用到Process类和Runtime类,大家可以自行查找其相关用法,部分核心代码如下:

在Java中,确实可以使用 Process 类和 Runtime 类来执行外部程序,包括但不限于 pingtracert(在Windows中)或 traceroute(在Unix/Linux系统中)等命令。这些命令通常用于网络诊断,比如检测网络连接、路由路径等。

以下是一些基本的示例代码,展示如何在Java程序中执行这些外部命令:

使用 Runtime 类执行外部命令
try {
    // 在Windows上执行ping命令
    String os = System.getProperty("os.name").toLowerCase();
    if (os.contains("win")) {
        Runtime.getRuntime().exec("ping example.com");
    } else {
        // 在Unix/Linux上执行ping命令
        Runtime.getRuntime().exec("ping -c 4 example.com");
    }
} catch (Exception e) {
    e.printStackTrace();
}
使用 ProcessBuilder 类执行外部命令并获取输出

ProcessBuilder 类提供了更灵活的方式来执行外部命令,并且可以更容易地获取命令的输出。

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class ExternalCommandExample {
    public static void main(String[] args) {
        try {
            // 使用ProcessBuilder执行ping命令
            ProcessBuilder processBuilder = new ProcessBuilder("ping", "example.com");
            processBuilder.redirectErrorStream(true); // 合并标准输出和错误输出

            Process process = processBuilder.start();

            BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }

            int exitCode = process.waitFor();
            if (exitCode == 0) {
                System.out.println("Command executed successfully");
            } else {
                System.out.println("Command execution failed");
            }
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }
    }
}
注意事项
  1. 安全性:执行外部命令时,如果命令或参数来自不可信的源,可能会引发安全问题。确保对输入进行适当的验证和清理。

  2. 平台依赖性:不同的操作系统可能有不同的命令和参数。在上面的例子中,我们根据操作系统的不同选择了不同的命令参数。

  3. 错误处理:执行外部命令时,应该妥善处理可能发生的异常,比如 IOExceptionInterruptedException

  4. 输出处理:使用 ProcessBuilder 可以更容易地获取命令的输出,这对于诊断和日志记录非常有用。

同样,很多命令行程序执行时间较长,例如netstat命令,所以"命令执行"按钮中的代码也需要放在新线程中执行。

HostScannerFx.java
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.InetAddress;

public class HostScannerFx extends Application {

    private final TextArea showArea = new TextArea(); // 显示扫描结果的TextArea组件

    private final TextField startField = new TextField(); // 输入起始地址的TextField组件
    private final TextField endField = new TextField(); // 输入结束地址的TextField组件
    private final Button scanButton = new Button("主机扫描"); // 扫描按钮
    private final TextField inputcmd = new TextField(); // 输入命令的TextField组件
    private final Button execButton = new Button("执行命令"); // 执行命令按钮

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        // 设置窗口标题
        primaryStage.setTitle("Host Scanner");

        BorderPane mainPane = new BorderPane(); // 创建BorderPane作为主容器

        VBox vbox1 = new VBox(10);
        vbox1.setPadding(new Insets(10, 10, 10, 10));
        vbox1.getChildren().addAll(new Label("扫描结果:"), showArea);
        VBox.setVgrow(vbox1, Priority.ALWAYS);
        VBox.setVgrow(showArea, Priority.ALWAYS);

        HBox hbox1 = new HBox(10);
        hbox1.setPadding(new Insets(10, 10, 10, 10));
        hbox1.getChildren().addAll(new Label("起始地址:"), startField, new Label("结束地址:"), endField, scanButton);


        HBox hbox2 = new HBox(10);
        hbox2.setPadding(new Insets(10, 10, 10, 10));
        hbox2.getChildren().addAll(new Label("输入命令:"), inputcmd, execButton);
        HBox.setHgrow(hbox2, Priority.ALWAYS);
        HBox.setHgrow(inputcmd, Priority.ALWAYS);

        VBox mainVBox = new VBox(10);
        mainVBox.setPadding(new Insets(10, 10, 10, 10));
        mainVBox.getChildren().addAll(vbox1, hbox1, hbox2);
        mainPane.setCenter(mainVBox); // 设置主容器的中心为mainVBox

        showArea.setEditable(false); // 设置TextArea不可编辑
        showArea.setWrapText(true); // 设置TextArea自动换行

        Scene scene = new Scene(mainPane, 800, 600); // 创建Scene对象,设置大小
        primaryStage.setScene(scene);
        startField.setText("192.168.128.1"); // 设置默认起始地址
        endField.setText("192.168.128.254"); // 设置默认结束地址
        // 显示窗口
        primaryStage.show();


        scanButton.setOnAction(event -> {
            String start = startField.getText();
            String end = endField.getText();
            showArea.clear();

            Thread scanThread = new Thread(() -> {
                String startip = startField.getText();
                String endip = endField.getText();
                long startnum = ipToLong(startip);
                long endnum = ipToLong(endip);
                for (long i = startnum; i <= endnum; i++) {
                    String ip = longToIp(i);
                    try {
                        InetAddress address = InetAddress.getByName(ip);
                        boolean status = address.isReachable(5000);// timeOut 为等待时间
                        Platform.runLater(() -> showArea.appendText(ip + " : " + ((status) ? "Reachable" : "Unreachable") + "\n"));
                    } catch (Exception e) {
                        Platform.runLater(() -> showArea.appendText(ip + " : " + "Unknown host" + "\n"));
                    }
                }
            }, "Host Scanner");
            scanThread.start();
        });

        execButton.setOnAction(event -> {
            String cmd = inputcmd.getText();
            showArea.clear();
            Thread execThread = new Thread(() -> {
                try {
                    Process process = Runtime.getRuntime().exec(cmd);
//                    process.waitFor(); // 等待命令执行完成
                    InputStream inputStream = process.getInputStream(); // 获取输入流
                    BufferedReader br = new BufferedReader(new InputStreamReader(inputStream, "GBK"));
                    String msg = null;
                    while ((msg = br.readLine()) != null) {
                        String  msgTmp = msg;
                        Platform.runLater(() -> showArea.appendText(msgTmp + "\n"));
                    }
                } catch (Exception e) {
                    Platform.runLater(() -> showArea.appendText("命令执行失败"));
                }
            }, "Command Executor");
            execThread.start();
        });
    }

    public long ipToLong(String ip) {
        String[] ipArray = ip.split("\\.");
        long num = 0;
        for (int i = 0; i < ipArray.length; i++) {

            long valueOfSection = Long.parseLong(ipArray[i]);
            num = (valueOfSection << 8 * (3 - i)) | num;
        }
        return num;
    }

    /**
     * 长整型转ip
     */
    public String longToIp(long i) {
//右移,并将高位置0 18
        return ((i >> 24) & 0xFF) + "." +
                ((i >> 16) & 0xFF) + "." +
                ((i >> 8) & 0xFF) + "." +
                (i & 0xFF);
    }
}

端口扫描

扫描目的主机开放的端口是常见操作:攻(寻找目的主机开放的端口)与 防(检测本机异常开放的端口)。基本的端口扫描可使用的技术:

  • New Socket(host, port);
  • TCP Connect 端口扫描;

创建端口扫描程序PortScannerFX.java,参考主界面如图9.2所示:

在这里插入图片描述

PortScannerFx.java
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.scene.control.Button;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;

public class PortScanner extends Application {
    private final TextArea resultArea = new TextArea();
    private final TextField ipField = new TextField();
    private final TextField startPortField = new TextField();
    private final TextField endPortField = new TextField();

    private final Button scanButton = new Button("扫描");
    private final Button quickScanButton = new Button("快速扫描");
    // 多线程扫描
    private final Button multiScanButton = new Button("多线程扫描");
    private final Button quitButton = new Button("退出");
    private static AtomicInteger portCount = new AtomicInteger(0);//portCount用于统计已扫描的端口数

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        primaryStage.setTitle("端口扫描程序");

        BorderPane mainPane = new BorderPane();

        VBox resultBox = new VBox(10);
        resultBox.setPadding(new Insets(10, 10, 10, 10));
        resultBox.getChildren().addAll(new Label("端口扫描结果:" + "\n"), resultArea);
        resultArea.setEditable(false);
        resultArea.setWrapText(true);
        VBox.setVgrow(resultArea, Priority.ALWAYS);
        VBox.setVgrow(resultBox, Priority.ALWAYS);

        HBox settingBox = new HBox(10);
        settingBox.setPadding(new Insets(10, 10, 10, 10));
        settingBox.getChildren().addAll(new Label("目标主机IP:"), ipField, new Label("起始端口:"), startPortField, new Label("结束端口:"), endPortField);


        HBox buttonBox = new HBox(20);
        buttonBox.setPadding(new Insets(10, 10, 10, 10));
        buttonBox.getChildren().addAll(scanButton, quickScanButton, multiScanButton, quitButton);
        buttonBox.setAlignment(Pos.CENTER);

        VBox mainBox = new VBox(10);
        mainBox.getChildren().addAll(resultBox, settingBox, buttonBox);

        mainPane.setCenter(mainBox);

        Scene scene = new Scene(mainPane, 700, 500); // 创建Scene对象,设置大小
        primaryStage.setScene(scene);

        ipField.setText("127.0.0.1");
        startPortField.setText("1");
        endPortField.setText("1000");
        primaryStage.show();

        this.scanButton.setOnAction(event -> {
            Thread scanThread = new Thread(() -> {
                String ip = ipField.getText();
                int startPort = Integer.parseInt(startPortField.getText());
                int endPort = Integer.parseInt(endPortField.getText());
                for (int port = startPort; port <= endPort; port++) {
                    try {
                        Socket socket = new Socket(ip, port);
                        socket.close();
                        int finalPort = port;
                        Platform.runLater(() -> resultArea.appendText("端口" + finalPort + ":开放\n"));
                    } catch (Exception e) {
                        // 端口关闭
                        int closedPort = port;
                        Platform.runLater(() -> resultArea.appendText("端口" + closedPort + ":关闭\n"));
                    }
                }
            }, "scanThread");
            scanThread.start();
        });
        this.quickScanButton.setOnAction(event -> {
            Thread quickScanThread = new Thread(() -> {
                String ip = ipField.getText();
                int startPort = Integer.parseInt(startPortField.getText());
                int endPort = Integer.parseInt(endPortField.getText());
                for (int port = startPort; port <= endPort; port++) {
                    try {
                        Socket socket = new Socket();
                        socket.connect(new InetSocketAddress(ip, port), 300);
                        socket.close();
                        int finalPort = port;
                        Platform.runLater(() -> resultArea.appendText("端口" + finalPort + ":开放\n"));
                    } catch (Exception e) {
                        // 端口关闭
                        int closedPort = port;
                        Platform.runLater(() -> resultArea.appendText("端口" + closedPort + ":关闭\n"));
                    }
                }
            }, "quickScanThread");
            quickScanThread.start();
        });
        this.quitButton.setOnAction(
                event -> {
                    Platform.exit();
                    System.exit(0);
                }
        );
        this.multiScanButton.setOnAction(event -> {
            int startPort = Integer.parseInt(startPortField.getText());
            int endPort = Integer.parseInt(endPortField.getText());
            ExecutorService executor = Executors.newFixedThreadPool(10);
            for (int i = 0; i < 10; i++) {
                int start = startPort + i * (endPort - startPort + 1) / 10;
                int end = startPort + (i + 1) * (endPort - startPort + 1) / 10;
                executor.execute(new MultiThreadScannerHandler(ipField.getText(), i, 10, start, end));
            }
        });
    }

    class MultiThreadScannerHandler implements Runnable {
        private int totalThreadNum;//用于端口扫描的总共线程数,默认为10
        private int threadNo;//线程号,表示第几个线程
        private final String host; //扫描的主机ip
        private int startPort; //扫描的起始端口
        private int endPort; //扫描的结束端口

        public MultiThreadScannerHandler(String host, int threadNo, int startPort, int endPort) {
            this.totalThreadNum = 10;
            this.host = host;
            this.threadNo = threadNo;
            this.startPort = startPort;
            this.endPort = endPort;
        }

        public MultiThreadScannerHandler(String host, int threadNo, int totalThreadNum, int startPort, int endPort) {
            this.totalThreadNum = totalThreadNum;
            this.host = host;
            this.threadNo = threadNo;
            this.startPort = startPort;
            this.endPort = endPort;

        }

        @Override
        public void run() {
            // startPort和endPort为外部类的成员变量,表示需要扫描的起止端口
            for (int port = startPort + threadNo; port <= endPort; port += totalThreadNum) {
                try {
                    Socket socket = new Socket();
                    socket.connect(new InetSocketAddress(host, port), 1000);
                    socket.close();
                    String msg = host + "端口 " + port + " is open\n";
                    Platform.runLater(() -> {
                        resultArea.appendText(msg);
                    });
                } catch (IOException e) {
                    // 不输出关闭的端口信息
                    /*
                    String msg = host + "端口 " + port + " is closed\n";
                    Platform.runLater(() -> {
                        taDisplay.appendText(msg);
                    });
                    */
                }
                portCount.incrementAndGet(); // 扫描的端口数 +1
            }

                /* 如果全部扫描完成,端口扫描数量置0,并提示扫描结束
                   注意,如果使用下面这种方式,在多线程下不能保证操作的原子性
                   if (portCount.get() == (endPort - startPort + 1)) {
                       portCount.set(0);
                       ......
                   }
                */

            if (portCount.compareAndSet(endPort - startPort + 1, 0)) {
                Platform.runLater(() -> {
                    resultArea.appendText("----------------多线程扫描结束--------------\n");
                });
            }
        }
    }
}

拓展练习

优雅的停止多余线程
  1. 使用线程技术,使得按钮执行不会卡死主界面,但如果用户再次点击按钮执行新任务,前面的没有完成的线程任务输出就会和后面任务的输出内容混杂显示在一起,请改进这两个程序,使得每次点击按钮时,先关闭之前执行的扫描线程。注意,不要使用Thread.stop()方法,该方法不安全,已经被废弃。大家搜索有关ThreadGroup类、Thread.currentThread()、线程对象的 interrupt()、isInterrupted()等用法,实现以上需求;
ThreadGroup 类用法

ThreadGroup 是 Java 中用来表示线程组的类,可以对线程进行分组管理。每个线程都属于一个线程组,线程组可以包含线程和其他线程组,形成树形结构。您可以创建一个线程组,并将线程添加到该组中,以便于管理。 2

public class ThreadGroupTest {
    public static void main(String[] args) {
        ThreadGroup rootThreadGroup = new ThreadGroup("root线程组");
        Thread thread0 = new Thread(rootThreadGroup, new MRunnable(), "线程A");
        Thread thread1 = new Thread(rootThreadGroup, new MRunnable(), "线程B");
        thread0.start();
        thread1.start();
    }
}

class MRunnable implements Runnable {
    @Override
    public void run() {
        while (!Thread.currentThread().isInterrupted()) {
            System.out.println("线程名: " + Thread.currentThread().getName() + ", 所在线程组: " + Thread.currentThread().getThreadGroup().getName());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
Thread.currentThread() 用法

Thread.currentThread() 方法用于获取当前执行的线程对象。这在多线程环境中非常有用,尤其是在需要获取当前线程状态或者名称时。

interrupt()isInterrupted() 方法用法

interrupt() 方法用于请求终止线程,而 isInterrupted() 方法用于检查线程是否被中断。这两个方法配合使用可以实现线程的优雅停止。

public class ThreadDemo9 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            while (!Thread.currentThread().isInterrupted()) {
                System.out.println("hello thread!!");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("线程 t 工作完毕");
        });
        t.start();
        Thread.sleep(3000);
        t.interrupt();
        System.out.println("让 t 线程结束");
    }
}
安全停止线程的实践

在实际应用中,我们可以通过设置一个标志位来控制线程的运行,结合 interrupt() 方法来安全地停止线程。

public class Test {
    public static void main(String[] args) {
        SystemMonitor sm = new SystemMonitor();
        sm.start();
        try {
            Thread.sleep(10 * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("监控任务启动 10 秒后,停止...");
        sm.stop();
    }
}

class SystemMonitor {
    private Thread t;
    private volatile boolean stop = false;

    void start() {
        t = new Thread(() -> {
            while (!stop) {
                System.out.println("正在监控系统...");
                try {
                    Thread.sleep(3 * 1000L);
                    System.out.println("任务执行 3 秒");
                    System.out.println("监控的系统正常!");
                } catch (InterruptedException e) {
                    System.out.println("任务执行被中断...");
                    Thread.currentThread().interrupt();
                }
            }
        });
        t.start();
    }

    void stop() {
        stop = true;
        t.interrupt();
    }
}
TextField组件的宽度设置

在JavaFX中,设置TextField的宽度可以通过几种不同的方法来实现:

  1. 使用CSS样式
    您可以为TextField设置CSS样式来指定宽度。例如:

    .text-field {
        -fx-min-width: 200px; /* 最小宽度 */
        -fx-max-width: 200px; /* 最大宽度 */
        -fx-width: 200px; /* 固定宽度 */
    }
    

    然后在Java代码中应用这个样式:

    TextField textField = new TextField();
    textField.getStyleClass().add("text-field");
    
  2. 使用setPrefColumnCount方法
    TextField有一个setPrefColumnCount方法,您可以设置期望的列数,JavaFX会根据列数和字体大小自动计算宽度。

    TextField textField = new TextField();
    textField.setPrefColumnCount(20); // 设置期望的列数
    
  3. 使用setPrefWidth方法
    您可以直接设置TextField的首选宽度:

    TextField textField = new TextField();
    textField.setPrefWidth(200); // 设置首选宽度为200像素
    
  4. 使用布局约束
    如果您使用的是JavaFX的布局管理器(如VBoxHBoxGridPane等),您可以在将TextField添加到布局时设置宽度约束。

    例如,在GridPane中:

    GridPane gridPane = new GridPane();
    TextField textField = new TextField();
    gridPane.add(textField, 0, 0);
    GridPane.setConstraints(textField, 0, 0, 1, 1); // 设置位置
    GridPane.setColumnSpan(textField, 2); // 设置列跨度
    GridPane.setHgrow(textField, Priority.ALWAYS); // 设置水平生长优先级
    

    HBoxVBox中,您可以使用HBox.setHgrowVBox.setVgrow方法来设置:

    HBox hbox = new HBox();
    TextField textField = new TextField();
    hbox.getChildren().add(textField);
    HBox.setHgrow(textField, Priority.ALWAYS); // 设置水平生长优先级
    
  5. 使用setMaxWidthsetMinWidth方法
    您还可以设置TextField的最大宽度和最小宽度:

    TextField textField = new TextField();
    textField.setMaxWidth(Double.MAX_VALUE); // 设置最大宽度
    textField.setMinWidth(100); // 设置最小宽度
    

HostAndPortScanner.java

package chapter09;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.text.DecimalFormat;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;

public class HostAndPortScanner extends Application {

  private final TextArea showArea = new TextArea();
  private final TextField startField = new TextField();
  private final TextField endField = new TextField();
  private final Button scanButton = new Button("扫描");
  private final TextField startPort = new TextField();
  private final TextField endPort = new TextField();
  private final TextField inputcmd = new TextField();
  private final Button execButton = new Button("执行命令");
  private final Button stopButton = new Button("停止");
  private Label lblProgress = new Label("");
  private ProgressBar bar = new ProgressBar();

  private AtomicInteger hostCount = new AtomicInteger(0);
  private ExecutorService executorService;
  private volatile boolean isRunning = false;

  public static void main(String[] args) {
    launch(args);
  }

  @Override
  public void start(Stage primaryStage) throws Exception {
    primaryStage.setTitle("Better Scanner");
    BorderPane mainPane = new BorderPane();

    VBox vbox1 = new VBox(10);
    vbox1.setPadding(new Insets(10, 10, 10, 10));
    vbox1.getChildren().addAll(new Label("扫描结果:"), showArea);
    VBox.setVgrow(vbox1, Priority.ALWAYS);
    VBox.setVgrow(showArea, Priority.ALWAYS);

    //进度条的界面布局
    HBox hBoxBar = new HBox();
    hBoxBar.setSpacing(10);
    hBoxBar.setPadding(new Insets(10, 0, 10, 0));
    hBoxBar.setAlignment(Pos.CENTER);
    //lblProgress 和 bar 分别为Label和ProgressBar 类型的成员变量
    hBoxBar.getChildren().addAll(lblProgress, bar);
//以下下两条语句使得进度条水平扩展
    bar.setMaxWidth(Double.MAX_VALUE);
    HBox.setHgrow(bar, Priority.ALWAYS);

    HBox hbox1 = new HBox(10);
    hbox1.setPadding(new Insets(10, 10, 10, 10));
    startField.setPrefColumnCount(8);
    endField.setPrefColumnCount(8);
    startPort.setPrefColumnCount(5);
    endPort.setPrefColumnCount(5);
    hbox1.getChildren().addAll(new Label("起始地址:"), startField, new Label("结束地址:"), endField, new Label("起始port:"), startPort, new Label("结束port"), endPort, scanButton);

    HBox hbox2 = new HBox(10);
    hbox2.setPadding(new Insets(10, 10, 10, 10));
    hbox2.getChildren().addAll(new Label("输入命令:"), inputcmd, execButton);
    HBox.setHgrow(hbox2, Priority.ALWAYS);
    HBox.setHgrow(inputcmd, Priority.ALWAYS);

    VBox mainVBox = new VBox(10);
    mainVBox.setPadding(new Insets(10, 10, 10, 10));
    mainVBox.getChildren().addAll(vbox1, hbox1, hbox2,hBoxBar, stopButton);
    mainPane.setCenter(mainVBox);

    showArea.setEditable(false);
    showArea.setWrapText(true);

    Scene scene = new Scene(mainPane, 900, 550);
    primaryStage.setScene(scene);
    startField.setText("192.168.128.1");
    endField.setText("192.168.128.254");
    primaryStage.show();


    scanButton.setOnAction(event -> {
      if (isRunning) {
        stopButton.fire();
      }
      isRunning = true;
      showArea.clear();
      executorService = Executors.newFixedThreadPool(10);
      long starthost = ipToLong(startField.getText());
      long endhost = ipToLong(endField.getText());
      for (int i = 0; i < 10; i++) {
        long startnum = starthost + (long) Math.ceil(i * 1.0 * (endhost - starthost + 1) / 10);
        long endnum = (i == 9) ? endhost : starthost + (long) Math.ceil((i + 1) * 1.0 * (endhost - starthost + 1) / 10) - 1;
        executorService.execute(new MultiThreadHostScanner(startnum, endnum));
      }
    });

    stopButton.setOnAction(event -> {
      isRunning = false;
      if (executorService != null) {
        executorService.shutdownNow();
      }
    });

    execButton.setOnAction(event -> {
      if (isRunning) {
        stopButton.fire();
      }
      isRunning = true;
      showArea.clear();
      Thread execThread = new Thread(() -> {
        try {
          Process process = Runtime.getRuntime().exec(inputcmd.getText());
          InputStream inputStream = process.getInputStream();
          BufferedReader br = new BufferedReader(new InputStreamReader(inputStream, "GBK"));
          String msg;
          while ((msg = br.readLine()) != null) {
            String finalMsg = msg;
            Platform.runLater(() -> showArea.appendText(finalMsg + "\n"));
          }
        } catch (Exception e) {
          Platform.runLater(() -> showArea.appendText("命令执行失败\n"));
        }
      }, "Command Executor");
      execThread.start();
    });
  }


  public long ipToLong(String ip) {
    String[] ipArray = ip.split("\\.");
    long num = 0;
    for (int i = 0; i < ipArray.length; i++) {
      long valueOfSection = Long.parseLong(ipArray[i]);
      num = (valueOfSection << 8 * (3 - i)) | num;
    }
    return num;
  }

  public String longToIp(long i) {
    return ((i >> 24) & 0xFF) + "." +
      ((i >> 16) & 0xFF) + "." +
      ((i >> 8) & 0xFF) + "." +
      (i & 0xFF);
  }

  class MultiThreadHostScanner implements Runnable {
    private long startHost;
    private long endHost;

    public MultiThreadHostScanner(long startHost, long endHost) {
      this.startHost = startHost;
      this.endHost = endHost;
    }
    private void progressChange() {
      hostCount.incrementAndGet(); // 扫描的端口数+1
      double progress = 1.0 * hostCount.get() / (endHost - startHost + 1);
      Platform.runLater(() -> {
        bar.setProgress(progress);
        DecimalFormat df = new DecimalFormat("0%");
        lblProgress.setText(df.format(progress));
      });
    }

    @Override
    public void run() {
      if (!isRunning) return;
      StringBuilder output = new StringBuilder();
      StringBuilder portOutput = new StringBuilder();
      for (long host = startHost; host <= endHost; host++) {
        if (!isRunning) return;
        try {
          InetAddress address = InetAddress.getByName(longToIp(host));
          boolean status = address.isReachable(1000);
          if (status) {
            int startport = Integer.parseInt(startPort.getText());
            int endport = Integer.parseInt(endPort.getText());
            ExecutorService portExecutorService = Executors.newFixedThreadPool(10);
            for (int i = 0; i < 10; i++) {
              int start = startport + i * (endport - startport + 1) / 10;
              int end = startport + (i + 1) * (endport - startport + 1) / 10;
              long finalHost = host;
              portExecutorService.execute(() -> {
                if (!isRunning) return;
                for (int port = start; port < end; port++) {
                  Socket socket = new Socket();
                  try {
                    socket.connect(new InetSocketAddress(longToIp(finalHost), port), 1000);
                    socket.close();
                    synchronized (portOutput) {
                      portOutput.append(longToIp(finalHost)).append(":").append(port).append(" is open\n");
                    }
                  } catch (IOException e) {
                    synchronized (portOutput) {
                      portOutput.append(longToIp(finalHost)).append(":").append(port).append(" is closed\n");
                    }
                  }
                }
              });
            }
            portExecutorService.shutdown();
            while (!portExecutorService.isTerminated()) {
              if (!isRunning) {
                portExecutorService.shutdownNow();
                break;
              }
            }
          }
          String statusMessage = status ? "Reachable" : "Unreachable";
          output.append(longToIp(host)).append(" : ").append(statusMessage).append("\n");
        } catch (Exception e) {
          output.append(longToIp(host)).append(" : Exception occurred\n");
        }
      }
      Platform.runLater(() -> {
        synchronized (showArea) {
          showArea.appendText(output.toString());
          showArea.appendText(portOutput.toString());
        }
      });
      progressChange();
    }
  }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

0zxm

祝大家天天开心

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值