Писане на софтуер с функционалността на Windows клиент-сървър помощни програми, част 01

Поздрави.

Днес бих искал да разгледам процеса на писане на приложения клиент-сървър, които изпълняват функциите на стандартни помощни програми на Windows, като Telnet, TFTP и т.н., и т.н. в чиста Java. Ясно е, че няма да донеса нищо ново - всички тези помощни програми работят успешно повече от една година, но вярвам, че не всеки знае какво се случва под капака.

Точно за това ще стане дума под разрез.

В тази статия, за да не я разтягам, в допълнение към общата информация ще пиша само за 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());
        }
    }

Претоварване на същата функция, свързване към порта по подразбиране - за 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());
        }
    }

Функцията получава данни от контакта и ги показва на екрана


    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

Този клас трябва да има функционалността да получава команда от сокет, да я изпраща за изпълнение и да изпраща отговор от командата обратно към сокета. Програмата умишлено не проверява входните данни, тъй като първо, дори в „boxed 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.

За да обобщим, след няколко вечери можете да разберете принципите на работа на основните помощни програми на конзолата. Сега, когато направим теленет към отдалечен компютър, разбираме какво се случва - магията е изчезнала)

И така, връзките:
Всички източници бяха, са и ще бъдат тук
Относно Telnet
Повече за Telnet

Източник: www.habr.com

Добавяне на нов коментар