編寫具有 Windows 用戶端-伺服器實用程式功能的軟體,第 01 部分

問候。

今天,我想了解一下編寫客戶端-伺服器應用程式的過程,這些應用程式執行標準 Windows 實用程式的功能,例如用純 Java 編寫的 Telnet、TFTP 等。 很明顯,我不會帶來任何新內容 - 所有這些實用程式都已成功運行一年多,但我相信並不是每個人都知道幕後發生了什麼。

這正是將在削減中討論的內容。

在本文中,為了不拖沓,除了一般資訊之外,我將只寫有關 Telnet 伺服器的內容,但目前還有有關其他實用程式的資料 - 它將在本系列的後續部分中。

首先你要弄清楚Telnet是什麼,需要它做什麼,有什麼用。 我不會逐字引用來源(如有必要,我會在文章末尾附上有關該主題的資料的連結),我只會說 Telnet 提供了對裝置命令列的遠端存取。 總的來說,這就是它的功能結束的地方(我故意對訪問伺服器連接埠保持沉默;稍後會詳細介紹)。 這意味著要實現它,我們需要在客戶端接受一行,將其傳遞給伺服器,嘗試將其傳遞給命令行,讀取命令列回應,如果有,則將其傳遞回客戶端,然後將其顯示在螢幕上,或者,如果發生錯誤,請讓使用者知道出現了問題。

因此,為了實現上述內容,我們需要 2 個工作類和一些測試類,我們將透過它們啟動伺服器並透過它們來運行客戶端。
因此,目前應用程式結構包括:

  • Telnet客戶端
  • Telnet客戶端測試儀
  • 遠端登入伺服器
  • Telnet伺服器測試儀

讓我們逐一討論:

Telnet客戶端

這個類別應該能夠做的就是發送收到的命令並顯示收到的回應。 此外,您需要能夠連接到遠端設備的任意(如上所述)連接埠並與其斷開連接。

為了實現這一目標,實現了以下功能:

一個函數,它將套接字地址作為參數,打開連接並啟動輸入和輸出流(流變量在上面聲明,完整的源代碼在文章末尾)。

 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());
        }
    }

重載相同的函數,連接到預設連接埠 - 對於 telnet,這是 23


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

此函數從鍵盤讀取字元並將它們發送到輸出套接字 - 這是典型的行模式,而不是字元模式:


    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());
        }
    }

此函數從socket接收資料並顯示在螢幕上


    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());
        }
    }

此函數停止資料接收和發送


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

遠端登入伺服器

此類必須具有從套接字接收命令、發送命令以供執行以及將命令的回應發送回套接字的功能。 該程式故意不檢查輸入數據,因為首先,即使在“盒裝telnet”中也可以格式化伺服器磁碟,其次,本文原則上忽略了安全問題,這就是為什麼沒有關於加密或 SSL 的一些話。

只有 2 個函數(其中一個已重載),一般來說這不是一個很好的做法,但出於此任務的目的,我認為保留所有內容原樣似乎是合適的。

 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();

    }

該程式打開伺服器端口,從中讀取數據,直到遇到命令結束字符,將命令傳遞給新進程,並將輸出從進程重定向到套接字。 一切都像卡拉什尼科夫突擊步槍一樣簡單。

因此,使用預設連接埠的此函數存在重載:

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

好吧,相應地,停止伺服器的函數也是微不足道的,它會中斷永恆循環,違反其條件。

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

我不會在這裡給出測試類,它們在下面 - 它們所做的只是檢查公共方法的功能。 一切都在 git 上。

總而言之,在幾個晚上您就可以了解主控制台實用程式的操作原理。 現在,當我們遠端連接到遠端電腦時,我們就明白發生了什麼 - 魔法已經消失了)

所以,連結:
所有來源過去、現在和將來都在這裡
關於遠端登入
關於遠端登入的更多信息

來源: www.habr.com

添加評論