Pisanje softvera s funkcionalnošću Windows klijent-poslužitelj pomoćnih programa, dio 01

Pozdrav.

Danas bih želio pogledati proces pisanja klijent-poslužitelj aplikacija koje obavljaju funkcije standardnih Windows uslužnih programa, kao što su Telnet, TFTP, itd., i tako dalje, u čistoj Javi. Jasno je da neću donijeti ništa novo - svi ovi uslužni programi uspješno rade više od godinu dana, ali vjerujem da ne znaju svi što se događa ispod haube.

Upravo o tome će biti riječi pod rezom.

U ovom ću članku, da ne duljim, osim općih informacija, pisati samo o Telnet poslužitelju, ali trenutno postoji i materijal o drugim uslužnim programima - to će biti u daljnjim dijelovima serije.

Prije svega, trebate shvatiti što je Telnet, za što je potreban i za što se koristi. Neću doslovno citirati izvore (ako je potrebno, priložit ću vezu na materijale o temi na kraju članka), samo ću reći da Telnet omogućuje daljinski pristup naredbenom retku uređaja. Uglavnom, tu prestaje njegova funkcionalnost (namjerno sam prešutio pristup portu poslužitelja; više o tome kasnije). To znači da da bismo ga implementirali, moramo prihvatiti redak na klijentu, proslijediti ga poslužitelju, pokušati ga proslijediti u naredbeni redak, pročitati odgovor naredbenog retka, ako postoji, proslijediti ga nazad klijentu i prikazati na ekranu ili, ako postoje pogreške, dati korisniku do znanja da nešto nije u redu.

Za implementaciju navedenog, sukladno tome, potrebne su nam 2 radne klase i neka testna klasa iz koje ćemo pokrenuti server i preko koje će raditi klijent.
Sukladno tome, trenutno struktura aplikacije uključuje:

  • TelnetKlijent
  • TelnetClientTester
  • TelnetServer
  • TelnetServerTester

Prođimo kroz svaki od njih:

TelnetKlijent

Sve što bi ova klasa trebala moći učiniti je poslati primljene naredbe i prikazati primljene odgovore. Osim toga, morate se moći spojiti na proizvoljan (kao što je gore navedeno) priključak udaljenog uređaja i prekinuti vezu s njim.

Da bi se to postiglo, implementirane su sljedeće funkcije:

Funkcija koja uzima adresu utičnice kao argument, otvara vezu i pokreće ulazne i izlazne tokove (varijable toka su deklarirane gore, puni izvori su na kraju članka).

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

Preopterećenje iste funkcije, povezivanje na zadani port - za telnet ovo je 23


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

Funkcija čita znakove s tipkovnice i šalje ih u izlaznu utičnicu - što je tipično, u linijskom, a ne znakovnom načinu:


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

Funkcija prima podatke iz utičnice i prikazuje ih na zaslonu


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

Funkcija zaustavlja prijem i prijenos podataka


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

TelnetServer

Ova klasa mora imati funkcionalnost primanja naredbe iz utičnice, slanja na izvršenje i slanja odgovora od naredbe natrag u utičnicu. Program namjerno ne provjerava ulazne podatke, jer prvo, čak iu “boxed telnetu” je moguće formatirati disk poslužitelja, a drugo, pitanje sigurnosti u ovom članku je načelno izostavljeno, pa zato i ne postoji nekoliko riječi o šifriranju ili SSL-u.

Postoje samo 2 funkcije (jedna od njih je preopterećena) i općenito to nije baš dobra praksa, ali za potrebe ovog zadatka činilo mi se primjerenim ostaviti sve kako jest.

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

    }

Program otvara port poslužitelja, čita podatke s njega dok ne naiđe na znak završetka naredbe, prosljeđuje naredbu novom procesu i preusmjerava izlaz iz procesa na utičnicu. Sve je jednostavno kao jurišna puška Kalašnjikov.

Sukladno tome, postoji preopterećenje ove funkcije sa zadanim priključkom:

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

Pa, prema tome, funkcija koja zaustavlja poslužitelj također je trivijalna, prekida vječnu petlju, kršeći njezino stanje.

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

Ovdje neću davati testne tečajeve, oni su ispod - sve što rade je provjera funkcionalnosti javnih metoda. Sve je na gitu.

Ukratko, za nekoliko večeri možete razumjeti principe rada uslužnih programa glavne konzole. Sada, kada telenetujemo udaljeno računalo, razumijemo što se događa - magija je nestala)

Dakle, linkovi:
Svi izvori su bili, jesu i bit će ovdje
O Telnetu
Više o Telnetu

Izvor: www.habr.com

Dodajte komentar