Hallo.
Vandaag wil ik het proces bespreken van het schrijven van client-serverapplicaties die de functies uitvoeren van standaard Windows-hulpprogramma's, zoals Telnet, TFTP, enzovoorts, in pure Java. Het is duidelijk dat ik niets nieuws zal toevoegen — al deze hulpprogramma's werken al geruime tijd succesvol, maar ik denk dat niet iedereen weet wat er onder de motorkap gebeurt.
Hierover zal ik het verderop in de tekst hebben.
In dit artikel zal ik, om het niet te lang te maken, alleen informatie over de Telnet-server geven, maar op dit moment is er ook materiaal over andere hulpprogramma's — dit komt in de volgende delen van de cyclus.
Allereerst moeten we begrijpen wat Telnet is, waarvoor het dient en wat je ermee kunt. Ik ga geen citaten uit bronnen geven (als het nodig is, zal ik aan het einde van het artikel een link naar het onderwerp toevoegen), maar ik zal alleen zeggen dat Telnet toegang op afstand biedt tot de opdrachtregel van een apparaat. Grofweg eindigt hier zijn functionaliteit (ik laat het aanroepen van de serverpoort bewust buiten beschouwing, hierover later meer). Dit betekent dat we om het te implementeren, een string op de client moeten accepteren, deze naar de server moeten verzenden, proberen deze door te geven aan de opdrachtregel, en als er een antwoord is, dit weer naar de client moeten sturen en op het scherm moeten weergeven, of, in geval van een fout, de gebruiker moeten laten begrijpen dat er iets mis is.
Om het bovenstaande te realiseren, hebben we dus 2 werkende klassen en een testklasse nodig, vanuit welke we de server zullen starten en waarmee de client zal werken.
De huidige structuur van de applicatie omvat:
- TelnetClient
- TelnetClientTester
- TelnetServer
- TelnetServerTester
Laten we elk van hen kort bekijken:
TelnetClient
Alles wat deze klasse moet kunnen, is het verzenden van de ontvangen opdrachten en het tonen van de ontvangen reacties. Daarnaast moet het in staat zijn om verbinding te maken met een willekeurige (zoals hierboven vermeld) poort van het externe apparaat en deze te ontkoppelen.
Hiervoor zijn de volgende functies geïmplementeerd:
Een functie die als argument het socketadres accepteert, een verbinding opent en de invoer- en uitvoerstromen start (de variabelen voor de stroom zijn hierboven gedefinieerd, de volledige code is aan het einde van het artikel te vinden).
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());
}
}
Overloading van dezelfde functie, die verbinding maakt met de standaardpoort – voor telnet is dat 23
public void run(String ip)
{
run(ip, 23);
}
De functie leest tekens van het toetsenbord en verzendt ze naar de uitgangssocket – wat opmerkelijk is, in lijnmodus en niet in tekenmodus:
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());
}
}
De functie ontvangt gegevens van de socket en toont deze op het scherm.
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());
}
}
De functie stopt de ontvangst en verzending van gegevens.
public void stop()
{
reader.stop();
writer.stop();
}
}TelnetServer
Deze klasse moet de functionaliteit hebben om een commando vanuit de socket te ontvangen, deze uit te voeren en het antwoord van het commando terug naar de socket te sturen. In het programma is er opzettelijk geen controle op de invoer, omdat ten eerste, er in de "standaard telnet" de mogelijkheid is om de schijf van de server te formatteren, en ten tweede, de beveiliging in dit artikel niet aan de orde is, en daarom wordt er geen woord gezegd over versleuteling of SSL.
Er zijn in totaal 2 functies (waarvan één is overbelast), en over het algemeen is dit geen goede praktijk, maar in het kader van deze taak leek het gepast om alles zoals het is te laten.
boolean isRunning = true;
public void run(int port) {
(new Thread(()->{ try {
ServerSocket ss = new ServerSocket(port); // maak een server socket en bind deze aan de opgegeven poort
System.out.println("Poort "+port+" wacht op verbindingen");
Socket socket = ss.accept();
System.out.println("Verbonden");
System.out.println();
// Verkrijg de invoer- en uitvoerstromen van de socket, nu kunnen we gegevens ontvangen en verzenden naar de klant.
InputStream sin = socket.getInputStream();
OutputStream sout = socket.getOutputStream();
Map env = System.getenv();
String wayToTemp = env.get("TEMP") + "tmp.txt";
for (int i : ("Verbondennnr".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 = "Er is iets misgegaan... Foutcode: "+ 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("Fout: " + e.getMessage());
}
}).start();
System.out.println(buffer);
buffer = "";
}
}
catch(Exception x) {
System.out.println(x.getMessage());
}})).start();
}
Het programma opent een serverpoort, leest gegevens van deze poort totdat het het commando-einde teken tegenkomt, geeft het commando door aan een nieuw proces, en de uitvoer van het proces wordt doorgestuurd naar de socket. Zo eenvoudig als een Kalashnikov-geweer.
Er is dus een overloaded versie van deze functie met een standaardpoort:
public void run()
{
run(23);
}En dus, de functie die de server stopt, is ook heel eenvoudig; hij onderbreekt de oneindige lus door de voorwaarde ervan te schenden.
public void stop()
{
System.out.println("Server was stopped");
this.isRunning = false;
}Ik zal hier geen testklassen geven, deze staan onderaan — alles wat ze doen, is de functionaliteit van de openbare methoden controleren. Alles is te vinden op Git.
Samenvattend, in een paar avonden kun je de principes van de belangrijkste console-tools begrijpen. Nu, wanneer we inloggen op een externe computer, begrijpen we wat er aan de hand is — de magie is verdwenen.)
Dus, de links:
Bron: habr.com
