Willkommen.
Heute möchte ich den Prozess der Erstellung von Client-Server-Anwendungen besprechen, die Funktionen von Standard-Windows-Dienstprogrammen wie Telnet, TFTP usw. in reinem Java übernehmen. Ich weiß, dass ich nichts Neues einführe – all diese Dienstprogramme funktionieren seit vielen Jahren erfolgreich, aber ich denke, nicht jeder kennt die Funktionsweise im Hintergrund.
Genau darum wird es im Folgenden gehen.
In diesem Artikel werde ich, um ihn nicht unnötig in die Länge zu ziehen, neben allgemeinen Informationen nur über den Telnet-Server schreiben, aber derzeit gibt es auch Material zu anderen Dienstprogrammen – dieses wird in weiteren Teilen des Zyklen behandelt.
Zunächst einmal sollten wir klären, was Telnet ist, wozu es dient und wie es verwendet wird. Ich werde keine wörtlichen Zitate aus Quellen anführen (falls erforderlich, werde ich am Ende des Artikels Links zu den Materialien bereitstellen). Nur so viel: Telnet ermöglicht den Remote-Zugriff auf die Befehlszeile eines Geräts. Im Grunde endet damit auch seine Funktionalität (meine Auslassung zum Zugriff auf den Serverport war absichtlich, dazu später mehr). Um Telnet zu implementieren, müssen wir eine Zeile im Client empfangen, sie an den Server senden, versuchen, sie in die Befehlszeile einzugeben, die Antwort der Befehlszeile, falls vorhanden, zurück an den Client senden und auf dem Bildschirm ausgeben oder im Falle eines Fehlers dem Benutzer klar machen, dass etwas nicht stimmt.
Für die Umsetzung des oben genannten benötigt man accordingly 2 funktionierende Klassen sowie eine Testklasse, von der aus wir den Server starten und über die der Client betrieben wird.
Derzeit umfasst die Struktur der Anwendung:
- TelnetClient
- TelnetClientTester
- TelnetServer
- TelnetServerTester
Lassen Sie uns jeden dieser Punkte durchgehen:
TelnetClient
Die Hauptaufgabe dieser Klasse besteht darin, empfangene Befehle zu senden und die erhaltenen Antworten anzuzeigen. Außerdem muss sie in der Lage sein, sich mit beliebigen (wie bereits erwähnt) Ports eines entfernten Geräts zu verbinden und sich von ihm zu trennen.
Zu diesem Zweck wurden die folgenden Funktionen implemented:
Eine Funktion, die als Argument die Socket-Adresse akzeptiert, eine Verbindung herstellt und die Eingangs- und Ausgangsströme startet (die Variablen für die Streams wurden oben deklariert, die vollständigen Quellcodes befinden 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());
}
}
Eine Überladung dieser Funktion, die sich mit dem Standardport verbindet – für Telnet ist dies 23.
public void run(String ip)
{
run(ip, 23);
}
Die Funktion liest Zeichen von der Tastatur und sendet sie an den Ausgangssocket – bemerkenswert ist, dass dies im Zeilen- und nicht im Zeichenmodus geschieht:
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 soll die Funktionalität bieten, Befehle vom Socket entgegenzunehmen, sie zur Ausführung zu senden und die Antwort des Befehls zurück an den Socket zu senden. In diesem Programm gibt es absichtlich keine Eingabedatenüberprüfung, da es erstens auch in einem "Boxed Telnet" möglich ist, die Festplatte des Servers zu formatieren, und zweitens wird das Thema Sicherheit in diesem Artikel grundsätzlich nicht behandelt, weshalb es hier kein Wort zu Verschlüsselung oder SSL gibt.
Es gibt insgesamt nur 2 Funktionen (eine davon ist überladen), und insgesamt ist das nicht die beste Praxis, aber im Rahmen dieser Aufgabe schien es mir sinnvoll, alles so zu belassen.
boolean isRunning = true;
public void run(int port) {
(new Thread(() -> { try {
ServerSocket ss = new ServerSocket(port); // Erstellen eines Server-Sockets und Binden an den angegebenen Port
System.out.println("Port " + port + " wartet auf Verbindungen");
Socket socket = ss.accept();
System.out.println("Verbunden");
System.out.println();
// Zugriff auf die Eingangs- und Ausgangsströme des Sockets, jetzt können Daten an den Client gesendet und empfangen werden.
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 = "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 von ihm Daten, bis es ein Endzeichen für den Befehl findet, übergibt den Befehl an einen neuen Prozess und leitet die Ausgabe des Prozesses an einen Socket weiter. Ganz einfach, wie ein Kalashnikov-Gewehr.
Dementsprechend gibt es für diese Funktion eine Überladung mit dem Standardport:
public void run()
{
run(23);
}Und ebenso ist die Funktion, die den Server stoppt, auch unkompliziert; sie bricht die Endlosschleife, indem sie deren Bedingung verletzt.
public void stop()
{
System.out.println("Der Server wurde gestoppt");
this.isRunning = false;
}Ich werde hier keine Testklassen anführen, sie sind weiter unten zu finden — alles, was sie tun, ist, die Funktionalität der öffentlichen Methoden zu überprüfen. Alles ist auf GitHub verfügbar.
Zusammenfassend kann man in ein paar Abenden die Funktionsweise der wichtigsten Konsolenwerkzeuge verstehen. Jetzt, wo wir mit Telnet auf den entfernten Rechner zugreifen, verstehen wir, was passiert — die Magie ist verschwunden)
Also, die Links:
Quelle: habr.com
