Hallo.
Heute möchte ich den Prozess der Erstellung von Client-Server-Anwendungen besprechen, die die Funktionen von Standard-Utilities wie Telnet, TFTP usw. in reinem Java ausführen. Es ist klar, dass ich nichts Neues einbringen werde — all diese Utilities arbeiten bereits seit vielen Jahren erfolgreich, aber ich denke, nicht jeder weiß, was unter der Haube passiert.
Darum wird es unter dem Cut gehen.
In diesem Artikel werde ich, um ihn nicht zu lange zu gestalten, außer allgemeinen Informationen nur über den Telnet-Server schreiben. Aber es gibt noch Material zu anderen Utilities — das wird in den weiteren Teilen dieser Serie behandelt.
Zunächst sollte geklärt werden, was Telnet ist, wozu es dient und wie man es verwendet. Ich werde keine wörtlichen Zitate aus den Quellen anführen (wenn nötig, füge ich am Ende des Artikels Links zu den Materialien hinzu), ich sage nur, dass Telnet den Remote-Zugang zur Kommandozeile eines Geräts ermöglicht. Im Grunde endet damit seine Funktionalität (ich habe absichtlich das zugrunde liegende Serverport-Thema nicht angesprochen, dazu später mehr). Das bedeutet, um es zu implementieren, müssen wir eine Eingabezeile auf dem Client annehmen, sie an den Server übermitteln, versuchen, sie an die Kommandozeile zu übergeben, die Antwort der Kommandozeile zu lesen, falls vorhanden, sie zurück an den Client zu schicken und sie auf dem Bildschirm anzuzeigen oder im Falle eines Fehlers dem Benutzer mitzuteilen, dass etwas nicht in Ordnung ist.
Für die oben genannte Implementierung benötigen wir entsprechend 2 funktionierende Klassen und eine Testklasse, von der aus wir den Server starten und über die der Client arbeiten wird.
Derzeit umfasst die Struktur der Anwendung:
- TelnetClient
- TelnetClientTester
- TelnetServer
- TelnetServerTester
Lassen Sie uns jeden von ihnen durchgehen:
TelnetClient
Alles, was diese Klasse tun sollte, ist, die empfangenen Befehle zu senden und die empfangenen Antworten anzuzeigen. Außerdem sollte sie in der Lage sein, sich mit einem beliebigen (wie oben erwähnt) Remote-Port zu verbinden und sich davon zu trennen.
Dafür wurden folgende Funktionen implementiert:
Eine Funktion, die als Argument die Socket-Adresse akzeptiert, eine Verbindung öffnet und die Ein- und Ausgabeströme startet (die Variablen des Streams wurden oben deklariert, der vollständige Quellcode befindet sich 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());
}
}
Überladung derselben Funktion, die sich mit dem Standardport verbindet – für Telnet ist das 23
public void run(String ip)
{
run(ip, 23);
}
Die Funktion liest Zeichen von der Tastatur und sendet sie an den Ausgabesocket – bemerkenswerterweise im Zeilen- und 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 gibt sie auf dem Bildschirm aus
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 sollte die Funktionalität haben, Befehle vom Socket zu empfangen, diese zur Ausführung zu senden und die Antwort des Befehls zurück an den Socket zu senden. In der Programmierung gibt es absichtlich keine Überprüfung der Eingabedaten, da es erstens auch im 'Box-Telnet' die Möglichkeit gibt, die Festplatte des Servers zu formatieren und zweitens die Sicherheitsfrage in diesem Artikel generell ausgeklammert wird, weshalb es hier kein Wort über Verschlüsselung oder SSL gibt.
Es gibt hier insgesamt 2 Funktionen (eine davon ist überladen), und im Allgemeinen ist es nicht sehr gute Praxis, aber im Rahmen dieser Aufgabe erschien es mir angemessen, alles so zu belassen wie es ist.
boolean isRunning = true;
public void run(int port) {
(new Thread(()->{ try {
ServerSocket ss = new ServerSocket(port); // Wir erstellen einen Server-Socket und binden ihn an den angegebenen Port
System.out.println("Port " + port + " wartet auf Verbindungen");
Socket socket = ss.accept();
System.out.println("Verbunden");
System.out.println();
// Wir erhalten die Ein- und Ausgabeströme des Sockets, jetzt können wir Daten an den Client senden und empfangen.
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 = "Etwas ist schiefgelaufen... Fehlercode: " + 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("Fehler: " + e.getMessage());
}
}).start();
System.out.println(buffer);
buffer = "";
}
}
catch(Exception x) {
System.out.println(x.getMessage());
}})).start();
}
Das Programm öffnet einen Serverport, liest Daten von diesem, bis es ein Ende-Symbol der Kommandozeile trifft, übergibt den Befehl an einen neuen Prozess, und die Ausgabe des Prozesses wird an den Socket weitergeleitet. Ganz einfach wie ein Kalaschnikow-Sturmgewehr.
Entsprechend gibt es für diese Funktion eine Überladung mit dem Standardport:
public void run()
{
run(23);
}Die Funktion, die den Server stoppt, ist ebenfalls sehr einfach, sie bricht die Endlosschleife, indem sie deren Bedingung stört.
public void stop()
{
System.out.println("Server wurde gestoppt");
this.isRunning = false;
}Ich werde hier keine Testklassen anführen, sie sind weiter unten – alles, was sie tun, ist, die Funktionalität der öffentlichen Methoden zu überprüfen. Alles ist auf Git.
Zusammenfassend lässt sich sagen, dass man an ein paar Abenden die Funktionsweise der grundlegenden Konsolen-Dienstprogramme verstehen kann. Jetzt, wo wir uns mit Telnet zu einem entfernten Computer verbinden, verstehen wir, was geschieht – die Magie ist verschwunden)
Hier sind die Links:
Quelle: habr.com
