نرم افزار رایتینگ با قابلیت های سرویس گیرنده-سرور ویندوز قسمت 02

در ادامه سری مقالات در حال انجام اختصاص داده شده به پیاده سازی سفارشی ابزارهای کنسول ویندوز، نمی توانیم از TFTP (پروتکل انتقال فایل بی اهمیت) - یک پروتکل ساده انتقال فایل، استفاده نکنیم.

مانند دفعه قبل، اجازه دهید به طور خلاصه به تئوری بپردازیم، کدی را ببینیم که عملکردی شبیه به مورد نیاز را پیاده سازی می کند و آن را تجزیه و تحلیل می کنیم. جزئیات بیشتر - زیر برش

من اطلاعات مرجع را کپی-پیست نمی کنم، پیوندهایی که به طور سنتی در انتهای مقاله یافت می شوند، فقط می گویم که در هسته خود، TFTP یک نوع ساده شده از پروتکل FTP است که در آن تنظیمات کنترل دسترسی وجود دارد. حذف شده است و در واقع چیزی جز دستورات دریافت و انتقال فایل در اینجا وجود ندارد. با این حال، برای اینکه پیاده سازی ما کمی ظریف تر و منطبق با اصول فعلی کد نوشتن باشد، نحو کمی تغییر کرده است - این اصول عملکرد را تغییر نمی دهد، اما رابط، IMHO، کمی منطقی تر می شود و ترکیبی از جنبه های مثبت FTP و TFTP.

به طور خاص، هنگام راه اندازی، مشتری آدرس IP سرور و پورتی را که TFTP سفارشی روی آن باز است را درخواست می کند (به دلیل ناسازگاری با پروتکل استاندارد، من مناسب دانستم که امکان انتخاب یک پورت را به کاربر واگذار کنم)، پس از آن یک اتصال رخ می دهد، در نتیجه مشتری می تواند یکی از دستورات را ارسال کند - get یا put، برای دریافت یا ارسال یک فایل به سرور. همه فایل ها در حالت باینری برای ساده سازی منطق ارسال می شوند.

برای پیاده سازی پروتکل، من به طور سنتی از 4 کلاس استفاده می کردم:

  • TFTPClient
  • سرور TFTPS
  • TFTPClientTester
  • TFTPServerTester

با توجه به اینکه کلاس‌های تست فقط برای اشکال‌زدایی اصلی‌ها وجود دارد، من آنها را تجزیه و تحلیل نمی‌کنم، اما کد در مخزن قرار می‌گیرد؛ پیوندی به آن در انتهای مقاله موجود است. اکنون به کلاس های اصلی نگاه خواهم کرد.

TFTPClient

وظیفه این کلاس اتصال به یک سرور راه دور توسط شماره ip و پورت آن، خواندن یک دستور از جریان ورودی (در این مورد، صفحه کلید)، تجزیه آن، انتقال آن به سرور و بسته به اینکه آیا شما نیاز به ارسال یا دریافت فایل، انتقال یا دریافت آن.

کد راه اندازی کلاینت برای اتصال به سرور و منتظر فرمان از جریان ورودی به این شکل است. تعدادی از متغیرهای سراسری که در اینجا استفاده می شوند در خارج از مقاله، در متن کامل برنامه توضیح داده شده اند. به دلیل پیش پا افتاده بودن آنها از آنها استناد نمی کنم تا مقاله را بیش از حد بار نکنم.

 public void run(String ip, int port)
    {
        this.ip = ip;
        this.port = port;
        try {
            inicialization();
            Scanner keyboard = new Scanner(System.in);
            while (isRunning) {
                getAndParseInput(keyboard);
                sendCommand();
                selector();
                }
            }
        catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }

بیایید روش های فراخوانی شده در این بلوک کد را بررسی کنیم:

در اینجا فایل ارسال می شود - با استفاده از یک اسکنر، محتویات فایل را به صورت آرایه ای از بایت ها ارائه می دهیم، که آنها را یکی یکی در سوکت می نویسیم، پس از آن آن را می بندیم و دوباره باز می کنیم (بدیهی ترین راه حل نیست، اما انتشار منابع را تضمین می کند)، پس از آن پیامی در مورد انتقال موفقیت آمیز نمایش می دهیم.

private  void put(String sourcePath, String destPath)
    {

        File src = new File(sourcePath);
        try {

            InputStream scanner = new FileInputStream(src);
            byte[] bytes = scanner.readAllBytes();
            for (byte b : bytes)
                sout.write(b);
            sout.close();
            inicialization();
            System.out.println("nDonen");
            }

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

این قطعه کد بازیابی داده ها از سرور را توصیف می کند. همه چیز دوباره بی اهمیت است، فقط اولین بلوک کد مورد توجه است. برای اینکه بفهمید دقیقا چند بایت باید از سوکت خوانده شود، باید وزن فایل منتقل شده را بدانید. اندازه فایل در سرور به عنوان یک عدد صحیح طولانی نشان داده می شود، بنابراین 4 بایت در اینجا پذیرفته می شود که متعاقباً به یک عدد تبدیل می شود. این یک رویکرد جاوا نیست، برای SI تقریباً مشابه است، اما مشکل آن را حل می کند.

سپس همه چیز بی اهمیت است - تعداد مشخصی از بایت ها را از سوکت دریافت می کنیم و آنها را در یک فایل می نویسیم و پس از آن یک پیام موفقیت آمیز نمایش می دهیم.

   private void get(String sourcePath, String destPath){
        long sizeOfFile = 0;
        try {


            byte[] sizeBytes = new byte[Long.SIZE];
           for (int i =0; i< Long.SIZE/Byte.SIZE; i++)
           {
               sizeBytes[i] = (byte)sin.read();
               sizeOfFile*=256;
               sizeOfFile+=sizeBytes[i];
           }

           FileOutputStream writer = new FileOutputStream(new File(destPath));
           for (int i =0; i < sizeOfFile; i++)
           {
               writer.write(sin.read());
           }
           writer.close();
           System.out.println("nDONEn");
       }
       catch (Exception e){
            System.out.println(e.getMessage());
       }
    }

اگر دستوری غیر از get یا put در پنجره مشتری وارد شده باشد، تابع showErrorMessage فراخوانی می شود که نشان می دهد ورودی نادرست بوده است. به دلیل پیش پا افتاده بودن، به آن استناد نمی کنم. تا حدودی جالب تر، عملکرد دریافت و تقسیم رشته ورودی است. ما اسکنر را به داخل آن می‌دهیم، که انتظار داریم از آن خطی دریافت کنیم که با دو فاصله از هم جدا شده و حاوی دستور، آدرس مبدا و آدرس مقصد است.

    private void getAndParseInput(Scanner scanner)
    {
        try {

            input = scanner.nextLine().split(" ");
            typeOfCommand = input[0];
            sourcePath = input[1];
            destPath = input[2];
        }
        catch (Exception e) {
            System.out.println("Bad input");
        }
    }

ارسال فرمان - فرمان وارد شده از اسکنر را به سوکت منتقل می کند و آن را مجبور به ارسال می کند.

    private void sendCommand()
    {
        try {

            for (String str : input) {
                for (char ch : str.toCharArray()) {
                    sout.write(ch);
                }
                sout.write(' ');
            }
            sout.write('n');
        }
        catch (Exception e) {
            System.out.print(e.getMessage());
        }
    }

انتخابگر تابعی است که اعمال برنامه را بسته به رشته وارد شده تعیین می کند. همه چیز در اینجا خیلی زیبا نیست و ترفند استفاده شده بهترین ترفند با خروج اجباری خارج از بلوک کد نیست، اما دلیل اصلی آن عدم وجود برخی چیزها در جاوا است، مانند delegates در C#، نشانگرهای تابع از C++ یا در حداقل گوتوی وحشتناک و وحشتناک، که به شما امکان می دهد این را به زیبایی اجرا کنید. اگر می دانید چگونه کد را کمی ظریف تر کنید، از انتقاد در نظرات استقبال می کنم. به نظر من اینجا به دیکشنری String-Delegate نیاز است، اما نماینده ای وجود ندارد...

    private void selector()
    {
        do{
            if (typeOfCommand.equals("get")){
                get(sourcePath, destPath);
                break;
            }
            if (typeOfCommand.equals("put")){
                put(sourcePath, destPath);
                break;
            }
            showErrorMessage();
        }
        while (false);
    }
}

سرور TFTPS

عملکرد سرور با عملکرد مشتری، به طور کلی، متفاوت است، تنها در این که دستورات نه از صفحه کلید، بلکه از سوکت به آن می آیند. برخی از روش ها به طور کلی یکسان هستند، بنابراین من به آنها اشاره نمی کنم، فقط به تفاوت ها می پردازم.

برای شروع، از روش run استفاده می شود که یک پورت را به عنوان ورودی دریافت می کند و داده های ورودی را از سوکت در یک حلقه ابدی پردازش می کند.

    public void run(int port) {
            this.port = port;
            incialization();
            while (true) {
                getAndParseInput();
                selector();
            }
    }

متد put که متد writeToFileFromSocket را می‌پیچد که یک جریان نوشتن را به یک فایل باز می‌کند و تمام بایت‌های ورودی را از سوکت می‌نویسد، پیامی را نشان می‌دهد که تکمیل موفقیت‌آمیز انتقال را پس از تکمیل نوشتن نشان می‌دهد.

    private  void put(String source, String dest){
            writeToFileFromSocket();
            System.out.print("nDonen");
    };
    private void writeToFileFromSocket()
    {
        try {
            FileOutputStream writer = new FileOutputStream(new File(destPath));
            byte[] bytes = sin.readAllBytes();
            for (byte b : bytes) {
                writer.write(b);
            }
            writer.close();
        }
        catch (Exception e){
            System.out.println(e.getMessage());
        }
    }

متد get فایل سرور را بازیابی می کند. همانطور که قبلاً در بخش سمت مشتری برنامه ذکر شد، برای انتقال موفقیت آمیز یک فایل باید اندازه آن را بدانید که در یک عدد صحیح طولانی ذخیره شده است، بنابراین من آن را به یک آرایه 4 بایتی تقسیم کردم و آنها را بایت به بایت منتقل کردم. به سوکت، و سپس، پس از دریافت و مونتاژ آنها بر روی کلاینت به یک عدد، تمام بایت هایی که فایل را تشکیل می دهند، از جریان ورودی از فایل می خوانم.


 private  void get(String source, String dest){
        File sending = new File(source);
        try {
            FileInputStream readFromFile = new FileInputStream(sending);
            byte[] arr = readFromFile.readAllBytes();
            byte[] bytes = ByteBuffer.allocate(Long.SIZE / Byte.SIZE).putLong(sending.length()).array();
            for (int i = 0; i<Long.SIZE / Byte.SIZE; i++)
                sout.write(bytes[i]);
            sout.flush();
            for (byte b : arr)
                sout.write(b);
        }
        catch (Exception e){
            System.out.println(e.getMessage());
        }
    };

روش getAndParseInput مانند کلاینت است، تنها تفاوت آن این است که داده ها را از سوکت می خواند نه از صفحه کلید. کد مانند انتخابگر در مخزن است.
در این مورد، مقداردهی اولیه در یک بلوک جداگانه از کد قرار می گیرد، زیرا در این پیاده سازی، پس از تکمیل انتقال، منابع آزاد می شوند و دوباره اشغال می شوند - دوباره برای محافظت در برابر نشت حافظه.

    private void  incialization()
    {
        try {
            serverSocket = new ServerSocket(port);
            socket = serverSocket.accept();
            sin = socket.getInputStream();
            sout = socket.getOutputStream();
        }
        catch (Exception e) {
            System.out.print(e.getMessage());
        }
    }

به طور خلاصه:

ما به تازگی تغییرات خود را بر روی یک پروتکل ساده انتقال داده نوشته ایم و متوجه شده ایم که چگونه باید کار کند. در اصل، من آمریکا را در اینجا کشف نکردم و چیزهای جدیدی ننوشتم، اما هیچ مقاله مشابهی در Habré وجود نداشت، و به عنوان بخشی از نوشتن یک سری مقالات در مورد ابزارهای cmd، غیرممکن بود که به آن دست نزنیم.

لینک ها:

مخزن کد منبع
مختصری در مورد TFTP
همان چیزی است، اما به زبان روسی

منبع: www.habr.com

اضافه کردن نظر