Windows հաճախորդ-սերվերի կոմունալ ծրագրերի ֆունկցիոնալությամբ գրելու ծրագրակազմ, մաս 02

Շարունակելով Windows-ի կոնսոլի կոմունալ ծառայությունների մաքսային իրականացումներին նվիրված շարունակվող հոդվածների շարքը, մենք չենք կարող չանդրադառնալ TFTP-ին (Trivial File Transfer Protocol)՝ պարզ ֆայլերի փոխանցման արձանագրությանը:

Ինչպես նախորդ անգամ, եկեք համառոտ անցնենք տեսությանը, տեսնենք այն կոդը, որն իրականացնում է պահանջվող ֆունկցիոնալությունը և վերլուծենք այն: Ավելի մանրամասն՝ կտրվածքի տակ

Ես չեմ պատճենի-տեղադրի հղումներ, որոնց հղումները ավանդաբար կարելի է գտնել հոդվածի վերջում, միայն կասեմ, որ իր հիմքում TFTP-ն FTP արձանագրության պարզեցված տարբերակն է, որում մուտքի վերահսկման կարգավորումն ունի. հեռացվել է, և իրականում այստեղ ոչինչ չկա, բացի ֆայլ ստանալու և փոխանցելու հրամաններից: Այնուամենայնիվ, մեր իրականացումը մի փոքր ավելի էլեգանտ և կոդ գրելու ներկայիս սկզբունքներին հարմարեցվելու համար, շարահյուսությունը մի փոքր փոխվել է. սա չի փոխում գործողության սկզբունքները, բայց ինտերֆեյսը, IMHO, դառնում է մի փոքր ավելի տրամաբանական և համատեղում է FTP-ի և TFTP-ի դրական կողմերը:

Մասնավորապես, երբ գործարկվում է, հաճախորդը պահանջում է սերվերի IP հասցեն և այն պորտը, որի վրա բաց է մաքսային TFTP (ստանդարտ արձանագրության հետ անհամատեղելիության պատճառով ես նպատակահարմար համարեցի օգտվողին թողնել նավահանգիստ ընտրելու հնարավորությունը), որից հետո տեղի է ունենում միացում, որի արդյունքում հաճախորդը կարող է ուղարկել հրամաններից մեկը՝ ստանալ կամ տեղադրել, ստանալ կամ ուղարկել ֆայլ սերվերին: Բոլոր ֆայլերը ուղարկվում են երկուական ռեժիմով՝ տրամաբանությունը պարզեցնելու համար:

Արձանագրությունն իրականացնելու համար ես ավանդաբար օգտագործում էի 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 բայթ, որոնք հետագայում վերածվում են մեկ թվի: Սա շատ Java մոտեցում չէ, այն բավականին նման է 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());
        }
    }

Ընտրիչը գործառույթ է, որը որոշում է ծրագրի գործողությունները՝ կախված մուտքագրված տողից: Այստեղ ամեն ինչ այնքան էլ գեղեցիկ չէ, և օգտագործված հնարքը լավագույնը չէ կոդի բլոկից դուրս հարկադիր ելքի դեպքում, բայց դրա հիմնական պատճառը Java-ում որոշ բաների բացակայությունն է, ինչպիսիք են պատվիրակները C#-ում, ֆունկցիայի ցուցիչները C++-ից կամ առնվազն սարսափելի և սարսափելի goto-ն, որը թույլ է տալիս դա գեղեցիկ կերպով իրականացնել: Եթե ​​գիտեք, թե ինչպես կարելի է ծածկագիրը մի փոքր ավելի էլեգանտ դարձնել, ես ողջունում եմ քննադատությունը մեկնաբանություններում: Ինձ թվում է՝ այստեղ լարային-պատվիրակական բառարան է պետք, բայց պատվիրակ չկա...

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

TFTPS սերվեր

Սերվերի ֆունկցիոնալությունը տարբերվում է հաճախորդի ֆունկցիոնալությունից, մեծ հաշվով, միայն նրանով, որ հրամանները նրան գալիս են ոչ թե ստեղնաշարից, այլ վարդակից: Մեթոդներից մի քանիսը հիմնականում նույնն են, ուստի ես դրանք չեմ մեջբերի, կանդրադառնամ միայն տարբերություններին։

Սկսելու համար օգտագործվում է գործարկման մեթոդը, որը որպես մուտք է ստանում պորտ և մշակում վարդակից մուտքային տվյալները հավերժական օղակով:

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

Ստանալ մեթոդը վերբերում է սերվերի ֆայլը: Ինչպես արդեն նշվեց ծրագրի հաճախորդի կողմից բաժնում, ֆայլը հաջողությամբ փոխանցելու համար անհրաժեշտ է իմանալ դրա չափը, որը պահվում է երկար ամբողջ թվով, այնպես որ ես այն բաժանեցի 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-ի մասին
Նույնը, բայց ռուսերեն

Source: www.habr.com

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