Vývoj softwaru s funkcemi klient-server nástrojů pro Windows, část 01

Dobrý den.

Dnes bych chtěl probrat proces psaní klient-serverových aplikací, které vykonávají funkce standardních utilit Windows, jako jsou Telnet, TFTP a další. Je jasné, že nepřináším nic nového — všechny tyto utility již úspěšně fungují řadu let, ale myslím, že co se skrývá pod kapotou, neví každý.

O tom se bude mluvit v následujícím textu.

V tomto článku, abychom ho nezdržovali, budu psát pouze o Telnet serveru, ale v tuto chvíli mám také materiál k dalším utilitám — ten bude v dalších částech cyklu.

Nejprve je potřeba objasnit, co to vlastně Telnet je, k čemu slouží a jak se používá. Nebudu doslovně citovat zdroje (pokud budete potřebovat — na konci článku připojím odkazy na materiály k tématu), řeknu pouze, že Telnet poskytuje vzdálený přístup k příkazovému řádku zařízení. V zásadě tím jego funkčnost končí (o přístupu k serverovému portu se zatím nezmiňuji, o tom později). Takže pro jeho realizaci potřebujeme přijmout řetězec na klientovi, předat ho na server, pokusit se předat ho do příkazového řádku, přečíst odpověď příkazového řádku, pokud nějaká existuje, vrátit ji zpět na klienta a zobrazit ji na obrazovce, nebo v případě chyby uživatelovi naznačit, že něco není v pořádku.

Pro realizaci výše uvedeného potřebujeme tedy 2 funkční třídy a nějakou testovací třídu, ze které budeme spouštět server a skrze kterou bude fungovat klient.
V současné době struktura aplikace zahrnuje:

  • TelnetClient
  • TelnetClientTester
  • TelnetServer
  • TelnetServerTester

Probereme si každou z nich:

TelnetClient

Vše, co by tato třída měla umět, je posílat přijaté příkazy a zobrazovat přijaté odpovědi. Kromě toho by se měla umět připojit k libovolnému (o čemž jsem se zmínil výše) portu vzdáleného zařízení a odpojit se od něj.

K tomu byly implementovány následující funkce:

Funkce, která jako argument přijímá adresu socketu, otevírá spojení a spouští vstupní a výstupní vlákna (proměnné vláken jsou deklarovány výše, kompletní zdrojové kódy budou 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řejmenování této funkce pro 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ího soketu – což je netypické, v řádkovém, nikoli znakovém režimu:


    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 soketu 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 zastavuje příjem a přenos dat.


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

TelnetServer

Třída by měla mít funkce pro příjem příkazu ze soketu, jeho provedení a odeslání odpovědi zpět na soket. Program úmyslně neprovádí kontrolu vstupních dat, protože za prvé, i v "krabicovém telnetu" je možnost formátování disku serveru, a za druhé, otázka bezpečnosti je v tomto článku zcela opomenuta, a proto tu není ani slovo o šifrování nebo SSL.

Celkem jsou zde 2 funkce (jedna z nich je přetížena), a celkově to není příliš dobrá praxe, nicméně v rámci této úlohy se mi zdálo na místě nechat vše tak, jak to je.

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

        (new Thread(()->{ try {
            ServerSocket ss = new ServerSocket(port); // vytváříme serverový socket a přiřazujeme ho k uvedenému portu
            System.out.println("Port " + port + " čeká na připojení");

            Socket socket = ss.accept();
            System.out.println("Připojeno");
            System.out.println();

            // Bereme vstupní a výstupní proudy socketu, což nám nyní umožňuje přijímat a posílat data klientovi.
            InputStream sin = socket.getInputStream();
            OutputStream sout = socket.getOutputStream();

            Map 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 = "Něco se pokazilo... Kód chyby: " + 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("Chyba: " + e.getMessage());
                    }
                }).start();
                System.out.println(buffer);
                buffer = "";

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

    }

Program otevře serverový port, čte z něj data, dokud nenarazí na symbol konce příkazu, předá příkaz do nového procesu a výstup z procesu je přesměrován do socketu. Všechno je to jednoduché jako kalašnikov.

Pro tuto funkci existuje odpovídající přetížení s výchozím portem:

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

A funkce, která zastavuje server, je také velmi triviální, přeruší nekonečný cyklus a naruší jeho podmínku.

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

Příklady testovacích tříd zde nebudu uvádět, jsou níže — vše, co dělají, je zkontrolovat funkčnost veřejných metod. Vše je na Gitu.

Shrnuto, během pár večerů můžete pochopit principy fungování základních konzolových utilit. Nyní, když se připojíme k vzdálenému počítači, chápeme, co se děje — magie zmizela)

Takže, odkazy:
Všechny zdrojové soubory byly, jsou a budou zde
O Telnetu
Další informace o Telnetu

Zdroj: habr.com

Koupit spolehlivý hosting pro webové stránky s ochranou proti DDoS, VPS VDS servery 🔥 Koupit spolehlivý hosting pro webové stránky s ochranou proti DDoS, VPS VDS servery | ProHoster