Escriptura de programari amb la funcionalitat de les utilitats client-servidor de Windows, part 01

Salutacions.

Avui m'agradaria mirar el procés d'escriptura d'aplicacions client-servidor que realitzen les funcions de les utilitats estàndard de Windows, com ara Telnet, TFTP, etcètera, etcètera en Java pur. Està clar que no portaré res de nou: totes aquestes utilitats han funcionat amb èxit durant més d'un any, però crec que no tothom sap què passa sota el capó.

Això és exactament el que es discutirà sota el tall.

En aquest article, per no arrossegar-lo, a més de la informació general, només escriuré sobre el servidor Telnet, però de moment també hi ha material sobre altres utilitats: serà en altres parts de la sèrie.

En primer lloc, heu d'esbrinar què és Telnet, per a què es necessita i per a què s'utilitza. No citaré les fonts textualment (si cal, adjuntaré un enllaç a materials sobre el tema al final de l'article), només diré que Telnet proporciona accés remot a la línia d'ordres del dispositiu. En general, aquí és on acaba la seva funcionalitat (deliberadament vaig guardar silenci sobre l'accés al port del servidor; més endavant). Això vol dir que per implementar-lo, hem d'acceptar una línia al client, passar-la al servidor, intentar passar-la a la línia d'ordres, llegir la resposta de la línia d'ordres, si n'hi ha, tornar-la al client i mostrar-lo a la pantalla o, si hi ha errors, avisar l'usuari que alguna cosa no funciona.

Per implementar l'anterior, en conseqüència, necessitem 2 classes de treball i alguna classe de prova des de la qual llançarem el servidor i a través de la qual treballarà el client.
En conseqüència, actualment l'estructura de l'aplicació inclou:

  • TelnetClient
  • TelnetClientTester
  • TelnetServer
  • TelnetServerTester

Repassem cadascun d'ells:

TelnetClient

Tot el que hauria de poder fer aquesta classe és enviar ordres rebudes i mostrar les respostes rebudes. A més, heu de poder connectar-vos a un port arbitrari (com s'ha esmentat anteriorment) d'un dispositiu remot i desconnectar-ne.

Per aconseguir-ho, s'han implementat les funcions següents:

Una funció que pren una adreça de sòcol com a argument, obre una connexió i inicia fluxos d'entrada i sortida (les variables de flux es declaren més amunt, les fonts completes es troben al final de l'article).

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

Sobrecàrrega de la mateixa funció, connexió al port predeterminat: per a telnet, aquest és 23


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

La funció llegeix caràcters del teclat i els envia al sòcol de sortida, que és típic, en mode de línia, no en mode de caràcters:


    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 funció rep dades del sòcol i les mostra a la pantalla


    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 funció atura la recepció i la transmissió de dades


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

TelnetServer

Aquesta classe ha de tenir la funcionalitat de rebre una ordre des d'un sòcol, enviar-la per a l'execució i enviar una resposta de l'ordre al sòcol. El programa no comprova deliberadament les dades d'entrada, ja que, en primer lloc, fins i tot a "telnet en caixa" és possible formatar el disc del servidor i, en segon lloc, s'omet en principi el problema de la seguretat en aquest article, i per això no hi ha una paraula sobre xifratge o SSL.

Només hi ha 2 funcions (una d'elles està sobrecarregada), i en general aquesta no és una pràctica molt bona, però a efectes d'aquesta tasca, em va semblar oportú deixar-ho tot tal com està.

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

    }

El programa obre el port del servidor, en llegeix dades fins que troba un caràcter de final de comanda, passa l'ordre a un procés nou i redirigeix ​​la sortida del procés al sòcol. Tot és tan senzill com un rifle d'assalt Kalashnikov.

En conseqüència, hi ha una sobrecàrrega per a aquesta funció amb un port predeterminat:

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

Bé, en conseqüència, la funció que atura el servidor també és trivial, interromp el bucle etern, violant la seva condició.

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

No donaré classes de prova aquí, són a continuació; tot el que fan és comprovar la funcionalitat dels mètodes públics. Tot està al git.

En resum, en un parell de vespres podreu entendre els principis de funcionament de les principals utilitats de la consola. Ara, quan fem telenet a un ordinador remot, entenem què està passant: la màgia ha desaparegut)

Així doncs, els enllaços:
Totes les fonts eren, són i seran aquí
Sobre Telnet
Més informació sobre Telnet

Font: www.habr.com

Afegeix comentari