Kirjutamistarkvara Windowsi klient-serveri utiliitide funktsionaalsusega, osa 02

Jätkates käimasolevat artiklite seeriat, mis on pühendatud Windowsi konsooli utiliitide kohandatud rakendustele, ei saa me jätta puudutamata TFTP-d (Trivial File Transfer Protocol) - lihtsat failiedastusprotokolli.

Nagu eelmiselgi korral, käime põgusalt üle teooriast, vaatame nõutavale sarnast funktsionaalsust realiseerivat koodi ja analüüsime seda. Täpsemalt - lõike all

Ma ei kopeeri-kleebi viiteteavet, mille lingid leiab tavapäraselt artikli lõpust, vaid ütlen vaid, et oma olemuselt on TFTP FTP-protokolli lihtsustatud variatsioon, milles juurdepääsu kontrolli seadistus on eemaldatud ja tegelikult pole siin midagi peale failide vastuvõtmise ja edastamise käskude . Kuid selleks, et muuta meie teostus veidi elegantsemaks ja praeguste koodi kirjutamise põhimõtetega kohandatuks, on süntaksit veidi muudetud - see ei muuda tööpõhimõtteid, kuid liides, IMHO, muutub veidi loogilisemaks ja ühendab FTP ja TFTP positiivsed küljed.

Eelkõige küsib klient käivitamisel serveri IP-aadressi ja porti, millel kohandatud TFTP on avatud (standardprotokolliga mitteühilduvuse tõttu pidasin sobivaks jätta kasutajale pordi valimise võimalus), misjärel tekib ühendus, mille tulemusena saab klient saata ühe käsu - saada või panna, faili vastuvõtmiseks või serverisse saatmiseks. Loogika lihtsustamiseks saadetakse kõik failid binaarrežiimis.

Protokolli rakendamiseks kasutasin traditsiooniliselt 4 klassi:

  • TFTPClient
  • TFTPServer
  • TFTPClientTester
  • TFTPServerTester

Kuna testimisklassid on olemas ainult peamiste silumiseks, ei analüüsi ma neid, vaid kood on hoidlas, lingi sellele leiate artikli lõpust. Nüüd vaatan põhiklasse.

TFTPClient

Selle klassi ülesanne on luua ühendus kaugserveriga selle IP ja pordi numbri järgi, lugeda sisendvoost (antud juhul klaviatuurilt) käsk, sõeluda see, edastada see serverisse ja olenevalt sellest, kas peate faili saatma või vastu võtma, selle üle kandma või hankima.

Kood kliendi käivitamiseks serveriga ühenduse loomiseks ja sisendvoost käsu ootamiseks näeb välja selline. Mitmeid siin kasutatavaid globaalseid muutujaid kirjeldatakse väljaspool artiklit, programmi täistekstis. Nende triviaalsuse tõttu ma neid ei tsiteeri, et artiklit mitte üle koormata.

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

Vaatame selles koodiplokis nimetatud meetodeid:

Siin saadetakse fail - skanneri abil esitame faili sisu baitide massiivina, mille kirjutame ükshaaval pesasse, misjärel sulgeme selle ja avame uuesti (mitte kõige ilmsem lahendus, kuid see garanteerib ressursside vabastamise), mille järel kuvame sõnumi eduka ülekande kohta.

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

See koodifragment kirjeldab andmete hankimist serverist. Kõik on jällegi triviaalne, huvi pakub vaid esimene koodiplokk. Selleks, et täpselt aru saada, mitu baiti tuleb pesast lugeda, peate teadma, kui palju edastatud fail kaalub. Faili suurus serveris on kujutatud pika täisarvuna, seega aktsepteeritakse siin 4 baiti, mis seejärel teisendatakse üheks numbriks. See ei ole väga Java-lähenemine, see on SI jaoks pigem sarnane, kuid lahendab selle probleemi.

Siis on kõik triviaalne - saame pesast teadaoleva arvu baite ja kirjutame need faili, mille järel kuvame eduteate.

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

Kui kliendiaknasse sisestati muu käsk peale get või put, kutsutakse välja funktsioon showErrorMessage, mis näitab, et sisend oli vale. Triviaalsuse tõttu ma seda ei maini. Mõnevõrra huvitavam on sisendstringi vastuvõtmise ja tükeldamise funktsioon. Anname sellesse skanneri, kust ootame kahe tühikuga eraldatud rea, mis sisaldab käsku, lähteaadressi ja sihtkoha aadressi.

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

Käsu saatmine – edastab skannerist sisestatud käsu pesasse ja sunnib selle saatma

    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 on funktsioon, mis määrab programmi toimingud sõltuvalt sisestatud stringist. Siin pole kõik kuigi ilus ja kasutatav nipp pole just parim, kui sunnitud väljumine toimub väljaspool koodiplokki, kuid selle peamiseks põhjuseks on mõne asja puudumine Javas, nagu delegaadid C#-s, funktsiooniosutajad C++-st või vähemalt kohutav ja kohutav goto, mis võimaldavad teil seda kaunilt rakendada. Kui teate, kuidas koodi veidi elegantsemaks muuta, siis ootan kommentaarides kriitikat. Mulle tundub, et siin on vaja String-delegati sõnastikku, aga delegaati pole...

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

TFTPServer

Serveri funktsionaalsus erineb üldiselt kliendi funktsionaalsusest ainult selle poolest, et käsud tulevad talle mitte klaviatuurilt, vaid pesast. Mõned meetodid on üldiselt samad, nii et ma neid ei tsiteerima, puudutan ainult erinevusi.

Alustuseks kasutatakse käitamismeetodit, mis võtab sisendiks vastu pordi ja töötleb igaveses tsüklis pesast saadud sisendandmeid.

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

Pane meetod, mis mähib writeToFileFromSocket meetodi, mis avab faili kirjutamisvoo ja kirjutab pesast kõik sisendbaidid, kuvab kirjutamise lõppedes teate, mis näitab edastuse edukat lõpetamist.

    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-meetod hangib serveri faili. Nagu programmi kliendipoolses osas juba mainitud, peate faili edukaks ülekandmiseks teadma selle suurust, mis on salvestatud pika täisarvuna, nii et jagasin selle 4-baidiseks massiiviks, edastasin need bait-baidi haaval. pesasse ning seejärel, olles need kliendil vastu võtnud ja numbriks kokku pannud, edastan kõik failist koosnevad baidid, mis loetakse failist sisendvoost.


 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 meetod on sama, mis kliendil, ainsaks erinevuseks on see, et see loeb andmeid pigem pesast kui klaviatuurilt. Kood on hoidlas, nagu valijagi.
Sel juhul asetatakse lähtestamine eraldi koodiplokki, kuna selle teostuse raames vabastatakse pärast ülekande lõpetamist ressursid ja taaskasutatakse uuesti – taaskord kaitseks mälulekke eest.

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

Kokku võtma:

Kirjutasime just oma variandi lihtsale andmeedastusprotokollile ja mõtlesime välja, kuidas see peaks töötama. Põhimõtteliselt ei avastanud ma siin Ameerikat ega kirjutanud palju uusi asju, kuid Habré kohta polnud sarnaseid artikleid ja cmd utiliitide artiklite seeria kirjutamise raames oli võimatu seda mitte puudutada.

Lingid:

Lähtekoodi hoidla
Lühidalt TFTP-st
Sama asi, aga vene keeles

Allikas: www.habr.com

Lisa kommentaar