Písanie softvéru s funkčnosťou klient-server nástrojov Windows, časť 01

Zdravím.

Dnes by som sa chcel zaoberať procesom písania klient-server aplikácií, ktoré vykonávajú funkcie štandardných utilít Windows, ako sú Telnet, TFTP a tak ďalej, na čistej Jave. Je jasné, že nič nové neprinášam – všetky tieto utility už úspešne fungujú niekoľko rokov, ale predpokladám, že nie všetci vedia, čo sa deje pod kapotou.

O tom presne bude reč nižšie.

V tomto článku, aby som ho nezdržiaval, napíšem len o Telnet serveri, avšak momentálne mám aj materiál o iných utilitách – ten bude v ďalších častiach cyklu.

Najprv je potrebné si ujasniť, čo je Telnet, na čo je potrebný a ako sa používa. Nebudem doslovne citovať zdroje (ak je potrebné – na konci článku pripojím odkaz na materiály k téme), poviem len, že Telnet poskytuje vzdialený prístup k príkazovému riadku zariadenia. V skratke, týmto jeho funkcionalita končí (o prístupe k serverovému portu som zámerne mlčal, o tom neskôr). Znamená to, že pre jeho realizáciu potrebujeme na klientovi prijať reťazec, preniesť ho na server, pokúsiť sa ho preniesť do príkazového riadku, prečítať odpoveď z príkazového riadku, ak existuje, preniesť ju späť na klienta a zobraziť na obrazovke, alebo ak dôjde k chybe, informovať používateľa, že niečo nie je v poriadku.

Pre realizáciu vyššie uvedeného, potrebujeme 2 pracovné triedy a niektorú testovaciu triedu, z ktorej budeme spúšťať server a cez ktorú bude fungovať klient.
Doterajšia štruktúra aplikácie zahŕňa:

  • TelnetClient
  • TelnetClientTester
  • TelnetServer
  • TelnetServerTester

Prejdime si každú z nich:

TelnetClient

Všetko, čo by mala táto trieda vedieť, je posielať prijaté príkazy a zobrazovať prijaté odpovede. Okrem toho by sa mala vedieť pripojiť k ľubovoľnému (o čom som hovoril vyššie) portu vzdialeného zariadenia a odpojiť sa od neho.

Na to boli implementované nasledujúce funkcie:

Funkcia, ktorá prijíma ako argument adresu socketu, otvára spojenie a spúšťa vstupné a výstupné vlákna (premenné vlákna sú definované vyššie, kompletné zdrojové kódy sú 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());
        }
    }

Preťaženie tej samej funkcie, ktorá sa pripája na predvolený port — pre telnet je to 23


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

Funkcia číta znaky z klávesnice a odosiela ich na výstupný socket — čo je zaujímavé, v riadkovom, a nie znakovom móde:


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

Funkcia prijíma dáta zo socketu a zobrazuje ich na obrazovke


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

Funkcia zastavuje prijímanie a odosielanie dát


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

TelnetServer

Táto trieda by mala mať funkciu prijímania príkazov zo socketu, ich vykonávania a vracania odpovedí príkazov späť na socket. V programe zámerne nie je kontrola vstupných dát, pretože predovšetkým, aj v "krabicovom telnete" je možnosť naformátovať disk servera, a za druhé, otázka bezpečnosti bola v tomto článku jednoducho opomenutá, a preto tu nie je ani slovo o šifrovaní alebo SSL.

Je tu len 2 funkcie (jedna z nich je preťažená), a celkovo to nie je veľmi dobrá prax, avšak v rámci tejto úlohy sa mi zdalo vhodné nechať všetko tak, ako to je.

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

        (new Thread(() -> { try {
            ServerSocket ss = new ServerSocket(port); // vytvoríme serverový socket a priradíme ho k vyššie uvedenému portu
            System.out.println("Port " + port + " čaká na pripojenia");

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

            // Získame vstupné a výstupné prúdy socketu, teraz môžeme prijímať a posielať údaje 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 = "Niečo je zle... 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 otvára serverový port, číta z neho údaje, kým nenatrafí na znak konca príkazu, prenáša príkaz do nového procesu, a výstup z procesu je presmerovaný do socketu. Je to jednoduché ako automat Kalashnikov.

Pre túto funkciu existuje príslušné preťaženie s predvoleným portom:

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

A funkcia, ktorá zastavuje server, je tiež jednoduchá, preruší nekonečnú slučku, čím porušuje jej podmienku.

    public void stop()
    {
        System.out.println("Server bol zastavený");
        this.isRunning = false;
    }

Testovacie triedy sem nezapojím, sú uvedené nižšie — všetko, čo robia, je kontrolovať funkčnosť verejných metód. Všetko je na Gite.

Zhrňujúc, za pár večerov sa dá pochopiť princíp činnosti základných konzolových utilít. Teraz, keď sa pripojíme na vzdialený počítač, chápeme, čo sa deje — mágia zmizla.)

Základné odkazy:
Všetky zdrojové kódy boli, sú a budú prístupné tu.
O Telnete
Ešte o Telnete

Zdroj: habr.com

Kúpte si spoľahlivý hosting pre webové stránky s ochranou proti DDoS, VPS VDS servery 🔥 Kúpte si spoľahlivý hosting pre webové stránky s ochranou proti DDoS, VPS VDS servery | ProHoster