Scrierea de software cu funcționalitatea utilităților client-server Windows, partea 01

Bine ai venit.

Astăzi aș dori să mă uit la procesul de scriere a aplicațiilor client-server care îndeplinesc funcțiile utilităților standard Windows, cum ar fi Telnet, TFTP și cetera, și cetera în Java pur. Este clar că nu voi aduce nimic nou - toate aceste utilități funcționează cu succes de mai bine de un an, dar cred că nu toată lumea știe ce se întâmplă sub capotă.

Acesta este exact ceea ce se va discuta sub tăietură.

În acest articol, pentru a nu-l trage, pe lângă informații generale, voi scrie doar despre serverul Telnet, dar în acest moment există și material despre alte utilități - va fi în alte părți ale seriei.

În primul rând, trebuie să vă dați seama ce este Telnet, pentru ce este necesar și pentru ce este folosit. Nu voi cita sursele textual (dacă va fi necesar, voi atașa un link către materiale pe subiect la sfârșitul articolului), voi spune doar că Telnet oferă acces la distanță la linia de comandă a dispozitivului. În general, aici se termină funcționalitatea sa (am tăcut în mod deliberat despre accesarea portului serverului; mai multe despre asta mai târziu). Aceasta înseamnă că pentru a-l implementa, trebuie să acceptăm o linie pe client, să o transmitem serverului, să încercăm să o trecem la linia de comandă, să citim răspunsul liniei de comandă, dacă există unul, să-l transmitem înapoi clientului și afișați-l pe ecran sau, dacă există erori, informați utilizatorul că ceva nu este în regulă.

Pentru a implementa cele de mai sus, în consecință, avem nevoie de 2 clase de lucru și de o clasă de test de pe care vom lansa serverul și prin care va lucra clientul.
În consecință, în prezent, structura aplicației include:

  • TelnetClient
  • TelnetClientTester
  • TelnetServer
  • TelnetServerTester

Să trecem prin fiecare dintre ele:

TelnetClient

Tot ce ar trebui să poată face această clasă este să trimită comenzile primite și să arate răspunsurile primite. În plus, trebuie să vă puteți conecta la un port arbitrar (după cum s-a menționat mai sus) al unui dispozitiv la distanță și să vă deconectați de la acesta.

Pentru a realiza acest lucru, au fost implementate următoarele funcții:

O funcție care ia o adresă de socket ca argument, deschide o conexiune și pornește fluxurile de intrare și de ieșire (variabilele fluxului sunt declarate mai sus, sursele complete sunt la sfârșitul articolului).

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

Supraîncărcarea aceleiași funcții, conectarea la portul implicit - pentru telnet, acesta este 23


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

Funcția citește caracterele de la tastatură și le trimite la soclul de ieșire - ceea ce este tipic, în modul linie, nu în modul caractere:


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

Funcția primește date de la priză și le afișează pe ecran


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

Funcția oprește recepția și transmiterea datelor


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

TelnetServer

Această clasă trebuie să aibă funcționalitatea de a primi o comandă de la un socket, de a o trimite pentru execuție și de a trimite un răspuns de la comandă înapoi la socket. Programul nu verifică în mod deliberat datele de intrare, deoarece, în primul rând, chiar și în „telnet în cutie” este posibil să formatați discul serverului, iar în al doilea rând, problema securității din acest articol este omise în principiu și de aceea nu există un cuvânt despre criptare sau SSL.

Există doar 2 funcții (una dintre ele este supraîncărcată) și, în general, aceasta nu este o practică foarte bună, dar în scopul acestei sarcini, mi s-a părut potrivit să las totul așa cum este.

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

    }

Programul deschide portul serverului, citește datele de pe acesta până când întâlnește un caracter de final de comandă, transmite comanda unui nou proces și redirecționează ieșirea din proces către socket. Totul este la fel de simplu ca o pușcă de asalt Kalashnikov.

În consecință, există o supraîncărcare pentru această funcție cu un port implicit:

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

Ei bine, în consecință, funcția care oprește serverul este și ea banală, întrerupe bucla eternă, încălcându-i condiția.

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

Nu voi da clase de testare aici, sunt mai jos - tot ce fac este să verifice funcționalitatea metodelor publice. Totul este pe minte.

Pentru a rezuma, în câteva seri puteți înțelege principiile de funcționare ale utilităților principale ale consolei. Acum, când telenetăm la un computer la distanță, înțelegem ce se întâmplă - magia a dispărut)

Deci, linkurile:
Toate sursele au fost, sunt și vor fi aici
Despre Telnet
Mai multe despre Telnet

Sursa: www.habr.com

Adauga un comentariu