Écriture de logiciels avec les fonctionnalités des utilitaires client-serveur Windows, partie 01

Bienvenue.

Aujourd'hui, je voudrais examiner le processus d'écriture d'applications client-serveur qui exécutent les fonctions des utilitaires Windows standard, tels que Telnet, TFTP, etc., et cetera en Java pur. Il est clair que je n'apporterai rien de nouveau - tous ces utilitaires fonctionnent avec succès depuis plus d'un an, mais je pense que tout le monde ne sait pas ce qui se passe sous le capot.

C’est exactement ce qui sera discuté sous la coupe.

Dans cet article, afin de ne pas l'éterniser, en plus des informations générales, j'écrirai uniquement sur le serveur Telnet, mais pour le moment, il existe également des informations sur d'autres utilitaires - ce sera dans d'autres parties de la série.

Tout d’abord, vous devez comprendre ce qu’est Telnet, à quoi il sert et à quoi il sert. Je ne citerai pas les sources textuellement (si nécessaire, je joindrai un lien vers des documents sur le sujet à la fin de l'article), je dirai seulement que Telnet fournit un accès à distance à la ligne de commande de l'appareil. Dans l'ensemble, c'est là que se termine sa fonctionnalité (j'ai délibérément gardé le silence sur l'accès au port du serveur ; nous en reparlerons plus tard). Cela signifie que pour l'implémenter, nous devons accepter une ligne sur le client, la transmettre au serveur, essayer de la transmettre à la ligne de commande, lire la réponse de la ligne de commande, s'il y en a une, la renvoyer au client et affichez-le à l'écran ou, en cas d'erreur, faites savoir à l'utilisateur que quelque chose ne va pas.

Pour implémenter ce qui précède, nous avons donc besoin de 2 classes de travail et d'une classe de test à partir de laquelle nous lancerons le serveur et à travers laquelle le client travaillera.
En conséquence, la structure de la candidature comprend actuellement :

  • Client Telnet
  • TelnetClientTester
  • TelnetServer
  • Testeur de serveur Telnet

Passons en revue chacun d'eux :

Client Telnet

Tout ce que cette classe devrait pouvoir faire, c'est envoyer les commandes reçues et afficher les réponses reçues. De plus, vous devez pouvoir vous connecter à un port arbitraire (comme mentionné ci-dessus) d'un périphérique distant et vous en déconnecter.

Pour y parvenir, les fonctions suivantes ont été implémentées :

Une fonction qui prend une adresse de socket comme argument, ouvre une connexion et démarre les flux d'entrée et de sortie (les variables de flux sont déclarées ci-dessus, les sources complètes se trouvent à 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 la même fonction, connexion 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 à la prise de sortie - ce qui est typique, en mode ligne, pas en mode 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 de la prise 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 avoir la fonctionnalité de recevoir une commande d'un socket, de l'envoyer pour exécution et de renvoyer une réponse de la commande au socket. Le programme ne vérifie délibérément pas les données d'entrée, car d'une part, même dans "telnet en boîte", il est possible de formater le disque du serveur, et d'autre part, la question de la sécurité dans cet article est en principe omise, et c'est pourquoi il n'y a pas un mot sur le cryptage ou SSL.

Il n'y a que 2 fonctions (l'une d'elles est surchargée), et en général ce n'est pas une très bonne pratique, mais pour les besoins de cette tâche, il m'a semblé opportun de tout laisser tel quel.

 boolean isRunning = true;
    public void run(int port)    {

        (new Thread(()->{ try {
            ServerSocket ss = new ServerSocket(port); // создаем сокет сервера и привязываем его к вышеуказанному порту
            System.out.println("Port "+port+" is waiting for connections");

            Socket socket = ss.accept();
            System.out.println("Connected");
            System.out.println();

            // Берем входной и выходной потоки сокета, теперь можем получать и отсылать данные клиенту.
            InputStream sin = socket.getInputStream();
            OutputStream sout = socket.getOutputStream();

            Map<String, String> 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 = "Something gets wrong... Err code: "+ 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("Error: " + e.getMessage());
                    }
                }).start();
                System.out.println(buffer);
                buffer = "";

            }
        }
        catch(Exception x) {
            System.out.println(x.getMessage());
        }})).start();

    }

Le programme ouvre le port du serveur, y lit les données jusqu'à ce qu'il rencontre un caractère de fin de commande, transmet la commande à un nouveau processus et redirige la sortie du processus vers le socket. Tout est aussi simple qu'un fusil d'assaut Kalachnikov.

En conséquence, il existe une surcharge pour cette fonction avec un port par défaut :

 public void run()
    {
        run(23);
    }

Eh bien, en conséquence, la fonction qui arrête le serveur est également triviale, elle interrompt la boucle éternelle, violant sa condition.

    public void stop()
    {
        System.out.println("Server was stopped");
        this.isRunning = false;
    }

Je ne donnerai pas de cours de test ici, ils sont ci-dessous - tout ce qu'ils font est de vérifier la fonctionnalité des méthodes publiques. Tout est sur git.

En résumé, en quelques soirées, vous pourrez comprendre les principes de fonctionnement des principaux utilitaires de la console. Maintenant, lorsque nous télénettons vers un ordinateur distant, nous comprenons ce qui se passe - la magie a disparu)

Donc les liens :
Toutes les sources étaient, sont et seront ici
À propos de Telnet
En savoir plus sur Telnet

Source: habr.com

Ajouter un commentaire