Shkrimi i softuerit me funksionalitete të mjeteve klient-server Windows, pjesa 02

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

Ashtu si herĂ«n e kaluar, do tĂ« bĂ«jmĂ« njĂ« pĂ«rmbledhje tĂ« shkurtĂ«r tĂ« teorisĂ«, do tĂ« shohim kodin qĂ« implementon funksionalitetin e ngjashĂ«m me atĂ« qĂ« ne kĂ«rkojmĂ« dhe do ta analizojmĂ« atĂ«. MĂ« shumĂ« — nĂ«n vijĂ«.

Nuk do tĂ« kopjoj informacionin ndihmĂ«s, lidhjet pĂ«r tĂ« cilat zakonisht mund t'i gjeni nĂ« fund tĂ« artikullit, do tĂ« them vetĂ«m se nĂ« thelb TFTP Ă«shtĂ« njĂ« variant i thjeshtuar i protokollit FTP, ku Ă«shtĂ« hequr konfigurimi i kontrollit tĂ« aksesit dhe nĂ« thelb nuk ka asgjĂ« tjetĂ«r pĂ«rveç komandave pĂ«r marrjen dhe dĂ«rgimin e skedarit. MegjithatĂ«, pĂ«r ta bĂ«rĂ« implementimin tonĂ« pak mĂ« elegant dhe tĂ« adaptuar ndaj principeve tĂ« sotme tĂ« kodimit, sintaksa Ă«shtĂ« ndryshuar pak — kjo nuk ndryshon principet e funksionimit, por ndihmon qĂ« interfeusi, IMHO, tĂ« jetĂ« pak mĂ« logjik dhe tĂ« kombinonte avantazhet e FTP dhe TFTP.

NĂ« veçanti, kur ulni klientin, ai kĂ«rkon adresĂ«n IP tĂ« serverit dhe portin ku Ă«shtĂ« hapur TFTP i personalizuar (pĂ«r shkak tĂ« moskompatibilitetit me protokollin standard, e konsiderova tĂ« arsyeshme tĂ« lĂ« mundĂ«sinĂ« pĂ«r tĂ« zgjedhur portin pĂ«r pĂ«rdoruesin), pas sĂ« cilĂ«s ndodh lidhja, rezultat i sĂ« cilĂ«s Ă«shtĂ« se klienti mund tĂ« dĂ«rgojĂ« njĂ« nga urdhrat — get ose put, pĂ«r tĂ« marrĂ« ose dĂ«rguar njĂ« skedar nĂ« server. TĂ« gjitha skedarĂ«t dĂ«rgohen nĂ« mĂ«nyrĂ« binare — pĂ«r tĂ« thjeshtuar logjikĂ«n.

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

  • TFTPClient
  • TFTPServer
  • TFTPClientTester
  • TFTPServerTester

PĂ«r shkak se klasat testuese ekzistojnĂ« vetĂ«m pĂ«r debugging tĂ« klasave kryesore, unĂ« nuk do t’i shqyrtoj ato, por kodi do tĂ« jetĂ« nĂ« repositor, me njĂ« lidhje pĂ«r tĂ« mund tĂ« konsultoheni nĂ« fund tĂ« artikullit. Tani do tĂ« shqyrtoj klasat kryesore.

TFTPClient

QĂ«llimi i kĂ«saj klase Ă«shtĂ« tĂ« lidhet me serverin e largĂ«t pĂ«rmes adresĂ«s sĂ« tij IP dhe numrit tĂ« portit, tĂ« lexojĂ« nga rrjedha hyrĂ«se (nĂ« kĂ«tĂ« rast — tastiera) urdhrin, ta analizojĂ«, ta dĂ«rgojĂ« te serveri, dhe, nĂ« varĂ«si tĂ« nevojĂ«s pĂ«r transferim ose marrje skedari, ta dĂ«rgojĂ« ose tĂ« marrĂ« atĂ«.

Kodi i lançimit të klientit për t'u lidhur me serverin dhe për të pritur një urdhër nga rrjedha e hyrjes duket kështu. Një sërë variablash globalë, që përdoren këtu, jepen jashtë artikullit, në tekstin e plotë të programit. Për shkak të trivialisë së tyre, nuk i sjell, për të mos e ngarkuar 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());
        }
    }

Të kalojmë në metodat që thirren në këtë bllok kodi:

KĂ«tu ndodh dĂ«rgimi i skedarit — me ndihmĂ«n e skanuesit ne e paraqesim pĂ«rmbajtjen e skedarit si njĂ« masiv bajtesh, tĂ« cilat i shkruajmĂ« radhazi nĂ« soket, pastaj e mbyllim dhe e hapim pĂ«rsĂ«ri (njĂ« zgjidhje e cila nuk Ă«shtĂ« e dukshme, por garanton lirimin e burimeve), pas sĂ« cilĂ«s shfaqim 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. Gjithçka Ă«shtĂ« prapĂ« triviale, e rĂ«ndĂ«sishme Ă«shtĂ« vetĂ«m blloku i parĂ« i kodit. PĂ«r tĂ« kuptuar saktĂ«sisht sa byte duhet tĂ« lexojmĂ« nga socket-i, duhet tĂ« dimĂ« se sa peshojnĂ« skedari qĂ« po dĂ«rgohet. MadhĂ«sia e skedarit nĂ« сДрĐČДр paraqitet si njĂ« numĂ«r i gjatĂ«, prandaj kĂ«tu merren 4 byte, tĂ« cilat mĂ« pas konvertohen nĂ« njĂ« numĂ«r. Ky nuk Ă«shtĂ« njĂ« qasje shumĂ« Javane, Ă«shtĂ« mĂ« tepĂ«r e ngjashme me C, por zgjidh problemin e vet.

MĂ« pas gjithçka Ă«shtĂ« triviale — ne marrim njĂ« numĂ«r tĂ« njohur byte-esh nga socket-i dhe i shkruajmĂ« ato nĂ« skedarin, dhe pastaj japim njĂ« mesazh mbi 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Ă« rast se nĂ« dritaren e klientit Ă«shtĂ« futur njĂ« komandĂ« e ndryshme nga get ose put, do tĂ« thirret funksioni showErrorMessage, i cili tregon se inputi nuk Ă«shtĂ« i saktĂ«. PĂ«r shkak tĂ« trivialitetit — nuk po e pĂ«rmend. MĂ« interesant Ă«shtĂ« funksioni i marrjes dhe ndarjes sĂ« vargut tĂ« hyrjes. NĂ« tĂ« ne kalojmĂ« skanerin, nga i cili presim qĂ« tĂ« marrim njĂ« varg tĂ« ndarĂ« me dy hapĂ«sira dhe qĂ« pĂ«rmban komandĂ«n, adresĂ«n burim dhe adresĂ«n destinacion.

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

Selektoƙi — njĂ« funksion qĂ« pĂ«rcakton veprimet e programit nĂ« varĂ«si tĂ« vargut tĂ« futur. KĂ«tu gjithçka nuk Ă«shtĂ« shumĂ« e bukur dhe pĂ«rdoret njĂ« teknikĂ« jo shumĂ« e mirĂ« me daljen e detyruar jashtĂ« bllokut tĂ« kodit, por arsyeja kryesore pĂ«r kĂ«tĂ« Ă«shtĂ« mungesa e disa gjĂ«rave nĂ« Java, si delegatĂ«t nĂ« C#, treguesit e funksionit nga C++ ose edhe njĂ« goto e tmerrshme, qĂ« lejojnĂ« implementimin e kĂ«saj me elegancĂ«. NĂ«se e dini si ta bĂ«ni kodin mĂ« tĂ« bukur — pres kritikĂ«n tuaj nĂ« komente. MĂ« duket se njĂ« fjalor String-delegate do tĂ« ishte i nevojshĂ«m, 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 ai i klientit kryesisht në mënyrën se si komandat vijnë; ato nuk vijnë nga tastiera, por nga një socket. Disa metoda përputhen krejtësisht, kështu që nuk do t'i përmend atje, do të trajtoj vetëm dallimet.

Për të filluar, përdoret metoda run, e cila merr portin si input dhe përpunon të dhënat hyrëse nga socket-i në një cikël të pafund.

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

Metoda put, që është një mbështjellës i metodës writeToFileFromSocket, hap një rrjedhë shkrimi në skedar dhe shkruan të gjitha bajtat e hyrjes nga socket-i; pas përfundimit të shkrimit, nxjerr 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 të serverit. Siç është përmendur në pjesën për anën e klientit të programit, për të siguruar një transmetim të suksesshëm të skedarit, duhet të dihet madhësia e tij, e cila ruhet në një tërë të gjatë, kështu që unë e ndaj atë në një array prej 4 bajtësh, duke i dërguar një nga një në soket, dhe pastaj, duke i pranuar dhe rikonstruktuar ato në klient në një numër, dërgoj të gjitha bajtët që përbëjnë skedarin, të lexuara nga rrjedha e hyrjes së skedarit.


 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 njëjtë me atë në anën e klientit, me përjashtim të asaj se ajo lexon të dhënat nga soketi, jo nga tastiera. Kodi në depo, ashtu si dhe selector.
NĂ« kĂ«tĂ« rast, inicializimi Ă«shtĂ« nxjerrĂ« nĂ« njĂ« bllok tĂ« veçantĂ« kode, sepse nĂ« kuadĂ«r tĂ« kĂ«saj zbatimi, pas pĂ«rfundimit tĂ« transmetimit, burimet lirohen dhe rikthehen sĂ«rish — pĂ«rsĂ«ri me qĂ«llimin pĂ«r tĂ« siguruar mbrojtjen nga rrjedhjet 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());
        }
    }

Në përfundim:

Sapo shkruam variacionin tonĂ« mbi protokollin e thjeshtĂ« tĂ« transmetimit tĂ« tĂ« dhĂ«nave dhe kuptuam se si duhet tĂ« funksionojĂ«. NĂ« thelb, nuk kam zbuluar AmerikĂ«n kĂ«tu dhe nuk kam shkruar diçka shumĂ« tĂ« re, por — nuk kishte artikuj tĂ« ngjashĂ«m nĂ« HabrĂ«, dhe nĂ« kuadĂ«r tĂ« shkruarjes sĂ« njĂ« cikli artikujsh pĂ«r utilitat e cmd, nuk mund ta kalonim pa e pĂ«rmendur.

Linket:

Repozitori me kodin burimor
Përmbledhje mbi TFTP
I njëjti, por në rusisht

Burimi: habr.com

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