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

Grüße.

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

Dies wird unter dem Schnitt besprochen.

Um dies nicht zu verzögern, werde ich in diesem Artikel zusätzlich zu allgemeinen Informationen nur über den Telnet-Server schreiben, aber im Moment gibt es noch Material zu anderen Dienstprogrammen - es wird in den weiteren Teilen des Zyklus sein.

Zunächst sollten Sie verstehen, was Telnet ist, warum es benötigt wird und womit es gegessen wird. Ich werde die Quellen nicht wörtlich zitieren (falls erforderlich, werde ich am Ende des Artikels einen Link zu verwandten Materialien anhängen), ich kann nur sagen, dass Telnet Fernzugriff auf die Befehlszeile des Geräts bietet. Im Großen und Ganzen endet hier seine Funktionalität (ich habe den Server-Port stillschweigend bewusst kontaktiert, dazu später mehr). Für die Implementierung müssen wir also die Zeile auf dem Client akzeptieren, auf den Server übertragen, versuchen, sie auf die Befehlszeile zu übertragen, die Befehlszeilenantwort, falls vorhanden, zurücklesen und auf den Client übertragen oder im Falle eines Auftretens anzeigen Fehler, lassen Sie den Benutzer verstehen, dass etwas nicht stimmt.

Um dies zu implementieren, benötigen Sie dementsprechend zwei Arbeitsklassen und eine Testklasse, von der aus wir den Server starten und über die der Client arbeiten wird.
Dementsprechend umfasst die Struktur der Anmeldung derzeit:

  • Telnetclient
  • TelnetClientTester
  • TelnetServer
  • TelnetServerTester

Lassen Sie uns jeden einzelnen von ihnen durchgehen:

Telnetclient

Diese Klasse sollte lediglich in der Lage sein, die empfangenen 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ähnten) Port des Remote-Geräts herzustellen und die Verbindung zu diesem zu trennen.

Hierzu wurden folgende Funktionen implementiert:

Eine Funktion, die eine Socket-Adresse als Argument verwendet, eine Verbindung öffnet und Eingabe- und Ausgabestreams startet (Stream-Variablen sind oben deklariert, vollständige Quellen 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()); } } 

Überladen derselben Funktion, Herstellen einer Verbindung zum Standardport - für Telnet sind dies 23

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

Die Funktion liest Zeichen von der Tastatur und sendet sie an den Ausgabe-Socket. Dies ist typisch für die Zeichenfolge und nicht für den 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 beendet das Empfangen und Senden 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 akzeptieren, zur Ausführung zu senden und eine Antwort vom Befehl zurück an den Socket zu senden. Das Programm überprüft die Eingabedaten nicht absichtlich, da zum einen die Möglichkeit besteht, die Serverfestplatte im "Box-Telnet" zu formatieren, und zum anderen das Sicherheitsproblem in diesem Artikel grundsätzlich weggelassen wird. Deshalb gibt es kein Wort über Verschlüsselung oder SSL

Es gibt nur zwei Funktionen (eine davon ist überlastet), und im Allgemeinen ist dies keine gute Vorgehensweise. Im Rahmen dieser Aufgabe erschien es mir jedoch 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 :("Connected\n\n\r".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 das Befehlsbeendigungszeichen trifft, überträgt den Befehl an einen neuen Prozess und die Ausgabe des Prozesses wird an den Socket umgeleitet. Alles ist wie ein Kalaschnikow-Sturmgewehr.

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

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

Nun und dementsprechend ist die Funktion, die den Server stoppt, ebenfalls trivial. Sie unterbricht den ewigen Zyklus und verletzt seinen Zustand.

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

Ich werde hier keine Testklassen geben, sie sind ganz unten - alles, was sie tun, ist die Leistung öffentlicher Methoden zu überprüfen. Alles ist auf der Gita.

Zusammenfassend können Sie an einigen Abenden die Funktionsprinzipien der Hauptkonsolen-Dienstprogramme verstehen. Wenn wir jetzt zum Remote-Computer kommen, verstehen wir, was passiert - die Magie ist verschwunden.

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

Source: https://habr.com/ru/post/de460569/


All Articles