Shkrimi i softuerit me funksionalitetin e mjeteve klient-server Windows, pjesa 02

Duke vazhduar ciklin e artikujve tĂ« dedikuar pĂ«r implementimet e personalizuara tĂ« mjeteve tĂ« komandave tĂ« Windows, nuk mund tĂ« mos pĂ«rmend TFTP (Protokolli i Transferimit tĂ« SkedarĂ«ve tĂ« ThjeshtĂ«) — njĂ« protokoll i thjeshtĂ« pĂ«r transferimin e skedarĂ«ve.

Ashtu si herĂ«n e kaluar, do tĂ« kalojmĂ« shkurtimisht pĂ«r teorinĂ«, do tĂ« shohim kodin qĂ« realizon funksionalitetin e ngjashĂ«m me atĂ« qĂ« kĂ«rkohet dhe do ta analizojmĂ«. MĂ« shumĂ« — poshtĂ«.

Nuk do tĂ« kopjoj informacionin ndihmĂ«s, tĂ« cilin zakonisht mund ta gjeni nĂ« fund tĂ« artikullit, do tĂ« them vetĂ«m se nĂ« thelb TFTP Ă«shtĂ« njĂ« variacion i thjeshtuar i protokollit FTP, ku Ă«shtĂ« hequr konfigurimi i kontrollit tĂ« aksesit, dhe nĂ« thelb kĂ«tu nuk ka gjĂ« tjetĂ«r pĂ«rveç komandave pĂ«r marrjen dhe dĂ«rgimin e skedarĂ«ve. MegjithatĂ«, pĂ«r tĂ« bĂ«rĂ« implementimin tonĂ« pak mĂ« tĂ« stilizuar dhe tĂ« adaptohet me parimet e tanishme tĂ« kodimit, sintaksa Ă«shtĂ« ndryshuar pak — parimet e funksionimit nuk ndryshojnĂ«, por ndĂ«rfaqja, IMHO, bĂ«het pak mĂ« logjike dhe kombinon aspektet pozitive tĂ« FTP dhe TFTP.

NĂ« veçanti, kur klienti niset, ai kĂ«rkon adresĂ«n ip tĂ« serverit dhe portin, nĂ« tĂ« cilin Ă«shtĂ« hapur TFTP e personalizuar (pĂ«r shkak tĂ« papajtueshmĂ«risĂ« me protokollin standard, mendoj se Ă«shtĂ« e nevojshme tĂ« lĂ«shohet mundĂ«sia pĂ«rzgjedhjeje tĂ« portit nga ana e pĂ«rdoruesit), pas sĂ« cilĂ«s ndodh lidhja, nĂ« rezultat tĂ« sĂ« cilĂ«s klienti mund tĂ« dĂ«rgojĂ« njĂ« nga komandat — get ose put, pĂ«r tĂ« marrĂ« ose dĂ«rguar njĂ« skedar nĂ« server. TĂ« gjitha skedarĂ«t dĂ«rgohen nĂ« modalitetin binar — me qĂ«llim tĂ« thjeshtimit tĂ« logjikĂ«s.

Për implementimin e protokollit, kam përdorur tradicionalisht 4 klasa:

  • TFTPClient
  • TFTPServer
  • TFTPClientTester
  • TFTPServerTester

Për shkak se klasat testuese ekzistojnë vetëm për debugimin e atyre kryesore, nuk do të merrem me to, por kodi do të gjendet në repositor, lidhja e të cilit mund të konsultoheni në fund të artikullit. Tani do të analizoj klasat kryesore.

TFTPClient

Detyra e kĂ«saj klase Ă«shtĂ« tĂ« lidhet me serverin e largĂ«t pĂ«rmes ip-sĂ« dhe numrit tĂ« portit tĂ« tij, tĂ« lexojĂ« komandĂ«n nga rrjedha hyrĂ«se (nĂ« kĂ«tĂ« rast — tastiera), ta analizojĂ« atĂ«, t'ia dĂ«rgojĂ« serverit, dhe, nĂ« varĂ«si tĂ« kĂ«saj, nĂ«se kĂ«rkohet dĂ«rgimi apo marrja e skedarit, ta dĂ«rgojĂ« ose ta marrĂ«.

Kodi që nis klientin për lidhjen me serverin dhe pret komandën nga rrjedha e inputit duket kështu. Një sërë variablash globalë që përdoren këtu, përshkruhen jashtë artikullit, në tekstin e plotë të programit. Për shkak të trivialitetit të tyre, nuk i sjell, që të mos ngarkoj artikullin.

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

Le të kalojmë në metodat e thirrura në këtë bllok kodi:

KĂ«tu ndodh dĂ«rgimi i skedarit — duke pĂ«rdorur skanerin ne i paraqesim pĂ«rmbajtjen e skedarit si njĂ« array bajtĂ«sh, tĂ« cilĂ«t i shkruajmĂ« njĂ« pas njĂ« nĂ« socket, pastaj e mbyllim atĂ« dhe e hapim pĂ«rsĂ«ri (nuk Ă«shtĂ« zgjidhja mĂ« e dukshme, por garanton çlirimin e burimeve), dhe mĂ« pas tregojmĂ« nĂ« ekran njĂ« mesazh pĂ«r dĂ«rgimin e suksesshĂ«m.

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

Ky fragment kodi përshkruan marrjen e të dhënave nga serveri. Të gjitha përsëri janë të thjeshta, interesi përqendrohet te blloku i parë i kodit. Për të kuptuar se sa bajtësh duhet të lexojmë nga socket, duhet të dimë sa peshon skedari i dërguar. Madhësia e skedarit në server paraqitet si një numër i gjatë, ndaj këtu merrni 4 bajtë që më vonë konvertohen në një numër. Kjo nuk është një qasje shumë Java, më shumë ngjan me C, por e zgjidh problemin e vet.

MĂ« pas, gjithçka Ă«shtĂ« e thjeshtĂ« — ne marrim njĂ« numĂ«r tĂ« njohur bajtĂ«sh nga socket dhe i shkruajmĂ« ata nĂ« skedar, pastaj tregojmĂ« njĂ« mesazh pĂ«r suksesin.

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

Në rastin kur në dritaren e klientit është futur një komandë, e ndryshme nga get ose put, do të thirret funksioni showErrorMessage, që jep alarmin për inputin e pavlefshëm. Për shkak të trivielitetit - nuk e sjell. Disa më interesante është funksioni i marrjes dhe ndarjes së vargut hyrës. Në të kalojmë skanerin, nga i cili presim të marrim një varg, të ndarë me dy hapësira dhe që përmban komandën, adresën burim dhe adresën e destinacionit.

    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("Input i keq");
        }
    }

Dërgimi i komandës - kalimi i komandës të futur nga skaneri në soket dhe dërgimi i saj me forcë.

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

Selekori - funksioni që përcakton veprimet e programit në varësi të vargut të futur. Këtu gjërat nuk janë shumë të bukura dhe përdoret një teknikë e dobët me daljen e detyruar jashtë bllokut të kodit, por arsyeja kryesore e kësaj është mungesa e disa gjërave në Java, si delegatët në C#, treguesit e funksionit nga C++ ose të paktën goto e tmerrshme, që lejojnë ta realizosh atë bukur. Nëse e dini si të bëni kodin pak më elegant - pres kritika në komentet. Më duket se nevojitet një fjalor String-delegate, por nuk ka delegatë...

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

TFTPServer

Funksionaliteti i serverit ndryshon nga funksionaliteti i klientit kryesisht vetëm në faktin se komandat për të vijnë jo nga tastiera, por nga soketi. Disa metoda janë të njëjta, prandaj nuk do t'i sjell, do të trajtoj vetëm dallimet.

Për aktivizimin e këtij përdoret metoda run, që merr si input portin dhe përpunon të dhënat hyrëse nga soketi në një cikël të pafund.

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

Metoda put, e cila është një mbështetje për metodën writeToFileFromSocket, hap një rrjedhë shkrimi në skedar dhe shkruan të gjitha bytët e hyrjes nga soketi, pas përfundimit të shkrimit, e boton një mesazh për përfundimin e suksesshëm të transferimit.

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

Metoda get siguron marrjen e skedarit nga serveri. Siç u tha në seksionin për anën e klientit të programit, për të realizuar me sukses transferimin e skedarit nevojitet të dihet madhësia e tij, që ruhet në një numër të gjatë të plotë, prandaj unë e ndaj në një masiv prej 4 bytësh, i kaloj ato një nga një në soket, dhe më pas, pasi i marr dhe i rimblidh në klient si një numër, dërgoj të gjithë bytët që përbëjnë skedarin, të lexuar nga rrjedha e hyrjes nga skedari.


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

Metoda getAndParseInput është e ngjashme me atë në klient, përveç faktit që ajo lexon të dhënat nga soketi dhe jo nga tastiera. Kodi në depo, si dhe selector.
Në këtë rast, inicializimi është nxjerrë në një bllok të veçantë kodi, për shkak se në kuadër të kësaj realizimi, pas përfundimit të transferimit, resurset lirohen dhe përsëri zënë vend, prapë me qëllim për të siguruar mbrojtjen nga rrjedhja e memories.

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

Përmbledhje:

Pak mĂ« parĂ«, ne shkruam variacionin tonĂ« mbi njĂ« protokoll tĂ« thjeshtĂ« tĂ« transferimit tĂ« tĂ« dhĂ«nave dhe sqaruam sesi duhet tĂ« funksionojĂ«. NĂ« parim, nuk kam zbuluar asgjĂ« tĂ« re dhe madje nuk kam shkruar asgjĂ« shumĂ« tĂ« re, por — artikuj tĂ« ngjashĂ«m nĂ« Habra nuk kishte, dhe nĂ« kuadĂ«r tĂ« shkrimit tĂ« ciklit tĂ« artikujve mbi utilitat cmd nuk mund ta lija jashtĂ«.

Lidhjet:

Depo me kodin burimor
Përmbledhje mbi TFTP
E njëjta gjë, por në rusisht

Burimi: habr.com

Blini hosting tĂ« besueshĂ«m pĂ«r faqe interneti me mbrojtje nga DDoS, serverĂ« VPS VDS đŸ”„ Blini hosting tĂ« besueshĂ«m pĂ«r faqe interneti me mbrojtje nga DDoS, serverĂ« VPS VDS | ProHoster