Schreiben von Software mit der Funktionalität von Windows-Client-Server-Dienstprogrammen, Teil 01

Willkommen.

Heute möchte ich mir den Prozess des Schreibens von Client-Server-Anwendungen ansehen, die die Funktionen von Standard-Windows-Dienstprogrammen wie Telnet, TFTP usw. usw. in reinem Java ausführen. Es ist klar, dass ich nichts Neues bringen werde – alle diese Dienstprogramme arbeiten seit mehr als einem Jahr erfolgreich, aber ich glaube, nicht jeder weiß, was unter der Haube vor sich geht.

Genau das wird unter dem Schnitt besprochen.

Um es nicht in die Länge zu ziehen, werde ich in diesem Artikel neben allgemeinen Informationen nur über den Telnet-Server schreiben, aber im Moment gibt es auch Material zu anderen Dienstprogrammen - es wird in weiteren Teilen der Serie sein.

Zunächst müssen Sie herausfinden, was Telnet ist, wofür es benötigt wird und wofür es verwendet wird. Ich werde Quellen nicht wörtlich zitieren (bei Bedarf werde ich am Ende des Artikels einen Link zu Materialien zu diesem Thema anhängen), sondern nur sagen, dass Telnet Fernzugriff auf die Befehlszeile des Geräts ermöglicht. Im Großen und Ganzen endet hier die Funktionalität (über den Zugriff auf den Server-Port habe ich bewusst Stillschweigen bewahrt; dazu später mehr). Das heißt, um es zu implementieren, müssen wir eine Zeile auf dem Client akzeptieren, sie an den Server übergeben, versuchen, sie an die Befehlszeile zu übergeben, die Befehlszeilenantwort lesen, falls vorhanden, sie an den Client zurückgeben und Zeigen Sie es auf dem Bildschirm an oder teilen Sie dem Benutzer bei Fehlern mit, dass etwas nicht stimmt.

Um das oben Gesagte umzusetzen, benötigen wir dementsprechend zwei Arbeitsklassen und eine Testklasse, von der aus wir den Server starten und über die der Client arbeiten wird.
Dementsprechend umfasst die Bewerbungsstruktur derzeit:

  • TelnetClient
  • TelnetClientTester
  • TelnetServer
  • TelnetServerTester

Gehen wir jeden einzelnen davon durch:

TelnetClient

Diese Klasse sollte lediglich in der Lage sein, empfangene Befehle zu senden und die empfangenen Antworten anzuzeigen. Darüber hinaus müssen Sie in der Lage sein, eine Verbindung zu einem beliebigen (wie oben erwähnt) Port eines Remote-Geräts herzustellen und von diesem zu trennen.

Um dies zu erreichen, wurden folgende Funktionen implementiert:

Eine Funktion, die eine Socket-Adresse als Argument verwendet, eine Verbindung öffnet und Eingabe- und Ausgabestreams startet (Stream-Variablen werden oben deklariert, vollständige Quellen finden Sie am Ende des Artikels).

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

Überlastung derselben Funktion, Verbindung zum Standardport – für Telnet ist dieser 23


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

Die Funktion liest Zeichen von der Tastatur und sendet sie an den Ausgabe-Socket – was typisch ist, im Zeilenmodus, nicht im Zeichenmodus:


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

Die Funktion empfängt Daten vom Socket und zeigt sie auf dem Bildschirm an


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

Die Funktion stoppt den Empfang und die Übertragung von Daten


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

TelnetServer

Diese Klasse muss über die Funktionalität verfügen, einen Befehl von einem Socket zu empfangen, ihn zur Ausführung zu senden und eine Antwort vom Befehl zurück an den Socket zu senden. Auf eine Überprüfung der Eingabedaten verzichtet das Programm bewusst, denn erstens ist es auch im „Boxed Telnet“ möglich, die Serverplatte zu formatieren, zweitens wird das Thema Sicherheit in diesem Artikel grundsätzlich weggelassen und ist es deshalb auch nicht ein Wort zur Verschlüsselung bzw. SSL.

Es gibt nur zwei Funktionen (eine davon ist überlastet), und im Allgemeinen ist dies keine sehr gute Vorgehensweise, aber für die Zwecke dieser Aufgabe schien es mir angemessen, alles so zu lassen, wie es ist.

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

    }

Das Programm öffnet den Server-Port, liest Daten von ihm, bis es auf ein Befehlsendezeichen trifft, übergibt den Befehl an einen neuen Prozess und leitet die Ausgabe des Prozesses an den Socket um. Alles ist so einfach wie ein Kalaschnikow-Sturmgewehr.

Dementsprechend gibt es für diese Funktion eine Überlastung mit einem Standardport:

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

Nun, dementsprechend ist auch die Funktion, die den Server stoppt, trivial, sie unterbricht die ewige Schleife und verletzt ihre Bedingung.

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

Ich werde hier keine Testklassen angeben, sie sind unten aufgeführt. Sie überprüfen lediglich die Funktionalität öffentlicher Methoden. Alles ist auf der Kippe.

Zusammenfassend lässt sich sagen, dass Sie an ein paar Abenden die Funktionsprinzipien der Hauptdienstprogramme der Konsole verstehen können. Wenn wir nun eine Telenetverbindung zu einem entfernten Computer herstellen, verstehen wir, was passiert – der Zauber ist verschwunden.)

Also die Links:
Alle Quellen waren, sind und werden hier sein
Über Telnet
Mehr über Telnet

Source: habr.com

Kommentar hinzufügen