Windows bezero-zerbitzariaren utilitateen funtzionaltasunarekin softwarea idaztea, 02. zatia

Windows kontsolaren utilitateen inplementazio pertsonalizatuei eskainitako etengabeko artikulu sortarekin jarraituz, ezin dugu TFTP (Trivial File Transfer Protocol) ukitu, fitxategiak transferitzeko protokolo sinple bat.

Azken aldian bezala, goazen teoria laburki, ikusi behar denaren antzeko funtzionalitateak ezartzen dituen kodea eta aztertu. Xehetasun gehiago - ebaki azpian

Ez dut kopiatu-itsatsiko erreferentzia-informazioa, tradizionalki artikuluaren amaieran aurki daitezkeen estekak, soilik esango dut bere oinarrian, TFTP FTP protokoloaren aldaera sinplifikatu bat dela, zeinetan sarbide-kontrolaren ezarpenak dituena. kendu egin da, eta, egia esan, hemen ez dago ezer fitxategi bat jaso eta transferitzeko komandoak izan ezik. Hala ere, gure inplementazioa apur bat dotoreagoa eta kodea idazteko egungo printzipioetara egokitua izan dadin, sintaxia apur bat aldatu da - horrek ez ditu funtzionamendu-printzipioak aldatzen, baina interfazea, IMHO, apur bat logikoagoa bihurtzen da eta FTP eta TFTPren alderdi positiboak konbinatzen ditu.

Bereziki, abiaraztean, bezeroak zerbitzariaren IP helbidea eta TFTP pertsonalizatua irekita dagoen ataka eskatzen du (protokolo estandarrekiko bateraezintasuna dela eta, egokitzat jo nuen erabiltzaileari ataka hautatzeko gaitasuna uztea), eta ondoren konexioa gertatzen da, eta horren ondorioz bezeroak komandoetako bat bidal dezake: lortu edo jarri, fitxategi bat zerbitzarira jaso edo bidaltzeko. Fitxategi guztiak modu bitarrean bidaltzen dira logika errazteko.

Protokoloa ezartzeko, tradizionalki 4 klase erabiltzen nituen:

  • TFTPClient
  • TFTPServer
  • TFTPClientTester
  • TFTPServerTester

Testing klaseak nagusiak arazketarako soilik existitzen direnez, ez ditut aztertuko, baina kodea biltegian egongo da; artikulu honen amaieran aurki daiteke esteka bat. Orain klase nagusiak aztertuko ditut.

TFTPClient

Klase honen zeregina da urruneko zerbitzari batera konektatzea bere ip eta ataka zenbakiaren bidez, sarrerako korrontetik komando bat irakurri (kasu honetan, teklatua), analizatu, zerbitzarira transferitzea eta, ala ez duzunaren arabera. fitxategi bat bidali edo jaso, transferitu edo lortu behar duzu.

Bezeroa abiarazteko zerbitzariarekin konektatzeko eta sarrerako korrontearen komando bat itxaron behar duen kodea honelakoa da. Hemen erabiltzen diren aldagai global batzuk artikulutik kanpo deskribatzen dira, programaren testu osoan. Haien huskeria dela eta, ez ditut aipatzen artikulua ez gainkargatzeko.

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

Ikus ditzagun kode bloke honetan deitutako metodoak:

Hemen fitxategia bidaltzen da - eskaner bat erabiliz, fitxategiaren edukia byte-matrize gisa aurkezten dugu, banan-banan idazten dugu socketean, ondoren itxi eta berriro irekiko dugu (ez da irtenbiderik nabariena, baina baliabideak askatzea bermatzen du), eta ondoren, transferentzia arrakastatsuari buruzko mezua bistaratzen dugu.

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

Kode zati honek zerbitzaritik datuak berreskuratzea deskribatzen du. Dena berriro hutsala da, lehen kode blokea baino ez da interesgarria. Socketetik zenbat byte irakurri behar diren zehazki ulertzeko, transferitutako fitxategiak zenbat pisatzen duen jakin behar duzu. Zerbitzariko fitxategiaren tamaina zenbaki oso luze gisa irudikatzen da, beraz, 4 byte onartzen dira hemen, eta gero zenbaki bakar batean bihurtzen dira. Hau ez da oso Java ikuspegia, nahiko antzekoa da SIrako, baina arazoa konpontzen du.

Orduan dena hutsala da - socketetik byte kopuru jakin bat jasotzen dugu eta fitxategi batean idazten ditugu, ondoren arrakasta mezua bistaratzen dugu.

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

Bezeroaren leihoan get edo put ez den komando bat sartu bada, showErrorMessage funtzioa deituko da, sarrera okerra dela adieraziz. Huskeria dela eta, ez dut aipatuko. Zertxobait interesgarriagoa da sarrerako katea jaso eta zatitzeko funtzioa. Eskanerra pasatzen dugu bertara, eta bertatik bi zuriunez bereizitako lerro bat jasotzea espero dugu, komandoa, sorburu-helbidea eta helmuga-helbidea dituena.

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

Komando bat bidaltzea: eskanerretik sartutako komandoa socketera bidaltzen du eta bidaltzera behartzen du

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

Hautatzailea sartutako katearen arabera programaren ekintzak zehazten dituen funtzioa da. Hemen dena ez da oso polita eta erabilitako trikimailua ez da onena kode bloketik kanpo behartutako irteerarekin, baina horren arrazoi nagusia Javan gauza batzuk ez egotea da, hala nola delegatuak C#-n, C++-ko funtzio erakusleak edo gutxienez goto ikaragarria eta ikaragarria, hau ederki ezartzeko aukera ematen dutenak. Kodea apur bat dotoreagoa egiten badakizu, ongi etorria ematen dizut iruzkinetan egindako kritikak. String-delegatuen hiztegia behar dela iruditzen zait hemen, baina ez dago ordezkaririk...

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

TFTPServer

Zerbitzariaren funtzionaltasuna bezeroaren funtzionaltasunetik desberdina da, orokorrean, komandoak teklatutik ez, socket-etik etortzen direlako bakarrik. Metodo batzuk, oro har, berdinak dira, beraz, ez ditut aipatuko, desberdintasunak bakarrik ukituko ditut.

Hasteko, run-metodoa erabiltzen da, sarrera gisa ataka bat jasotzen duena eta socketeko sarrerako datuak betiko begizta batean prozesatzen ditu.

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

put metodoak, idazketa korronte bat irekitzen duen fitxategi batean idazketa-korrontea irekitzen duen eta socket-etik sarrerako byte guztiak idazten dituen writeToFileFromSocket metodoa biltzen duena, mezu bat bistaratzen du idazketa amaitzean transferentzia arrakastaz amaitu dela adierazten duena.

    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 metodoak zerbitzariaren fitxategia berreskuratzen du. Programaren bezeroaren atalean esan bezala, fitxategi bat behar bezala transferitzeko bere tamaina jakin behar duzu, zenbaki oso luze batean gordeta, beraz 4 byte-ko array batean zatitu dut, bytez byte transferitu socketera, eta gero, bezeroan jaso eta zenbaki batean bildu ondoren, fitxategia osatzen duten byte guztiak transferitzen ditut, fitxategiko sarrerako korrontetik irakurrita.


 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 metodoa bezeroaren berdina da, desberdintasun bakarra teklatutik baino socketetik datuak irakurtzen dituela da. Kodea biltegian dago, hautatzailea bezala.
Kasu honetan, hasierako kode-bloke bereizi batean jartzen da, zeren inplementazio honen barruan, transferentzia amaitu ondoren, baliabideak askatu eta berriro okupatzen dira, berriro ere memoria ihesen aurkako babesa emateko.

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

Laburtzeko:

Datuen transferentzia protokolo sinple batean gure aldaera idatzi berri dugu eta nola funtzionatu behar duen asmatu dugu. Printzipioz, ez nuen Amerika hemen deskubritu eta ez nuen gauza berri handirik idatzi, baina ez zegoen HabrΓ©-ri buruzko antzeko artikulurik, eta cmd utilitateei buruzko artikulu sorta bat idaztean ezinezkoa zen hura ez ukitzea.

erreferentziak:

Iturburu-kodeen biltegia
TFTPri buruz laburki
Gauza bera, baina errusieraz

Iturria: www.habr.com

Gehitu iruzkin berria