Viết phần mềm với chức năng tiện ích client-server của Windows, phần 01

Hoan nghênh.

Hôm nay tôi muốn xem xét quá trình viết các ứng dụng máy khách-máy chủ thực hiện các chức năng của các tiện ích Windows tiêu chuẩn, chẳng hạn như Telnet, TFTP, v.v., v.v. bằng Java thuần túy. Rõ ràng là tôi sẽ không mang đến bất cứ điều gì mới - tất cả những tiện ích này đã hoạt động thành công trong hơn một năm, nhưng tôi tin rằng không phải ai cũng biết điều gì đang diễn ra bên trong.

Đây chính xác là những gì sẽ được thảo luận dưới phần cắt giảm.

Trong bài viết này, để không kéo dài ra, ngoài thông tin chung, tôi sẽ chỉ viết về máy chủ Telnet, hiện tại cũng có tài liệu về các tiện ích khác - nó sẽ có trong các phần tiếp theo của loạt bài.

Trước hết, bạn cần tìm hiểu Telnet là gì, nó cần thiết để làm gì và dùng để làm gì. Tôi sẽ không trích dẫn nguồn nguyên văn (nếu cần, tôi sẽ đính kèm liên kết đến các tài liệu về chủ đề này ở cuối bài), tôi chỉ nói rằng Telnet cung cấp quyền truy cập từ xa vào dòng lệnh của thiết bị. Nhìn chung, đây là lúc chức năng của nó kết thúc (tôi cố tình giữ im lặng về việc truy cập vào cổng máy chủ; sẽ nói thêm về điều đó sau). Điều này có nghĩa là để triển khai nó, chúng ta cần chấp nhận một dòng trên máy khách, chuyển nó đến máy chủ, cố gắng chuyển nó đến dòng lệnh, đọc phản hồi dòng lệnh, nếu có, chuyển nó trở lại máy khách và hiển thị nó trên màn hình hoặc nếu có lỗi, hãy cho người dùng biết rằng có điều gì đó không ổn.

Theo đó, để thực hiện những điều trên, chúng ta cần 2 lớp làm việc và một số lớp thử nghiệm mà từ đó chúng ta sẽ khởi chạy máy chủ và thông qua đó máy khách sẽ hoạt động.
Theo đó, hiện tại cấu trúc ứng dụng bao gồm:

  • Khách hàng Telnet
  • TelnetKhách hàngTester
  • Máy chủ Telnet
  • Máy chủ TelnetTester

Chúng ta hãy đi qua từng người trong số họ:

Khách hàng Telnet

Tất cả những gì lớp này có thể làm là gửi các lệnh đã nhận và hiển thị các phản hồi đã nhận. Ngoài ra, bạn cần có khả năng kết nối với một cổng tùy ý (như đã đề cập ở trên) của thiết bị từ xa và ngắt kết nối khỏi thiết bị đó.

Để đạt được điều này, các chức năng sau đã được triển khai:

Một hàm lấy địa chỉ socket làm đối số, mở kết nối và bắt đầu các luồng đầu vào và đầu ra (các biến luồng được khai báo ở trên, nguồn đầy đủ ở cuối bài viết).

 public void run(String ip, int port)
    {
        try {
            Socket socket = new Socket(ip, port);
            InputStream sin = socket.getInputStream();
            OutputStream sout = socket.getOutputStream();
            Scanner keyboard = new Scanner(System.in);
            reader = new Thread(()->read(keyboard, sout));
            writer = new Thread(()->write(sin));
            reader.start();
            writer.start();
        }
        catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }

Quá tải chức năng tương tự, kết nối với cổng mặc định - đối với telnet thì đây là 23


    public void run(String ip)
    {
        run(ip, 23);
    }

Hàm đọc các ký tự từ bàn phím và gửi chúng đến ổ cắm đầu ra - điển hình là ở chế độ dòng, không phải chế độ ký tự:


    private void read(Scanner keyboard, OutputStream sout)
    {
        try {
            String input = new String();
            while (true) {
                input = keyboard.nextLine();
                for (char i : (input + " n").toCharArray())
                    sout.write(i);
            }
        }
        catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }

Hàm nhận dữ liệu từ socket và hiển thị lên màn hình


    private void write(InputStream sin)
    {
        try {
            int tmp;
            while (true){
                tmp = sin.read();
                System.out.print((char)tmp);
            }
        }
        catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }

Chức năng dừng nhận và truyền dữ liệu


    public void stop()
    {
        reader.stop();
        writer.stop();
    }
}

Máy chủ Telnet

Lớp này phải có chức năng nhận lệnh từ socket, gửi lệnh để thực thi và gửi phản hồi từ lệnh trở lại socket. Chương trình cố tình không kiểm tra dữ liệu đầu vào, bởi vì thứ nhất, ngay cả trong “telnet đóng hộp” vẫn có thể định dạng đĩa máy chủ, và thứ hai, vấn đề bảo mật trong bài viết này về nguyên tắc bị bỏ qua, và đó là lý do tại sao không có một lời về mã hóa hoặc SSL.

Chỉ có 2 chức năng (một trong số đó đã bị quá tải) và nhìn chung đây không phải là một cách thực hành tốt cho lắm, nhưng với mục đích của nhiệm vụ này, đối với tôi, có vẻ thích hợp để giữ nguyên mọi thứ.

 boolean isRunning = true;
    public void run(int port)    {

        (new Thread(()->{ try {
            ServerSocket ss = new ServerSocket(port); // создаем сокет сервера и привязываем его к вышеуказанному порту
            System.out.println("Port "+port+" is waiting for connections");

            Socket socket = ss.accept();
            System.out.println("Connected");
            System.out.println();

            // Берем входной и выходной потоки сокета, теперь можем получать и отсылать данные клиенту.
            InputStream sin = socket.getInputStream();
            OutputStream sout = socket.getOutputStream();

            Map<String, String> env = System.getenv();
            String wayToTemp = env.get("TEMP") + "tmp.txt";
            for (int i :("Connectednnr".toCharArray()))
                sout.write(i);
            sout.flush();

            String buffer = new String();
            while (isRunning) {

                int intReader = 0;
                while ((char) intReader != 'n') {
                    intReader = sin.read();
                    buffer += (char) intReader;
                }


                final String inputToSubThread = "cmd /c " + buffer.substring(0, buffer.length()-2) + " 2>&1";


                new Thread(()-> {
                    try {

                        Process p = Runtime.getRuntime().exec(inputToSubThread);
                        InputStream out = p.getInputStream();
                        Scanner fromProcess = new Scanner(out);
                        try {

                            while (fromProcess.hasNextLine()) {
                                String temp = fromProcess.nextLine();
                                System.out.println(temp);
                                for (char i : temp.toCharArray())
                                    sout.write(i);
                                sout.write('n');
                                sout.write('r');
                            }
                        }
                        catch (Exception e) {
                            String output = "Something gets wrong... Err code: "+ e.getStackTrace();
                            System.out.println(output);
                            for (char i : output.toCharArray())
                                sout.write(i);
                            sout.write('n');
                            sout.write('r');
                        }

                        p.getErrorStream().close();
                        p.getOutputStream().close();
                        p.getInputStream().close();
                        sout.flush();

                    }
                    catch (Exception e) {
                        System.out.println("Error: " + e.getMessage());
                    }
                }).start();
                System.out.println(buffer);
                buffer = "";

            }
        }
        catch(Exception x) {
            System.out.println(x.getMessage());
        }})).start();

    }

Chương trình mở cổng máy chủ, đọc dữ liệu từ cổng đó cho đến khi gặp ký tự kết thúc lệnh, chuyển lệnh sang một quy trình mới và chuyển hướng đầu ra từ quy trình đó đến ổ cắm. Mọi thứ đều đơn giản như súng trường tấn công Kalashnikov.

Theo đó, có tình trạng quá tải đối với chức năng này với cổng mặc định:

 public void run()
    {
        run(23);
    }

Chà, theo đó, chức năng dừng máy chủ cũng tầm thường, nó làm gián đoạn vòng lặp vĩnh cửu, vi phạm điều kiện của nó.

    public void stop()
    {
        System.out.println("Server was stopped");
        this.isRunning = false;
    }

Tôi sẽ không cung cấp các lớp kiểm tra ở đây, chúng ở bên dưới - tất cả những gì họ làm là kiểm tra chức năng của các phương thức công khai. Mọi thứ đều có trên git.

Tóm lại, trong một vài buổi tối, bạn có thể hiểu nguyên tắc hoạt động của các tiện ích trên bảng điều khiển chính. Bây giờ, khi chúng tôi gửi tele tới một máy tính từ xa, chúng tôi hiểu chuyện gì đang xảy ra - điều kỳ diệu đã biến mất)

Vì vậy, các liên kết:
Tất cả các nguồn đã, đang và sẽ ở đây
Giới thiệu về Telnet
Tìm hiểu thêm về Telnet

Nguồn: www.habr.com

Thêm một lời nhận xét