Írószoftver a Windows kliens-szerver segédprogramjaival, 01. rész

Приветствую.

Ma az olyan kliens-szerver alkalmazások írási folyamatát szeretném megvizsgálni, amelyek a szabványos Windows segédprogramok funkcióit látják el, mint például Telnet, TFTP, stb., stb. tiszta Java nyelven. Egyértelmű, hogy nem hozok semmi újat - ezek a segédprogramok több mint egy éve sikeresen működnek, de úgy gondolom, hogy nem mindenki tudja, mi folyik a motorháztető alatt.

Pontosan erről lesz szó a vágás alatt.

Ebben a cikkben, hogy ne húzódjon el, az általános információkon kívül csak a Telnet szerverről írok, de jelenleg más segédprogramokról is van anyag - a sorozat további részeiben lesz.

Először is ki kell találnia, hogy mi az a Telnet, mire van szükség és mire használják. A forrásokat szó szerint nem idézem (ha szükséges, a cikk végén linket csatolok a témával foglalkozó anyagokhoz), csak annyit mondok, hogy a Telnet távoli hozzáférést biztosít az eszköz parancssorához. Nagyjából itt ér véget a funkcionalitása (szándékosan hallgattam a szerverport eléréséről; erről később). Ez azt jelenti, hogy a megvalósításhoz el kell fogadnunk egy sort a kliensen, át kell adni a szervernek, megpróbálni átadni a parancssornak, elolvasni a parancssori választ, ha van, vissza kell adni a kliensnek és jelenítse meg a képernyőn, vagy hiba esetén értesítse a felhasználót, hogy valami nincs rendben.

A fentiek megvalósításához ennek megfelelően szükségünk van 2 munkaosztályra és néhány tesztosztályra, ahonnan elindítjuk a szervert, és amelyen keresztül a kliens fog működni.
Ennek megfelelően a pályázati struktúra jelenleg a következőket tartalmazza:

  • TelnetClient
  • TelnetClientTester
  • TelnetServer
  • TelnetServerTester

Nézzük végig mindegyiket:

TelnetClient

Ennek az osztálynak csak a fogadott parancsok elküldésére és a kapott válaszok megjelenítésére kell tudnia. Ezenkívül képesnek kell lennie egy távoli eszköz tetszőleges (a fent említett) portjához csatlakozni, és le kell kapcsolódnia róla.

Ennek érdekében a következő funkciókat valósították meg:

Olyan függvény, amely egy socket címet vesz argumentumként, megnyit egy kapcsolatot, és elindítja a bemeneti és kimeneti adatfolyamokat (a stream változók fent vannak deklarálva, a teljes források a cikk végén találhatók).

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

Ugyanazon funkció túlterhelése, csatlakozás az alapértelmezett porthoz - telnet esetén ez a 23


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

A funkció beolvassa a karaktereket a billentyűzetről, és elküldi a kimeneti aljzatba - ami jellemző, vonal módban, nem karakter módban:


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

A funkció adatokat fogad az aljzatból és megjeleníti a képernyőn


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

A funkció leállítja az adatok fogadását és továbbítását


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

TelnetServer

Ennek az osztálynak rendelkeznie kell azzal a funkcióval, hogy parancsot fogadjon egy socketből, elküldje végrehajtásra, és a parancsból választ küldjön vissza a socketbe. A program szándékosan nem ellenőrzi a bemeneti adatokat, mert egyrészt még „dobozos telnetben” is lehet formázni a szerverlemezt, másrészt a biztonság kérdése ebben a cikkben elvileg kimaradt, és ezért nincs néhány szót a titkosításról vagy az SSL-ről.

Csak 2 funkció van (az egyik túlterhelt), és általában ez nem túl jó gyakorlat, de ennek a feladatnak a szempontjából helyénvalónak tűnt, hogy mindent úgy hagyjak, ahogy van.

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

    }

A program megnyitja a szerverportot, addig olvassa be az adatokat, amíg nem találkozik egy parancsvég karakterrel, átadja a parancsot egy új folyamatnak, és átirányítja a kimenetet a folyamatból a socketbe. Minden olyan egyszerű, mint egy Kalasnyikov géppuska.

Ennek megfelelően a funkció túlterhelése van az alapértelmezett porttal:

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

Nos, ennek megfelelően a szervert leállító funkció is triviális, megszakítja az örök hurkot, megsértve annak feltételét.

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

Itt nem adok tesztosztályokat, ezek lent találhatók - csak ellenőrzik a nyilvános metódusok működését. Minden a git-en van.

Összefoglalva, néhány este alatt megértheti a fő konzol segédprogramok működési elvét. Most, amikor telenetezünk egy távoli számítógépre, megértjük, mi történik - a varázslat eltűnt)

Szóval a linkek:
Minden forrás itt volt, van és lesz
A Telnetről
További információ a Telnetről

Forrás: will.com

Hozzászólás