Salve.
Oggi vorrei esaminare il processo di sviluppo di applicazioni client-server che svolgono funzioni di utility standard di Windows, come Telnet, TFTP, eccetera, eccetera, utilizzando puro Java. È chiaro che non porterò nulla di nuovo — tutte queste utility funzionano con successo da diversi anni, ma credo che non tutti sappiano cosa accade sotto il cofano.
Di questo si parlerà nel seguito.
In questo articolo, per non allungarlo troppo, oltre alle informazioni generali parlerò solo del server Telnet, ma al momento ci sono anche materiali su altre utility — saranno inclusi nelle prossime parti del ciclo.
Prima di tutto, dobbiamo chiarire cosa sia Telnet, a cosa serva e come funziona. Non citerò letteralmente le fonti (se necessario, allegherò un link ai materiali pertinenti alla fine dell'articolo), dirò solo che Telnet consente l'accesso remoto al prompt dei comandi dell' dispositivo. In sostanza, la sua funzionalità si esaurisce qui (ho omesso di menzionare l'accesso alla porta del server volontariamente, di ciò parleremo più tardi). Quindi, per la sua implementazione, dobbiamo ricevere una stringa sul client, trasmetterla al server, cercare di inviarla al prompt dei comandi, leggere la risposta del prompt dei comandi, se presente, restituirla al client e mostrarla a schermo, oppure, in caso di errore, far capire all'utente che qualcosa non funziona.
Per realizzare quanto sopra, sono necessari, di conseguenza, 2 classi funzionanti e una classe di test da cui avvieremo il server e attraverso la quale funzionerà il client.
Pertanto, attualmente la struttura dell'applicazione consiste in:
- TelnetClient
- TelnetClientTester
- TelnetServer
- TelnetServerTester
Passiamo rapidamente in rassegna ciascuna di esse:
TelnetClient
Tutto ciò che questa classe deve fare è inviare i comandi ricevuti e mostrare le risposte ricevute. Inoltre, deve essere in grado di connettersi a una porta remota a piacimento (come detto sopra) e disconnettersi da essa.
Per questo sono state implementate le seguenti funzioni:
Una funzione che riceve come argomento l'indirizzo del socket, apre una connessione e avvia i flussi di input e output (le variabili di flusso sono state dichiarate sopra, i sorgenti completi sono alla fine dell'articolo).
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());
}
}
Sovraccarico della stessa funzione, che si connette alla porta predefinita: per telnet è 23
public void run(String ip)
{
run(ip, 23);
}
La funzione legge i caratteri dalla tastiera e li invia al socket di uscita — cosa tipica, in modalità a riga, non a carattere:
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());
}
}
La funzione riceve dati dal socket e li visualizza sullo schermo
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());
}
}
La funzione interrompe la ricezione e la trasmissione dei dati
public void stop()
{
reader.stop();
writer.stop();
}
}TelnetServer
Questa classe deve avere la funzionalità di ricevere un comando dal socket, inviarlo per l'esecuzione e rispedire la risposta del comando indietro al socket. Nel programma non ci sono controlli sui dati in ingresso, perché, da un lato, nel "telnet di base" c'è la possibilità di formattare il disco del server e, dall'altro, la questione della sicurezza è completamente trascurata in questo articolo, ed è per questo che qui non si menziona nulla riguardo cifratura o SSL.
Ci sono solo 2 funzioni (una delle quali è sovraccaricata), e in generale questa non è una buona pratica, tuttavia nel contesto di questo compito mi è sembrato opportuno lasciare tutto com'è.
boolean isRunning = true;
public void run(int port) {
(new Thread(()->{ try {
ServerSocket ss = new ServerSocket(port); // creiamo un socket server e lo colleghiamo alla porta specificata
System.out.println("Port " + port + " is waiting for connections");
Socket socket = ss.accept();
System.out.println("Connesso");
System.out.println();
// Prendiamo i flussi di input e output del socket, ora possiamo ricevere e inviare dati al client.
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 = "Qualcosa è andato storto... Codice errore: " + 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("Errore: " + e.getMessage());
}
}).start();
System.out.println(buffer);
buffer = "";
}
}
catch(Exception x) {
System.out.println(x.getMessage());
}})).start();
}
Il programma apre una porta server, legge i dati finché non incontra il carattere di fine comando, trasferisce il comando a un nuovo processo e l'output del processo è reindirizzato al socket. È semplice come un Kalashnikov.
Di conseguenza, per questa funzione esiste un sovraccarico con una porta predefinita:
public void run()
{
run(23);
}E, di conseguenza, la funzione che ferma il server è anch'essa molto semplice, interrompe il ciclo infinito, violando la sua condizione.
public void stop()
{
System.out.println("Il server è stato arrestato");
this.isRunning = false;
}Non fornirò qui le classi di test, sono in basso — tutto ciò che fanno è verificare il funzionamento dei metodi pubblici. Tutto è su Git.
In sintesi, in un paio di serate si possono comprendere i principi di funzionamento delle principali utilità da console. Ora, quando ci connettiamo a un computer remoto, comprendiamo cosa succede — la magia è scomparsa)
Quindi, i link:
Fonte: habr.com
