Scrittura di software con funzionalità delle utilità client-server di Windows, parte 01

Benvenuti.

Oggi vorrei esaminare il processo di scrittura di applicazioni client-server che eseguono le funzioni delle utilità Windows standard, come Telnet, TFTP, eccetera, eccetera in Java puro. È chiaro che non porterò nulla di nuovo: tutte queste utility funzionano con successo da più di un anno, ma credo che non tutti sappiano cosa sta succedendo sotto il cofano.

Questo è esattamente ciò che verrà discusso sotto il taglio.

In questo articolo, per non trascinarlo fuori, oltre alle informazioni generali, scriverò solo sul server Telnet, ma al momento c'è anche materiale su altre utilità: sarà in ulteriori parti della serie.

Prima di tutto, devi capire cos'è Telnet, a cosa serve e a cosa serve. Non citerò le fonti alla lettera (se necessario, allegherò un collegamento ai materiali sull'argomento alla fine dell'articolo), dirò solo che Telnet fornisce l'accesso remoto alla riga di comando del dispositivo. In generale, è qui che finisce la sua funzionalità (ho deliberatamente taciuto sull'accesso alla porta del server; ne parleremo più avanti). Ciò significa che per implementarlo dobbiamo accettare una riga sul client, passarla al server, provare a passarla alla riga di comando, leggere la risposta della riga di comando, se ce n'è una, ripassarla al client e visualizzarlo sullo schermo o, in caso di errori, avvisare l'utente che qualcosa non va.

Per implementare quanto sopra, abbiamo bisogno di 2 classi di lavoro e di una classe di test da cui lanceremo il server e attraverso la quale funzionerà il client.
Pertanto, al momento la struttura della domanda prevede:

  • Cliente Telnet
  • TelnetClientTester
  • TelnetServer
  • TelnetServerTester

Esaminiamo ciascuno di essi:

Cliente Telnet

Tutto ciò che questa classe dovrebbe essere in grado di fare è inviare i comandi ricevuti e mostrare le risposte ricevute. Inoltre, è necessario essere in grado di connettersi a una porta arbitraria (come menzionato sopra) di un dispositivo remoto e disconnettersi da esso.

Per raggiungere questo obiettivo sono state implementate le seguenti funzionalità:

Una funzione che accetta un indirizzo socket come argomento, apre una connessione e avvia flussi di input e output (le variabili del flusso sono dichiarate sopra, i sorgenti completi sono alla fine dell'articolo).

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

Sovraccarico della stessa funzione, connessione alla porta predefinita: per Telnet è 23


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

La funzione legge i caratteri dalla tastiera e li invia alla presa di uscita, cosa tipica in modalità linea e non in modalità carattere:


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

La funzione riceve i dati dalla presa e li visualizza sullo schermo


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

La funzione interrompe la ricezione e la trasmissione dei dati


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

TelnetServer

Questa classe deve avere la funzionalità di ricevere un comando da un socket, inviarlo per l'esecuzione e inviare una risposta dal comando al socket. Il programma deliberatamente non controlla i dati immessi, perché in primo luogo, anche in "boxed telnet" è possibile formattare il disco del server, e in secondo luogo, la questione della sicurezza in questo articolo è in linea di principio omessa, ed è per questo che non c'è una parola sulla crittografia o SSL.

Ci sono solo 2 funzioni (una di queste è sovraccarica), e in generale questa non è una buona pratica, ma ai fini di questo compito mi è sembrato opportuno lasciare tutto così com'è.

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

    }

Il programma apre la porta del server, legge i dati da essa finché non incontra un carattere di fine comando, passa il comando a un nuovo processo e reindirizza l'output dal processo al socket. Tutto è semplice come un fucile d'assalto Kalashnikov.

Di conseguenza, esiste un sovraccarico per questa funzione con una porta predefinita:

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

Ebbene, di conseguenza, anche la funzione che ferma il server è banale, interrompe il ciclo eterno, violandone lo stato.

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

Non fornirò lezioni di prova qui, sono riportate di seguito: tutto ciò che fanno è verificare la funzionalità dei metodi pubblici. Tutto è su Git.

Ricapitolando, in un paio di sere potrete comprendere i principi di funzionamento delle principali utility della console. Ora, quando ci colleghiamo a un computer remoto, capiamo cosa sta succedendo: la magia è scomparsa)

Allora, i link:
Tutte le fonti erano, sono e saranno qui
A proposito di Telnet
Maggiori informazioni su Telnet

Fonte: habr.com

Aggiungi un commento