Skryfsagteware met die funksionaliteit van Windows-kliënt-bedienerhulpprogramme, deel 02

Om voort te gaan met die deurlopende reeks artikels wat gewy is aan pasgemaakte implementering van Windows-konsole-hulpmiddels, kan ons nie anders as om TFTP (Trivial File Transfer Protocol) aan te raak nie - 'n eenvoudige lêeroordragprotokol.

Soos verlede keer, kom ons gaan kortliks oor die teorie, sien die kode wat funksionaliteit soortgelyk aan die vereiste een implementeer, en ontleed dit. Meer besonderhede - onder die snit

Ek sal nie verwysingsinligting kopieer-plak nie, waarna skakels tradisioneel aan die einde van die artikel gevind kan word, ek sal net sê dat TFTP in sy kern 'n vereenvoudigde variasie van die FTP-protokol is, waarin die toegangsbeheerinstelling verwyder is, en in werklikheid is daar niks hier nie, behalwe opdragte vir die ontvang en oordrag van 'n lêer . Om ons implementering egter 'n bietjie meer elegant te maak en aangepas by die huidige beginsels van die skryf van kode, is die sintaksis effens verander - dit verander nie die beginsels van werking nie, maar die koppelvlak, IMHO, word 'n bietjie meer logies en kombineer die positiewe aspekte van FTP en TFTP.

In die besonder, wanneer dit geloods word, versoek die kliënt die bediener se IP-adres en die poort waarop persoonlike TFTP oop is (as gevolg van onverenigbaarheid met die standaardprotokol, het ek dit gepas geag om die gebruiker die vermoë te laat om 'n poort te kies), waarna 'n verbinding vind plaas, as gevolg waarvan die kliënt een van die opdragte kan stuur - kry of sit, om 'n lêer te ontvang of na die bediener te stuur. Alle lêers word in binêre modus gestuur om die logika te vereenvoudig.

Om die protokol te implementeer, het ek tradisioneel 4 klasse gebruik:

  • TFTPkliënt
  • TFTPServer
  • TFTPClientTester
  • TFTPServerTester

As gevolg van die feit dat toetsklasse slegs bestaan ​​​​vir die ontfouting van die belangrikste, sal ek hulle nie analiseer nie, maar die kode sal in die bewaarplek wees, 'n skakel daarna kan gevind word aan die einde van die artikel. Nou sal ek na die hoofklasse kyk.

TFTPkliënt

Die taak van hierdie klas is om aan 'n afgeleë bediener te koppel deur sy ip en poortnommer te gebruik, 'n opdrag van die invoerstroom (in hierdie geval die sleutelbord) te lees, dit te ontleed, dit na die bediener oor te dra, en, afhangende van of jy moet 'n lêer stuur of ontvang, dit oordra of kry.

Die kode vir die bekendstelling van die kliënt om aan die bediener te koppel en te wag vir 'n opdrag van die invoerstroom lyk soos volg. 'n Aantal globale veranderlikes wat hier gebruik word, word buite die artikel beskryf, in die volledige teks van die program. Weens hul nietigheid haal ek hulle nie aan om nie die artikel te oorlaai nie.

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

Kom ons gaan oor die metodes wat in hierdie blok kode genoem word:

Hier word die lêer gestuur - met behulp van 'n skandeerder bied ons die inhoud van die lêer aan as 'n reeks grepe, wat ons een vir een na die sok skryf, waarna ons dit toemaak en weer oopmaak (nie die mees voor die hand liggende oplossing nie, maar dit waarborg die vrystelling van hulpbronne), waarna ons 'n boodskap oor suksesvolle oordrag vertoon.

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

Hierdie kodefragment beskryf die herwinning van data vanaf die bediener. Alles is weer triviaal, net die eerste blok kode is van belang. Om presies te verstaan ​​hoeveel grepe uit die sok gelees moet word, moet jy weet hoeveel die oorgedrade lêer weeg. Die lêergrootte op die bediener word voorgestel as 'n lang heelgetal, so 4 grepe word hier aanvaar, wat daarna in een getal omgeskakel word. Dit is nie 'n baie Java-benadering nie, dit is eerder soortgelyk vir SI, maar dit los sy probleem op.

Dan is alles triviaal - ons ontvang 'n bekende aantal grepe uit die sok en skryf dit na 'n lêer, waarna ons 'n suksesboodskap vertoon.

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

As 'n opdrag anders as kry of sit in die kliëntvenster ingevoer is, sal die showErrorMessage-funksie geroep word, wat aandui dat die invoer verkeerd was. Weens onbenullighede sal ek dit nie aanhaal nie. Ietwat meer interessant is die funksie om die invoerstring te ontvang en te verdeel. Ons gee die skandeerder daarin, waarvandaan ons verwag om 'n reël te ontvang wat deur twee spasies geskei word en wat die opdrag, bronadres en bestemmingsadres bevat.

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

Stuur 'n opdrag—stuur die opdrag wat vanaf die skandeerder ingevoer is na die sok en dwing dit om gestuur te word

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

'n Kieser is 'n funksie wat die program se aksies bepaal afhangende van die ingevoerde string. Alles hier is nie baie mooi nie en die truuk wat gebruik word is nie die beste een met gedwonge uitgang buite die kodeblok nie, maar die hoofrede hiervoor is die afwesigheid in Java van sommige dinge, soos afgevaardigdes in C#, funksiewysers van C++, of by ten minste die verskriklike en verskriklike goto, wat jou toelaat om dit pragtig te implementeer. As jy weet hoe om die kode 'n bietjie meer elegant te maak, verwelkom ek kritiek in die kommentaar. Dit lyk vir my of 'n String-delegate-woordeboek hier nodig is, maar daar is geen afgevaardigde nie...

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

TFTPServer

Die funksionaliteit van die bediener verskil grootliks van die funksionaliteit van die kliënt, net deurdat opdragte nie vanaf die sleutelbord nie, maar van die sok na hom toe kom. Sommige van die metodes is oor die algemeen dieselfde, so ek sal hulle nie aanhaal nie, ek sal net die verskille aanraak.

Om te begin, word die hardloopmetode gebruik, wat 'n poort as invoer ontvang en die invoerdata vanaf die sok in 'n ewige lus verwerk.

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

Die put-metode, wat die writeToFileFromSocket-metode omvou wat 'n skryfstroom na 'n lêer oopmaak en alle invoergrepe vanaf die sok skryf, vertoon 'n boodskap wat die suksesvolle voltooiing van die oordrag aandui wanneer die skryf voltooi is.

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

Die get-metode haal die bedienerlêer op. Soos reeds genoem in die afdeling aan die kliëntkant van die program, om 'n lêer suksesvol oor te dra, moet jy die grootte daarvan ken, gestoor in 'n lang heelgetal, so ek verdeel dit in 'n verskeidenheid van 4 grepe, dra dit byte-vir-greep oor na die sok, en dan, nadat ek dit op die kliënt ontvang en saamgestel het in 'n nommer terug, dra ek al die grepe oor waaruit die lêer bestaan, gelees vanaf die invoerstroom van die lêer.


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

Die getAndParseInput-metode is dieselfde as in die kliënt, die enigste verskil is dat dit data vanaf die sok lees eerder as vanaf die sleutelbord. Die kode is in die bewaarplek, net soos selector.
In hierdie geval word die inisialisering in 'n aparte blok kode geplaas, want binne hierdie implementering, nadat die oordrag voltooi is, word hulpbronne vrygestel en weer beset - weer om beskerming teen geheuelekkasies te bied.

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

Op te som:

Ons het pas ons eie variasie op 'n eenvoudige data-oordragprotokol geskryf en uitgepluis hoe dit moet werk. In beginsel het ek Amerika nie hier ontdek nie en nie veel nuwe dinge geskryf nie, maar daar was geen soortgelyke artikels oor Habré nie, en as deel van die skryf van 'n reeks artikels oor cmd-nutsprogramme was dit onmoontlik om nie daaraan te raak nie.

verwysings:

Bronkode-bewaarplek
Kortliks oor TFTP
Dieselfde ding, maar in Russies

Bron: will.com

Voeg 'n opmerking