Hej.
I dag vil jeg gerne gennemgå processen med at skrive klient-server-applikationer, der udfører funktioner som standard Windows-værktøjer som Telnet, TFTP og så videre på ren Java. Det er klart, at jeg ikke vil bringe noget nyt - alle disse værktøjer har allerede fungeret i mange år, men jeg tror, at ikke alle ved, hvad der sker under sædet hos dem.
Det er netop dette, vi vil tale om i det følgende.
I denne artikel, for ikke at trække den ud, vil jeg kun skrive om Telnet-serveren, men der er også materiale om andre værktøjer - det vil komme i de senere dele af cyklussen.
Først og fremmest skal vi forstå, hvad Telnet er, hvad det bruges til, og hvordan man bruger det. Jeg vil ikke citere kilder ordret (hvis det er nødvendigt, vedhæfter jeg et link til materialer om emnet i slutningen af artiklen), men jeg vil sige, at Telnet giver fjernadgang til kommandoprompten på en enhed. Stort set slutter dets funktionalitet her (jeg vil bevidst undlade at nævne opkald til serverporten, som vi vil komme ind på senere). Så for at implementere det, skal vi tage en streng på klienten, sende den til serveren, forsøge at sende den til kommandoprompten, læse svaret, hvis der er et, sende det tilbage til klienten og vise det på skærmen, eller i tilfælde af en fejl, gøre brugeren opmærksom på, at der er noget galt.
For at realisere det ovenstående, behøver vi således 2 arbejdsrelaterede klasser og en testklasse, fra hvilken vi vil starte serveren og gennem hvilken klienten vil fungere.
Strukturen af applikationen indeholder på nuværende tidspunkt:
- TelnetClient
- TelnetClientTester
- TelnetServer
- TelnetServerTester
Lad os tage et kig på hver af dem:
TelnetClient
Alt, hvad denne klasse skal kunne, er at sende de modtagne kommandoer og vise de modtagne svar. Derudover skal den kunne oprette forbindelse til en hvilken som helst (som nævnt tidligere) port på den fjernliggende enhed og afbryde forbindelsen.
Til dette er følgende funktioner blevet implementeret:
En funktion, der tager en socket-adresse som argument, åbner en forbindelse og starter ind- og uddata-tråde (variablerne til trådene er erklæret ovenfor, de fulde kilder vil være i slutningen af artiklen).
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());
}
}
Overbelastning af denne samme funktion, der opretter forbindelse til porten som standard — for telnet er den 23
public void run(String ip)
{
run(ip, 23);
}
Funktionen læser tegn fra tastaturet og sender dem til udgangssocketen — hvilket er karakteristisk i linje- og ikke tegnmode:
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());
}
}
Funktionen modtager data fra socketen og viser dem på skærmen
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());
}
}
Funktionen stopper modtagelse og overførsel af data
public void stop()
{
reader.stop();
writer.stop();
}
}TelnetServer
Denne klasse skal have funktionalitet til at modtage kommandoer fra socketen, sende dem til udførelse og sende svaret fra kommandoen tilbage til socketen. Programmet indeholder med vilje ikke indtastningskontrol, fordi for det første, i den "kasseformat telnet", er der mulighed for at formatere serverens disk, og for det andet, er spørgsmålet om sikkerhed i denne artikel helt udeladt, og derfor er der ikke et ord om kryptering eller SSL.
Der er kun 2 funktioner (den ene er overbelastet), og generelt set er det ikke en særlig god praksis, men i forbindelse med denne opgave syntes jeg, det var passende at lade alt være som det er.
boolean isRunning = true;
public void run(int port) {
(new Thread(() -> { try {
ServerSocket ss = new ServerSocket(port); // opretter server socket og binder den til den angivne port
System.out.println("Port " + port + " venter på forbindelser");
Socket socket = ss.accept();
System.out.println("Forbundet");
System.out.println();
// Henter ind- og udgangsstrømmene fra socketten, så vi kan modtage og sende data til klienten.
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 = "Noget gik galt... Fejl kode: " + 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("Fejl: " + e.getMessage());
}
}).start();
System.out.println(buffer);
buffer = "";
}
}
catch(Exception x) {
System.out.println(x.getMessage());
}})).start();
}
Programmet åbner en serverport, læser data fra den, indtil det møder en kommandoslutter, sender kommandoen til en ny proces, og output fra processen omdirigeres til socketten. Alt er ligetil som en Kalashnikov.
Der findes således en overbelastning af denne funktion med en standardport:
public void run()
{
run(23);
}Og tilsvarende er funktionen, der stopper serveren, også triviel; den bryder den evige løkke ved at bryde dens betingelse.
public void stop()
{
System.out.println("Server was stopped");
this.isRunning = false;
}Jeg vil ikke inkludere testklasser her, de findes nedenfor – alt hvad de gør – tester funktionaliteten af de offentlige metoder. Alt er på Git.
For at opsummere, kan man på et par aftener forstå principperne bag de grundlæggende konsolværktøjer. Nu, når vi bruger telnet til den eksterne computer, forstår vi, hvad der sker – magien er forsvundet)
Så links:
Kilde: habr.com
