Windows müştəri-server utilitlərinin funksionallığı ilə proqram təminatının yazılması, hissə 02

Windows konsol kommunallarının fərdi tətbiqlərinə həsr olunmuş davam edən məqalələr seriyasını davam etdirərək, biz sadə fayl ötürmə protokolu olan TFTP (Trivial File Transfer Protocol)-a toxunmağa kömək edə bilmərik.

Keçən dəfə olduğu kimi, qısaca nəzəriyyəyə keçək, tələb olunana bənzər funksionallığı həyata keçirən koda baxaq və onu təhlil edək. Daha ətraflı - kəsik altında

Ənənəvi olaraq məqalənin sonunda bağlantılar tapıla bilən istinad məlumatlarını kopyalayıb yapışdırmayacağam, yalnız deyəcəyəm ki, TFTP FTP protokolunun sadələşdirilmiş variantıdır, burada girişə nəzarət parametrləri var. silindi və əslində burada faylın qəbulu və ötürülməsi üçün əmrlərdən başqa heç nə yoxdur. Bununla belə, tətbiqimizi bir az daha zərif və mövcud kod yazmaq prinsiplərinə uyğunlaşdırmaq üçün sintaksis bir qədər dəyişdirildi - bu, iş prinsiplərini dəyişmir, lakin interfeys, IMHO, bir az daha məntiqli olur və FTP və TFTP-nin müsbət aspektlərini özündə birləşdirir.

Xüsusilə, işə salındıqda, müştəri serverin IP ünvanını və xüsusi TFTP-nin açıq olduğu portu tələb edir (standart protokolla uyğunsuzluğa görə istifadəçiyə port seçmək imkanı buraxmağı məqsədəuyğun hesab etdim), bundan sonra a əlaqə baş verir, bunun nəticəsində müştəri əmrlərdən birini göndərə bilər - almaq və ya qoymaq, faylı qəbul etmək və ya serverə göndərmək. Məntiqi sadələşdirmək üçün bütün fayllar ikili rejimdə göndərilir.

Protokolu həyata keçirmək üçün ənənəvi olaraq 4 sinifdən istifadə etdim:

  • TFTPClient
  • TFTPSserver
  • TFTPClientTester
  • TFTPS Server Tester

Sınaq dərsləri yalnız əsasları aradan qaldırmaq üçün mövcud olduğuna görə onları təhlil etməyəcəyəm, lakin kod depoda olacaq, ona keçid məqalənin sonunda tapıla bilər. İndi əsas dərslərə baxacağam.

TFTPClient

Bu sinfin vəzifəsi ip və port nömrəsi ilə uzaq serverə qoşulmaq, giriş axınından əmr oxumaq (bu halda klaviatura), onu təhlil etmək, serverə köçürmək və sizdən asılı olaraq faylı göndərmək və ya almaq, köçürmək və ya almaq lazımdır.

Serverə qoşulmaq və giriş axınından əmr gözləmək üçün müştərinin işə salınması kodu belə görünür. Burada istifadə olunan bir sıra qlobal dəyişənlər məqalədən kənarda, proqramın tam mətnində təsvir edilmişdir. Xırdalıqlarına görə məqaləni çox yükləməmək üçün onlara istinad etmirəm.

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

Bu kod blokunda çağırılan metodlara nəzər salaq:

Burada fayl göndərilir - bir skanerdən istifadə edərək, faylın məzmununu bir-bir rozetkaya yazdığımız bayt massivi kimi təqdim edirik, bundan sonra onu bağlayırıq və yenidən açırıq (ən aydın həll deyil, lakin resursların buraxılmasına zəmanət verir), bundan sonra uğurlu köçürmə haqqında mesaj göstəririk.

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

Bu kod parçası serverdən məlumatların alınmasını təsvir edir. Hər şey yenə mənasızdır, yalnız ilk kod bloku maraq doğurur. Soketdən tam olaraq neçə bayt oxumaq lazım olduğunu başa düşmək üçün ötürülən faylın nə qədər ağırlığını bilməlisiniz. Serverdəki fayl ölçüsü uzun bir tam ədəd kimi təmsil olunur, buna görə də burada 4 bayt qəbul edilir, sonradan bir ədədə çevrilir. Bu çox Java yanaşması deyil, SI üçün olduqca oxşardır, lakin problemini həll edir.

Sonra hər şey mənasızdır - biz rozetkadan məlum sayda bayt alırıq və onları bir fayla yazırıq, bundan sonra müvəffəqiyyət mesajı göstəririk.

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

Əgər müştəri pəncərəsinə get və ya put əmrindən başqa bir əmr daxil edilibsə, daxiletmənin səhv olduğunu göstərən showErrorMessage funksiyası çağırılacaq. Əhəmiyyətsiz olduğuna görə onu qeyd etməyəcəyəm. Bir az daha maraqlısı giriş sətirinin qəbulu və bölünməsi funksiyasıdır. Skaneri ona keçirik, oradan iki boşluqla ayrılmış və əmr, mənbə ünvanı və təyinat ünvanını ehtiva edən sətir alacağımızı gözləyirik.

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

Komandanın göndərilməsi — skanerdən daxil edilmiş əmri rozetkaya ötürür və onu göndərilməyə məcbur edir

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

Selektor daxil edilmiş sətirdən asılı olaraq proqramın hərəkətlərini təyin edən funksiyadır. Burada hər şey çox gözəl deyil və istifadə edilən hiylə kod blokundan kənarda məcburi çıxış üçün ən yaxşısı deyil, lakin bunun əsas səbəbi Java-da bəzi şeylərin olmamasıdır, məsələn C#-da nümayəndələr, C++-dan funksiya göstəriciləri və ya ən azı bunu gözəl şəkildə həyata keçirməyə imkan verən dəhşətli və dəhşətli goto. Kodu bir az daha zərif hala gətirməyi bilirsinizsə, şərhlərdə tənqidi alqışlayıram. Mənə elə gəlir ki, burada String-delegate lüğəti lazımdır, amma nümayəndə yoxdur...

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

TFTPSserver

Serverin funksionallığı müştərinin funksionallığından böyük ölçüdə fərqlənir, ona görə ki, ona əmrlər klaviaturadan deyil, rozetkadan gəlir. Metodların bəziləri ümumiyyətlə eynidir, ona görə də onları qeyd etməyəcəyəm, yalnız fərqlərə toxunacağam.

Başlamaq üçün giriş kimi bir port qəbul edən və soketdən daxil olan məlumatları əbədi döngədə emal edən run metodundan istifadə olunur.

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

Fayla yazma axını açan və bütün giriş baytlarını rozetkadan yazan writeToFileFromSocket metodunu əhatə edən put metodu, yazma tamamlandıqda köçürmənin uğurla tamamlandığını göstərən bir mesaj göstərir.

    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 metodu server faylını alır. Proqramın müştəri tərəfindəki bölmədə artıq qeyd edildiyi kimi, bir faylı uğurla ötürmək üçün onun ölçüsünü bilməlisiniz, uzun bir tam ədəddə saxlanılır, ona görə də onu 4 baytlıq bir sıraya bölürəm, onları bayt-bayt köçürürəm. rozetkaya, sonra onları qəbul edib müştəriyə yığdıqdan sonra faylı təşkil edən bütün baytları fayldan giriş axınından oxuyuram.


 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 metodu müştəridəki kimidir, yeganə fərq odur ki, o, məlumatları klaviaturadan deyil, rozetkadan oxuyur. Kod selektor kimi depodadır.
Bu halda inisializasiya ayrıca kod blokunda yerləşdirilir, çünki bu həyata keçirmə çərçivəsində, köçürmə tamamlandıqdan sonra ehtiyatlar buraxılır və yenidən yaddaş sızmalarına qarşı qorunma təmin etmək üçün yenidən məşğul olur.

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

Xülasə etmək üçün:

Biz sadəcə olaraq sadə məlumat ötürmə protokolu üzərində öz variasiyamızı yazdıq və onun necə işləməli olduğunu anladıq. Prinsipcə, mən burada Amerikanı kəşf etmədim və çox da yeni şeylər yazmadım, lakin Habré-də oxşar məqalələr yox idi və cmd utilitləri haqqında bir sıra məqalələr yazarkən ona toxunmamaq mümkün deyildi.

Referanslar:

Mənbə kodu deposu
TFTP haqqında qısaca
Eyni şey, amma rus dilində

Mənbə: www.habr.com

Добавить комментарий