Напісанне праграмнага забеспячэння з функцыяналам кліент-серверных утыліт Windows, part 01

Вітаю.

Сёння жадалася б разабраць працэс напісання кліент-серверных прыкладанняў, якія выконваюць функцыі стандартных утыліт Windows, як то Telnet, TFTP, et cetera, et cetera на чыстай Javа. Зразумела, што нічога новага я не прыўнясу - усе гэтыя ўтыліты ўжо паспяхова працуюць не адзін год, але, мяркую, што адбываецца пад капотам у іх ведаюць не ўсе.

Менавіта пра гэта і пойдзе гаворка пад катом.

У гэтым артыкуле, для таго, каб яе не зацягваць, акрамя агульнай інфармацыі я напішу толькі аб Telnet серверы, але на дадзены момант ёсць яшчэ матэрыял па іншых утылітах – ён будзе ў далейшых частках цыклу.

Першым чынам варта разабрацца, што ж такое Telnet, для чаго ён патрэбен і з чым яго ядуць. Не буду даслоўна цытаваць крыніцы (калі трэба – у канцы артыкула прымацую спасылку на матэрыялы па тэме), скажу толькі, што Telnet забяспечвае выдалены доступ да каманднага радка прылады. Па вялікім, на гэтым яго функцыянал сканчаецца (аб звароце да сервернага порта змаўчаў свядома, пра гэта пазней). Значыць, для яго рэалізацыі, нам трэба прыняць радок на кліенце, перадаць яго на сервер, паспрабаваць перадаць яго ў камандны радок, лічыць адказ каманднага радка, калі ён ёсць, перадаць яго назад на кліент і вывесці на экран, ці ж, у выпадку ўзнікнення памылкі, даць карыстачу зразумець, што нешта не так.

Для рэалізацыі вышэйпададзенага, адпаведна, трэба 2 працоўных класа, і некаторы тэставы клас, з якога мы будзем запускаць сервер і праз які будзе працаваць кліент.
Адпаведна, на дадзены момант структура дадатку ўключае ў сябе:

  • TelnetClient
  • TelnetClientTester
  • TelnetServer
  • TelnetServerTester

Прабяжымся па кожным з іх:

TelnetClient

Усё, што павінен умець рабіць гэты клас - адпраўляць атрыманыя каманды і паказваць атрыманыя адказы. Акрамя таго, трэба ўмець падлучацца да адвольнага (пра што казаў вышэй) порта выдаленай прылады і адключацца ад яго.

Для гэтага былі рэалізаваны наступныя функцыі:

Функцыя, якая прымае ў якасці аргументу адрас сокета, якая адкрывае злучэнне і якая запускае струмені ўводу і высновы (зменныя струмені абвешчаныя вышэй, поўныя зыходнікі - у канцы артыкула).

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

Перагрузка гэтай жа функцыі, якая падключаецца да порта па змаўчанні - для телнета гэта 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());
        }
    }

Функцыя прымае дадзеныя з сокета і выводзіць іх на экран


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

TelnetServer

Гэты клас павінен валодаць функцыяналам прыняцця каманды з сокета, адпраўкі яе на выкананне і адпраўкі адказу ад каманды назад на сокет. У праграме наўмысна няма праверкі ўваходных дадзеных, таму што па-першае, і ў «скрынкавым тэнэце» ёсць магчымасць фарматнуць дыск сервера, а па-другое, пытанне бяспекі ў гэтым артыкуле апушчаны ў прынцыпе, і менавіта таму тут няма ні слова аб шыфраванні або 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;
    }

Тэставыя класы я тут прыводзіць не буду, яны ёсць унізе – усё што яны робяць – правяраюць працаздольнасць публічных метадаў. Усё ёсць на гіце.

Рэзюмуючы, за пару вечароў можна зразумець прынцыпы дзеяння асноўных кансольных утыліт. Зараз, калі мы целнемся да выдаленага кампа, мы разумеем, што адбываецца - магія знікла)

Такім чынам, спасылкі:
Усе зыходнікі былі, ёсць і будуць есці тут
Аб Тэлнэце
Яшчэ пра Тэлнет

Крыніца: habr.com

Дадаць каментар