Bonjour.
Aujourd'hui, j'aimerais discuter du processus de crĂ©ation d'applications client-serveur qui remplissent les fonctions des utilitaires Windows standard, tels que Telnet, TFTP, etc., en Java pure. Il est clair que je n'apporterai rien de nouveau â tous ces utilitaires fonctionnent avec succĂšs depuis plusieurs annĂ©es, mais je pense que tout le monde ne sait pas ce qui se passe en coulisses.
C'est précisément de cela qu'il sera question ci-dessous.
Dans cet article, afin de ne pas trop l'allonger, je ne parlerai que du serveur Telnet, mais à l'heure actuelle, j'ai aussi des informations sur d'autres utilitaires qui seront présentées dans les prochaines parties de la série.
Tout d'abord, il est nĂ©cessaire de comprendre ce qu'est Telnet, Ă quoi il sert et comment il fonctionne. Je ne vais pas citer les sources littĂ©ralement (si nĂ©cessaire, je joindrai un lien vers des matĂ©riaux sur le sujet Ă la fin de l'article), je dirai juste que Telnet permet un accĂšs Ă distance Ă l'invite de commande d'un appareil. En gros, c'est lĂ que s'arrĂȘte sa fonctionnalitĂ© (je ne parlerai pas de l'accĂšs au port serveur, c'est dĂ©libĂ©rĂ©, nous y reviendrons plus tard). Cela signifie que pour le mettre en Ćuvre, nous devons recevoir une chaĂźne sur le client, la transmettre au serveur, essayer de l'exĂ©cuter dans l'invite de commande, lire la rĂ©ponse de l'invite de commande, si elle existe, la renvoyer au client et l'afficher Ă l'Ă©cran, ou, en cas d'erreur, informer l'utilisateur qu'il y a un problĂšme.
Pour réaliser ce qui précÚde, il nous faut donc 2 classes fonctionnelles, et une certaine classe de test à partir de laquelle nous lancerons le serveur et à travers laquelle le client travaillera.
Ainsi, Ă l'heure actuelle, la structure de l'application comprend :
- TelnetClient
- TelnetClientTester
- TelnetServer
- TelnetServerTester
Examinons chacun d'eux :
TelnetClient
Tout ce que cette classe doit pouvoir faire est d'envoyer les commandes reçues et d'afficher les rĂ©ponses reçues. De plus, elle doit ĂȘtre capable de se connecter Ă un port arbitraire (comme mentionnĂ© prĂ©cĂ©demment) de l'appareil distant et de s'en dĂ©connecter.
Pour cela, les fonctions suivantes ont Ă©tĂ© mises en Ćuvre :
Une fonction qui prend en argument l'adresse du socket, ouvre une connexion et lance les flux d'entrée et de sortie (les variables de flux sont déclarées ci-dessus, le code complet est à la fin de l'article).
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());
}
}
Surcharge de cette mĂȘme fonction, se connectant au port par dĂ©faut â pour Telnet, c'est 23
public void run(String ip)
{
run(ip, 23);
}
La fonction lit les caractĂšres du clavier et les envoie au socket de sortie â notons que cela se fait en mode ligne, et non caractĂšre :
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 fonction reçoit les données du socket et les affiche à l'écran
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 fonction arrĂȘte la rĂ©ception et la transmission des donnĂ©es
public void stop()
{
reader.stop();
writer.stop();
}
}TelnetServer
Cette classe doit possĂ©der la fonctionnalitĂ© de recevoir une commande depuis le socket, de l'exĂ©cuter et d'envoyer la rĂ©ponse de la commande de retour au socket. Le programme ne vĂ©rifie intentionnellement pas les entrĂ©es, car d'une part, mĂȘme dans un « Telnet box » il est possible de formater le disque du serveur, et d'autre part, la question de la sĂ©curitĂ© n'est pas abordĂ©e dans cet article, c'est pourquoi il n'est pas question de cryptage ou de SSL ici.
Il y a seulement 2 fonctions (l'une d'elles est surchargée), et en général ce n'est pas une trÚs bonne pratique, cependant, dans le cadre de cette tùche, il m'a semblé approprié de laisser tout comme c'est.
boolean isRunning = true;
public void run(int port) {
(new Thread(()->{ try {
ServerSocket ss = new ServerSocket(port); // crée un socket serveur et le lie au port spécifié
System.out.println("Port " + port + " en attente de connexions");
Socket socket = ss.accept();
System.out.println("Connecté");
System.out.println();
// Obtenez les flux d'entrée et de sortie du socket, nous pouvons maintenant recevoir et envoyer des données au 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 = "Quelque chose ne va pas... Code d'erreur : " + 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("Erreur : " + e.getMessage());
}
}).start();
System.out.println(buffer);
buffer = "";
}
}
catch(Exception x) {
System.out.println(x.getMessage());
}})).start();
}
Le programme ouvre un port serveur, lit les données jusqu'à rencontrer un symbole de fin de commande, transmet la commande à un nouveau processus, et l'affichage de ce processus est redirigé vers le socket. C'est aussi simple qu'un fusil d'assaut Kalachnikov.
Il existe donc une surcharge pour cette fonction avec un port par défaut :
public void run()
{
run(23);
}Et donc, la fonction qui arrĂȘte le serveur est Ă©galement triviale, elle interrompt la boucle infinie en violant sa condition.
public void stop()
{
System.out.println("Le serveur a Ă©tĂ© arrĂȘtĂ©");
this.isRunning = false;
}Je ne vais pas fournir ici les classes de test, elles sont en bas â tout ce qu'elles font, c'est vĂ©rifier le bon fonctionnement des mĂ©thodes publiques. Tout est sur Git.
En rĂ©sumĂ©, en quelques soirĂ©es, on peut comprendre les principes de base des utilitaires de console. Maintenant, lorsque nous nous connectons au PC distant, nous comprenons ce qui se passe â la magie a disparu)
Voici les liens :
Source : habr.com
