Rakstīšanas programmatūra ar Windows klienta-servera utilītu funkcionalitāti, 02. daļa

Turpinot iesākto rakstu sēriju, kas veltīta Windows konsoles utilītu pielāgotai ieviešanai, mēs nevaram nepieskarties TFTP (Trivial File Transfer Protocol) - vienkāršam failu pārsūtīšanas protokolam.

Tāpat kā pagājušajā reizē, īsi apskatīsim teoriju, apskatīsim kodu, kas ievieš nepieciešamajai līdzīgai funkcionalitātei, un analizēsim to. Sīkāka informācija - zem griezuma

Nekopēšu-ielīmēšu atsauces informāciju, uz kuru saites tradicionāli var atrast raksta beigās, teikšu tikai to, ka savā būtībā TFTP ir FTP protokola vienkāršota variācija, kurā piekļuves kontroles iestatījums ir ir noņemts, un patiesībā šeit nav nekā, izņemot komandas faila saņemšanai un pārsūtīšanai. Tomēr, lai padarītu mūsu ieviešanu nedaudz elegantāku un pielāgotu pašreizējiem koda rakstīšanas principiem, ir nedaudz mainīta sintakse - tas nemaina darbības principus, bet saskarne, IMHO, kļūst nedaudz loģiskāka un apvieno FTP un TFTP pozitīvos aspektus.

Konkrēti, palaižot, klients pieprasa servera IP adresi un portu, kurā ir atvērts pielāgotais TFTP (nesaderības ar standarta protokolu dēļ uzskatīju par lietderīgu atstāt lietotājam iespēju izvēlēties portu), pēc kura rodas savienojums, kā rezultātā klients var nosūtīt vienu no komandām - get vai put, saņemt vai nosūtīt failu uz serveri. Visi faili tiek nosūtīti binārajā režīmā, lai vienkāršotu loģiku.

Lai ieviestu protokolu, es tradicionāli izmantoju 4 klases:

  • TFTPClient
  • TFTPServeris
  • TFTPClientTester
  • TFTPServerTester

Sakarā ar to, ka testēšanas klases pastāv tikai galveno atkļūdošanai, es tās neanalizēšu, bet kods būs repozitorijā, un to var atrast raksta beigās. Tagad es apskatīšu galvenās klases.

TFTPClient

Šīs klases uzdevums ir izveidot savienojumu ar attālo serveri, izmantojot tā IP un porta numuru, nolasīt komandu no ievades straumes (šajā gadījumā tastatūras), parsēt to, pārsūtīt uz serveri un atkarībā no tā, vai nepieciešams nosūtīt vai saņemt failu, pārsūtīt to vai saņemt.

Kods klienta palaišanai, lai izveidotu savienojumu ar serveri un gaidītu komandu no ievades straumes, izskatās šādi. Vairāki šeit izmantotie globālie mainīgie ir aprakstīti ārpus raksta, pilnā programmas tekstā. To trivialitātes dēļ es tos necitēju, lai nepārslogotu rakstu.

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

Apskatīsim šajā koda blokā izsauktās metodes:

Šeit fails tiek nosūtīts - izmantojot skeneri, faila saturu uzrāda kā baitu masīvu, ko pa vienam ierakstām ligzdā, pēc tam to aizveram un atveram vēlreiz (nav acīmredzamākais risinājums, bet tas garantē resursu atbrīvošanu), pēc kura tiek parādīts ziņojums par veiksmīgu pārsūtīšanu.

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

Šis koda fragments apraksta datu izgūšanu no servera. Viss atkal ir triviāls, interesē tikai pirmais koda bloks. Lai precīzi saprastu, cik baitu ir jānolasa no ligzdas, jums jāzina, cik daudz sver pārsūtītais fails. Faila lielums serverī tiek attēlots kā garš vesels skaitlis, tāpēc šeit tiek pieņemti 4 baiti, kas pēc tam tiek pārvērsti vienā ciparā. Šī nav ļoti Java pieeja, tā ir diezgan līdzīga SI, taču tā atrisina tās problēmu.

Tad viss ir triviāls – no ligzdas saņemam zināmu baitu skaitu un ierakstām tos failā, pēc kura parādām veiksmes ziņojumu.

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

Ja klienta logā tika ievadīta cita komanda, nevis get vai put, tiks izsaukta funkcija showErrorMessage, norādot, ka ievade bija nepareiza. Trivialitātes dēļ es to necitēšu. Nedaudz interesantāka ir ievades virknes saņemšanas un sadalīšanas funkcija. Mēs tajā ievietojam skeneri, no kura mēs sagaidām rindiņu, kas atdalīta ar divām atstarpēm un satur komandu, avota adresi un galamērķa adresi.

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

Komandas nosūtīšana — no skenera ievadīto komandu pārsūta uz ligzdu un piespiež to nosūtīt

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

Atlasītājs ir funkcija, kas nosaka programmas darbības atkarībā no ievadītās virknes. Šeit viss nav īpaši skaisti, un izmantotais triks nav labākais ar piespiedu izeju ārpus koda bloka, taču galvenais iemesls tam ir dažu lietu trūkums Java, piemēram, delegāti C#, funkciju norādes no C++ vai vismaz briesmīgais un briesmīgais goto, kas ļauj jums to skaisti īstenot. Ja jūs zināt, kā padarīt kodu nedaudz elegantāku, es atzinīgi vērtēju kritiku komentāros. Man šķiet, ka šeit ir vajadzīga String-delegate vārdnīca, bet nav delegāta...

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

TFTPServeris

Servera funkcionalitāte kopumā atšķiras no klienta funkcionalitātes tikai ar to, ka komandas tam nāk nevis no tastatūras, bet gan no ligzdas. Dažas metodes kopumā ir vienādas, tāpēc es tās nesniegšu, tikai pieskaršos atšķirībām.

Lai sāktu, tiek izmantota palaišanas metode, kas saņem portu kā ievadi un apstrādā ieejas datus no ligzdas mūžīgā cilpā.

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

Put metode, kas aptin writeToFileFromSocket metodi, kas atver rakstīšanas straumi failā un ieraksta visus ievades baitus no ligzdas, parāda ziņojumu, kas norāda uz veiksmīgu pārsūtīšanas pabeigšanu, kad rakstīšana ir pabeigta.

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

Iegūšanas metode izgūst servera failu. Kā jau minēts sadaļā programmas klienta pusē, lai veiksmīgi pārsūtītu failu, ir jāzina tā lielums, kas saglabāts garā veselā skaitlī, tāpēc es to sadalu 4 baitu masīvā, pārsūtot tos pa baitam uz ligzdu, un pēc tam, saņemot un samontējot tos uz klienta atpakaļ ciparā, es pārsūtu visus baitus, kas veido failu, nolasu no ievades straumes no faila.


 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 metode ir tāda pati kā klientam, vienīgā atšķirība ir tā, ka tā nolasa datus no ligzdas, nevis no tastatūras. Kods atrodas repozitorijā, tāpat kā atlasītājs.
Šajā gadījumā inicializācija tiek ievietota atsevišķā koda blokā, jo šīs realizācijas ietvaros pēc pārsūtīšanas pabeigšanas resursi tiek atbrīvoti un atkārtoti aizņemti – atkal, lai nodrošinātu aizsardzību pret atmiņas noplūdēm.

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

Apkopot:

Mēs tikko esam uzrakstījuši savu variantu vienkāršam datu pārsūtīšanas protokolam un izdomājām, kā tam vajadzētu darboties. Principā es šeit neatklāju Ameriku un nerakstīju daudz jaunu lietu, bet par Habré līdzīgu rakstu nebija, un, rakstot rakstu sēriju par cmd utilītprogrammām, nebija iespējams tai nepieskarties.

Saites:

Avota koda repozitorijs
Īsi par TFTP
Tas pats, bet krievu valodā

Avots: www.habr.com

Pievieno komentāru