Rašymo programinė įranga su Windows kliento-serverio paslaugų funkcijomis, 02 dalis

Tęsdami nuolatinę straipsnių seriją, skirtą pasirinktiniam „Windows“ konsolės paslaugų diegimui, negalime nepaliesti TFTP (Trivial File Transfer Protocol) – paprasto failų perdavimo protokolo.

Kaip ir praeitą kartą, trumpai apžvelgsime teoriją, pažiūrėkime kodą, kuris įgyvendina panašų į reikalingą funkcionalumą, ir išanalizuosime. Daugiau detalių – po pjūviu

Nekopijuosiu-įklijuosiu nuorodos informacijos, kurios nuorodas tradiciškai galima rasti straipsnio pabaigoje, tiesiog pasakysiu, kad iš esmės TFTP yra supaprastintas FTP protokolo variantas, kuriame prieigos kontrolės nustatymas buvo pašalintas, ir iš tikrųjų čia nėra nieko, išskyrus komandas, skirtas gauti ir perkelti failą . Tačiau, kad mūsų įgyvendinimas būtų kiek elegantiškesnis ir pritaikytas prie dabartinių kodo rašymo principų, šiek tiek pakeista sintaksė – tai nekeičia veikimo principų, tačiau sąsaja, IMHO, tampa šiek tiek logiškesnė ir sujungia teigiamus FTP ir TFTP aspektus.

Visų pirma, paleidžiant, klientas prašo serverio IP adreso ir prievado, kuriame yra atidarytas tinkintas TFTP (dėl nesuderinamumo su standartiniu protokolu maniau, kad tikslinga palikti vartotojui galimybę pasirinkti prievadą), po kurio įvyksta ryšys, dėl kurio klientas gali siųsti vieną iš komandų - gauti arba įdėti, gauti arba siųsti failą į serverį. Visi failai siunčiami dvejetainiu režimu, kad būtų supaprastinta logika.

Protokolui įgyvendinti tradiciškai naudojau 4 klases:

  • TFTPClient
  • TFTPServeris
  • TFTPClientTester
  • TFTPServerTester

Dėl to, kad testavimo klasės egzistuoja tik pagrindinėms derinti, aš jų neanalizuosiu, bet kodas bus saugykloje, kurią rasite straipsnio pabaigoje. Dabar pažvelgsiu į pagrindines klases.

TFTPClient

Šios klasės užduotis yra prisijungti prie nuotolinio serverio naudojant jo ip ir prievado numerį, nuskaityti komandą iš įvesties srauto (šiuo atveju klaviatūros), ją išanalizuoti, perkelti į serverį ir, priklausomai nuo to, ar reikia siųsti arba gauti failą, jį perkelti arba gauti.

Kodas, skirtas paleisti klientą, norint prisijungti prie serverio ir laukti komandos iš įvesties srauto, atrodo taip. Kai kurie čia naudojami visuotiniai kintamieji yra aprašyti už straipsnio ribų, visame programos tekste. Dėl jų trivialumo jų necituoju, kad neapkrautų straipsnio.

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

Peržiūrėkime metodus, vadinamus šiame kodo bloke:

Čia failas siunčiamas - naudodami skaitytuvą pateikiame failo turinį kaip baitų masyvą, kurį po vieną įrašome į lizdą, po kurio uždarome ir vėl atidarome (ne pats akivaizdžiausias sprendimas, bet tai garantuoja išteklių atleidimą), po kurio parodome pranešimą apie sėkmingą perkėlimą.

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 kodo fragmentas apibūdina duomenų gavimą iš serverio. Viskas vėlgi nereikšminga, domina tik pirmasis kodo blokas. Norėdami tiksliai suprasti, kiek baitų reikia nuskaityti iš lizdo, turite žinoti, kiek sveria perkeltas failas. Failo dydis serveryje vaizduojamas kaip ilgas sveikasis skaičius, todėl čia priimami 4 baitai, kurie vėliau konvertuojami į vieną skaičių. Tai nėra labai Java metodas, jis yra gana panašus į SI, bet išsprendžia jo problemą.

Tada viskas yra nereikšminga – gauname žinomą skaičių baitų iš lizdo ir įrašome juos į failą, po kurio parodome sėkmės pranešimą.

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

Jei į kliento langą buvo įvesta kita komanda nei gauti arba įdėti, bus iškviesta funkcija showErrorMessage, nurodant, kad įvestis buvo neteisinga. Dėl trivialumo to necituosiu. Šiek tiek įdomesnė yra įvesties eilutės priėmimo ir padalijimo funkcija. Perduodame į jį skaitytuvą, iš kurio tikimės gauti eilutę, atskirtą dviem tarpais ir kurioje yra komanda, šaltinio adresas ir paskirties adresas.

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

Komandos siuntimas – iš skaitytuvo įvesta komanda perduodama į lizdą ir verčiama ją išsiųsti

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

Selektorius yra funkcija, kuri nustato programos veiksmus, priklausomai nuo įvestos eilutės. Viskas čia nėra labai gražu ir naudojamas triukas nėra pats geriausias su priverstiniu išėjimu už kodo bloko ribų, tačiau pagrindinė to priežastis yra kai kurių dalykų nebuvimas Java programoje, pvz., delegatų C#, funkcijų rodyklių iš C++ arba mažiausiai baisus ir baisus goto, kuris leidžia jums tai įgyvendinti gražiai. Jei žinote, kaip kodą padaryti šiek tiek elegantiškesnį, laukiu kritikos komentaruose. Man atrodo, kad čia reikia String-delelegate žodyno, bet nėra delegato...

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

TFTPServeris

Serverio funkcionalumas iš esmės skiriasi nuo kliento funkcionalumo tik tuo, kad komandos į jį ateina ne iš klaviatūros, o iš lizdo. Kai kurie metodai iš esmės yra vienodi, todėl jų necituosiu, paliesiu tik skirtumus.

Norėdami pradėti, naudojamas paleidimo metodas, kuris gauna prievadą kaip įvestį ir apdoroja įvesties duomenis iš lizdo amžinuoju ciklu.

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

Įdėjimo metodas, apvyniojantis writeToFileFromSocket metodą, kuris atidaro rašymo srautą į failą ir įrašo visus įvesties baitus iš lizdo, rodo pranešimą, nurodantį sėkmingą perkėlimą, kai įrašymas baigtas.

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

Gauti metodas nuskaito serverio failą. Kaip jau buvo minėta skyrelyje programos kliento pusėje, norint sėkmingai perkelti failą reikia žinoti jo dydį, saugomą ilgu sveikuoju skaičiumi, todėl aš jį suskaidau į 4 baitų masyvą, perkeliau baitas po baitų į lizdą, o tada, gavęs ir surinkęs juos kliente į skaičių atgal, perkeliu visus failą sudarančius baitus, nuskaitau iš įvesties srauto iš failo.


 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 metodas yra toks pat kaip kliento, vienintelis skirtumas yra tas, kad jis skaito duomenis iš lizdo, o ne iš klaviatūros. Kodas yra saugykloje, kaip ir parinkiklis.
Tokiu atveju inicijavimas dedamas į atskirą kodo bloką, nes Šiame įgyvendinime, užbaigus perkėlimą, ištekliai išleidžiami ir vėl užimti – vėlgi, siekiant apsaugoti nuo atminties nutekėjimo.

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

Apibendrinti:

Mes ką tik parašėme savo paprasto duomenų perdavimo protokolo variantą ir supratome, kaip jis turėtų veikti. Iš principo aš čia Amerikos neatradau ir daug naujų dalykų nerašiau, bet apie Habré panašių straipsnių nebuvo, o rašant straipsnių seriją apie cmd komunalines paslaugas buvo neįmanoma to nepaliesti.

Nuorodos:

Šaltinio kodo saugykla
Trumpai apie TFTP
Tas pats, tik rusiškai

Šaltinis: www.habr.com

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