Windows kliens-szerver funkciókkal rendelkező szoftver írása, 01. rész

Üdvözlöm.

Ma a kliens-szerver alkalmazások írásának folyamatát szeretném megvizsgálni, amelyek a Windows standard segédprogramjainak, mint például a Telnet, TFTP stb. stb. funkcióit látják el, tiszta Javával. Nyilvánvaló, hogy semmi újat nem hozok be — ezek a segédprogramok már évek óta sikeresen működnek, de azt hiszem, nem mindenki ismeri, mi történik mögöttük.

Erről fog szólni a cikk alján.

Ebben a cikkben, hogy ne legyen túl hosszú, az általános információk mellett csak a Telnet szerverről írok, de jelenleg van anyag más segédprogramokról is — ezek a ciklus későbbi részeiben lesznek.

Mindenekelőtt tisztáznunk kell, mi is az a Telnet, mire való, és hogyan működik. Nem fogom szó szerint idézni a forrásokat (ha szükséges — a cikk végén csatolok hivatkozást a témához), csak annyit mondok, hogy a Telnet távoli hozzáférést biztosít az eszköz parancssorához. Nagyjából itt véget is ér a funkciója (a szerver portjának eléréséről szándékosan hallgattam, erről később). Tehát, a megvalósításához szükségünk van egy sorra a kliens oldalon, amit át kell adni a szervernek, meg kell próbálnunk végrehajtani a parancssorban, olvasni a parancssorból az esetleges választ, azt vissza kell küldeni a kliensnek, és ki kell írni a képernyőre, vagy ha hiba lép fel, jelezni a felhasználónak, hogy valami nem oké.

A fentiek implementálásához szükség van 2 működő osztályra és egy teszt osztályra, ahonnan elindítjuk a szervert, és amivel a kliens működni fog.
Tehát jelenleg az alkalmazás struktúrája a következőket tartalmazza:

  • TelnetClient
  • TelnetClientTester
  • TelnetServer
  • TelnetServerTester

Nézzük át mindegyiket:

TelnetClient

Mindannyiunknak, amit ez az osztály csinálnia kell — küldeni a kapott parancsokat és megjeleníteni a kapott válaszokat. Ezen kívül képesnek kell lennie kapcsolódni bármelyik (amit fent említettem) távoli eszköz portjához, és leválni róla.

Ehhez a következő funkciók lettek implementálva:

Egy függvény, amely socket címet vár paraméterül, megnyitja a kapcsolatot és elindítja a bemeneti és kimeneti szálakat (a szál változói fent lettek deklarálva, a teljes forráskód a cikk végén található).

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

Ugyanennek a funkciónak a túlterhelése, amely az alapértelmezett porthoz csatlakozik - a telnet esetében ez a 23


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

A funkció a billentyűzetről olvas karaktereket, és azokat a kimeneti socketre küldi – jellemzően sor- és nem karaktermó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ó az adatokat a socketből veszi és kiírja a képernyőre


    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 küldését


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

TelnetServer

Ennek az osztálynak rendelkeznie kell azzal a funkcióval, hogy parancsot fogad a socketből, végrehajtja azt, és a parancs válaszát visszaküldi a socketre. A programban szándékosan nincs bemeneti adatellenőrzés, mert egyrészt a "do-boxtelnetben" is van lehetőség a szerver lemezének formázására, másrészt a biztonsági kérdés ebben a cikkben alapvetően elkerülésre került, így itt egy szó sem esik sem titkosításról, sem SSL-ről.

Itt mindössze 2 funkció van (az egyik túlterhelt), és ez összességében nem egy túl jó gyakorlat, azonban a feladat keretein belül megfelelőnek tartottam mindent úgy hagyni, ahogy van.

 boolean isRunning = true;
    public void run(int port)    {

        (new Thread(()->{ try {
            ServerSocket ss = new ServerSocket(port); // szerver socket létrehozása és a megadott porthoz való csatolása
            System.out.println("Port "+port+" connections-re vár");

            Socket socket = ss.accept();
            System.out.println("Kapcsolódva");
            System.out.println();

            // Az socket bemeneti és kimeneti folyamait vesszük, most már adatokat küldhetünk és kaphatunk az ügyféltől.
            InputStream sin = socket.getInputStream();
            OutputStream sout = socket.getOutputStream();

            Map 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 = "Valami elromlott... Hiba kód: "+ 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("Hiba: " + e.getMessage());
                    }
                }).start();
                System.out.println(buffer);
                buffer = "";

            }
        }
        catch(Exception x) {
            System.out.println(x.getMessage());
        }})).start();

    }

A program megnyit egy szerver portot, olvas adatokat tőle, amíg a parancs végét jelző karaktert nem találja, a parancsot egy új folyamatba küldi, és a folyamat kimenete átirányításra kerül a socket-re. Mindez olyan egyszerű, mint egy Kalasnyikov gépkarabély.

Ezért ennek a funkciónak van egy előtöltése az alapértelmezett porttal:

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

És természetesen a szervert leállító funkció is nagyon egyszerű, megszakítja a végtelen ciklust, megszegve annak feltételét.

    public void stop()
    {
        System.out.println("A szerver leállt");
        this.isRunning = false;
    }

Nem hozok ide tesztosztályokat, azok lent találhatók — mindössze annyit tesznek, hogy ellenőrzik a nyilvános metódusok működését. Minden ott van a github-on.

Összefoglalva, néhány este alatt megérthetik a fő konzolos segédprogramok működési elveit. Most, hogy telnetelek egy távoli számítógépre, már értjük, mi történik — a varázslat eltűnt)

Tehát, itt vannak a linkek:
Minden forrás itt volt, van és lesz
A Telnetről
További Telnet információk

Forrás: habr.com

Megbízható hosting vásárlása weboldalak számára, DDoS védelemmel, VPS VDS szerverek. 🔥 Megbízható webtárhely vásárlása DDoS védelemmel, VPS VDS szerverek | ProHoster