كتابة البرامج مع وظائف الأدوات المساعدة لخادم العميل في Windows، الجزء 01

تحية.

أود اليوم أن ألقي نظرة على عملية كتابة تطبيقات خادم العميل التي تؤدي وظائف الأدوات المساعدة القياسية لنظام التشغيل Windows، مثل Telnet وTFTP وما إلى ذلك وما إلى ذلك في Java الخالصة. من الواضح أنني لن أحضر أي شيء جديد - كل هذه المرافق تعمل بنجاح منذ أكثر من عام، لكنني أعتقد أن الجميع لا يعرفون ما يحدث تحت الغطاء.

هذا هو بالضبط ما سيتم مناقشته تحت الخفض.

في هذه المقالة، من أجل عدم سحبها، بالإضافة إلى المعلومات العامة، سأكتب فقط عن خادم Telnet، ولكن في الوقت الحالي هناك أيضا مواد حول المرافق الأخرى - ستكون في أجزاء أخرى من السلسلة.

بادئ ذي بدء، تحتاج إلى معرفة ما هو Telnet، وما هو مطلوب من أجله، وما يستخدم من أجله. لن أقتبس المصادر حرفيًا (إذا لزم الأمر، سأرفق رابطًا للمواد المتعلقة بالموضوع في نهاية المقالة)، سأقول فقط أن Telnet يوفر الوصول عن بعد إلى سطر أوامر الجهاز. على العموم، هذا هو المكان الذي تنتهي فيه وظيفته (لقد التزمت الصمت عمدًا بشأن الوصول إلى منفذ الخادم؛ المزيد عن ذلك لاحقًا). هذا يعني أنه لتنفيذه، نحتاج إلى قبول سطر على العميل، وتمريره إلى الخادم، ومحاولة تمريره إلى سطر الأوامر، وقراءة استجابة سطر الأوامر، إن وجدت، وتمريرها مرة أخرى إلى العميل وعرضها على الشاشة، أو في حالة وجود أخطاء، دع المستخدم يعرف أن هناك خطأ ما.

لتنفيذ ما سبق، وفقًا لذلك، نحتاج إلى فئتين عمل وبعض فئات الاختبار التي سنطلق منها الخادم والتي سيعمل العميل من خلالها.
وفقًا لذلك، يتضمن هيكل التطبيق حاليًا ما يلي:

  • عميل Telnet
  • TelnetClientTester
  • خادم التلنت
  • TelnetServerTester

دعنا نذهب من خلال كل واحد منهم:

عميل Telnet

كل ما يجب أن يكون هذا الفصل قادرًا على فعله هو إرسال الأوامر المستلمة وإظهار الاستجابات المستلمة. بالإضافة إلى ذلك، يجب أن تكون قادرًا على الاتصال بمنفذ عشوائي (كما هو مذكور أعلاه) لجهاز بعيد وقطع الاتصال به.

ولتحقيق ذلك، تم تنفيذ الوظائف التالية:

دالة تأخذ عنوان مأخذ التوصيل كوسيطة، وتفتح اتصالاً وتبدأ تدفقات الإدخال والإخراج (تم الإعلان عن متغيرات الدفق أعلاه، والمصادر الكاملة موجودة في نهاية المقالة).

 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());
        }
    }

التحميل الزائد على نفس الوظيفة، والاتصال بالمنفذ الافتراضي - بالنسبة لـ telnet، هذا هو 23


    public void run(String ip)
    {
        run(ip, 23);
    }

تقوم الوظيفة بقراءة الأحرف من لوحة المفاتيح وإرسالها إلى مقبس الإخراج - وهو أمر نموذجي، في وضع الخط، وليس وضع الأحرف:


    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());
        }
    }

تستقبل الوظيفة البيانات من المقبس وتعرضها على الشاشة


    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());
        }
    }

تعمل الوظيفة على إيقاف استقبال البيانات وإرسالها


    public void stop()
    {
        reader.stop();
        writer.stop();
    }
}

خادم التلنت

يجب أن تتمتع هذه الفئة بوظيفة تلقي أمر من مأخذ التوصيل، وإرساله للتنفيذ، وإرسال استجابة من الأمر مرة أخرى إلى المقبس. لا يقوم البرنامج بفحص بيانات الإدخال عمدًا، لأنه أولاً، حتى في "التلنت المعبأ" من الممكن تهيئة قرص الخادم، وثانيًا، تم حذف مسألة الأمان في هذه المقالة من حيث المبدأ، ولهذا السبب لا يوجد كلمة حول التشفير أو SSL.

هناك وظيفتان فقط (إحداهما مثقلة)، وبشكل عام، هذه ليست ممارسة جيدة جدًا، ولكن لأغراض هذه المهمة، بدا لي أنه من المناسب ترك كل شيء كما هو.

 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();

    }

يفتح البرنامج منفذ الخادم، ويقرأ البيانات منه حتى يصادف حرف نهاية الأمر، ويمرر الأمر إلى عملية جديدة، ويعيد توجيه الإخراج من العملية إلى المقبس. كل شيء بسيط مثل بندقية كلاشينكوف الهجومية.

وبناء على ذلك، هناك حمل زائد لهذه الوظيفة مع المنفذ الافتراضي:

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

حسنًا، وفقًا لذلك، فإن الوظيفة التي توقف الخادم تافهة أيضًا، فهي تقاطع الحلقة الأبدية، وتنتهك حالتها.

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

لن أعطي دروس اختبار هنا، فهي أدناه - كل ما يفعلونه هو التحقق من وظائف الطرق العامة. كل شيء على البوابة.

لتلخيص، في بضع أمسيات، يمكنك فهم مبادئ تشغيل الأدوات المساعدة لوحدة التحكم الرئيسية. الآن، عندما نقوم بالاتصال عبر الهاتف بجهاز كمبيوتر بعيد، فإننا نفهم ما يحدث - لقد اختفى السحر)

إذن الروابط:
جميع المصادر كانت، وستكون هنا
حول تيلنت
المزيد عن تلنت

المصدر: www.habr.com

إضافة تعليق