Psací software s funkcemi obslužných programů klient-server Windows, část 01

Vítejte u nás.

Dnes bych se rád podíval na proces psaní klient-server aplikací, které plní funkce standardních Windows utilit, jako je Telnet, TFTP, atd., a tak dále v čisté Javě. Je jasné, že nepřinesu nic nového – všechny tyto utility fungují úspěšně více než rok, ale věřím, že ne každý ví, co se děje pod kapotou.

Právě o tom bude řeč pod střihem.

V tomto článku, abych to nenatahoval, budu kromě obecných informací psát pouze o Telnet serveru, ale v tuto chvíli jsou i materiály o dalších utilitách - budou v dalších dílech série.

Nejprve musíte zjistit, co je Telnet, k čemu je potřeba a k čemu se používá. Nebudu citovat zdroje doslovně (v případě potřeby připojím na konci článku odkaz na materiály k tématu), pouze řeknu, že Telnet poskytuje vzdálený přístup k příkazové řádce zařízení. Celkově tím jeho funkčnost končí (o přístupu k portu serveru jsem záměrně pomlčel; o tom později). To znamená, že k jeho implementaci potřebujeme přijmout řádek na klientovi, předat jej serveru, pokusit se jej předat příkazovému řádku, přečíst odpověď příkazového řádku, pokud existuje, předat jej zpět klientovi a zobrazte jej na obrazovce nebo v případě chyb dejte uživateli vědět, že něco není v pořádku.

K implementaci výše uvedeného tedy potřebujeme 2 pracovní třídy a nějakou testovací třídu, ze které spustíme server a přes kterou bude pracovat klient.
V souladu s tím v současné době struktura aplikace zahrnuje:

  • TelnetClient
  • TelnetClient Tester
  • TelnetServer
  • TelnetServerTester

Pojďme si projít každou z nich:

TelnetClient

Vše, co by tato třída měla umět, je odesílat přijaté příkazy a zobrazovat přijaté odpovědi. Kromě toho musíte mít možnost se připojit k libovolnému (jak je uvedeno výše) portu vzdáleného zařízení a odpojit se od něj.

K dosažení tohoto cíle byly implementovány následující funkce:

Funkce, která jako argument vezme adresu soketu, otevře spojení a spustí vstupní a výstupní proudy (proměnné streamu jsou deklarovány výše, úplné zdroje jsou na konci článku).

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

Přetížení stejné funkce, připojení k výchozímu portu - pro telnet je to 23


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

Funkce čte znaky z klávesnice a posílá je do výstupní zásuvky - což je typické, v režimu řádků, nikoli v režimu znaků:


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

Funkce přijímá data ze zásuvky a zobrazuje je na obrazovce


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

Funkce zastaví příjem a přenos dat


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

TelnetServer

Tato třída musí mít funkci příjmu příkazu ze soketu, jeho odeslání k provedení a odeslání odpovědi z příkazu zpět do soketu. Program záměrně nekontroluje vstupní data, protože zaprvé i v „krabicovém telnetu“ je možné naformátovat disk serveru, zadruhé je v tomto článku zásadně vynechána otázka bezpečnosti, a proto zde není pár slov o šifrování nebo SSL.

Existují pouze 2 funkce (jedna z nich je přetížená) a obecně to není příliš dobrá praxe, ale pro účely tohoto úkolu se mi zdálo vhodné nechat vše tak, jak je.

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

    }

Program otevře port serveru, čte z něj data, dokud nenarazí na znak konce příkazu, předá příkaz novému procesu a přesměruje výstup z procesu do soketu. Všechno je jednoduché jako útočná puška Kalašnikov.

V souladu s tím existuje přetížení této funkce s výchozím portem:

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

No, podle toho je funkce, která zastaví server, také triviální, přeruší věčnou smyčku a poruší její stav.

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

Nebudu zde dávat testovací třídy, jsou níže - vše, co dělají, je kontrola funkčnosti veřejných metod. Všechno je na git.

Abych to shrnul, za pár večerů můžete pochopit principy fungování hlavních konzolových nástrojů. Nyní, když telenetujeme na vzdálený počítač, chápeme, co se děje - kouzlo zmizelo)

Takže odkazy:
Všechny zdroje zde byly, jsou a budou
O Telnetu
Více o Telnetu

Zdroj: www.habr.com

Přidat komentář