Pisanje softvera sa funkcionalnošću Windows klijent-server uslužnih programa, dio 01

Pozdrav.

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

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

U ovom članku, da ne bih odugovlačio, osim općih informacija, pisat ću samo o Telnet serveru, ali trenutno ima i materijala o drugim uslužnim programima - to će biti u daljnjim dijelovima serije.

Prije svega, morate shvatiti šta je Telnet, za šta je potreban i za šta se koristi. Neću doslovno citirati izvore (ako je potrebno, priložit ću vezu na materijale na temu na kraju članka), samo ću reći da Telnet omogućava daljinski pristup komandnoj liniji uređaja. Uglavnom, tu se njegova funkcionalnost završava (namjerno sam ćutao o pristupu serverskom portu; više o tome kasnije). To znači da da bismo ga implementirali, moramo prihvatiti liniju na klijentu, proslediti je serveru, pokušati je proslediti komandnoj liniji, pročitati odgovor komandne linije, ako postoji, proslediti ga nazad klijentu i prikažite ga na ekranu ili, ako dođe do greške, obavijestite korisnika da nešto nije u redu.

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

  • TelnetClient
  • TelnetClientTester
  • TelnetServer
  • TelnetServerTester

Prođimo kroz svaki od njih:

TelnetClient

Sve što ova klasa treba da može da uradi je da šalje primljene komande i prikazuje primljene odgovore. Osim toga, morate biti u mogućnosti da se povežete na proizvoljni (kao što je gore spomenuto) port udaljenog uređaja i da prekinete 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 iznad, 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 podrazumevani port - za telnet je ovo 23


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

Funkcija čita znakove sa tastature i šalje ih u izlaznu utičnicu - što je tipično, u linijskom režimu, a ne u režimu znakova:


    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 ekranu


    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 je na izvršenje i slanja odgovora iz naredbe natrag u socket. Program namjerno ne provjerava ulazne podatke, jer prvo, čak i u “boxed telnetu” moguće je formatirati serverski disk, a drugo, pitanje sigurnosti u ovom članku je u principu izostavljeno, pa zato nema nekoliko riječi o enkripciji ili SSL-u.

Postoje samo 2 funkcije (jedna od njih je preopterećena), i generalno to nije baš dobra praksa, ali za potrebe ovog zadatka, činilo mi se prikladnim da sve ostavim kako jeste.

 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 servera, čita podatke iz njega sve dok ne naiđe na znak završetka komande, prosljeđuje naredbu novom procesu i preusmjerava izlaz iz procesa u socket. Sve je jednostavno kao jurišna puška Kalašnjikov.

U skladu s tim, postoji preopterećenje za ovu funkciju sa zadanim portom:

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

Pa, shodno tome, funkcija koja zaustavlja server je također trivijalna, prekida vječnu petlju, narušavajući njen uvjet.

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

Ovdje neću davati testne klase, one su ispod - sve što rade je provjeravati funkcionalnost javnih metoda. Sve je na pameti.

Ukratko, za nekoliko večeri možete razumjeti principe rada glavnih uslužnih programa konzole. Sada, kada telenetiramo na udaljeni kompjuter, razumijemo šta se dešava - magija je nestala)

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

izvor: www.habr.com

Dodajte komentar