Skrivesoftware med funktionaliteten fra Windows-klient-server-værktøjer, del 02

Vi fortsætter den igangværende serie af artikler om tilpassede implementeringer af Windows-konsolværktøjer, og vi kan ikke undgå at røre ved TFTP (Trivial File Transfer Protocol) - en simpel filoverførselsprotokol.

Lad os som sidste gang kort gennemgå teorien, se koden, der implementerer funktionalitet svarende til den påkrævede, og analysere den. Flere detaljer - under snittet

Jeg vil ikke kopiere og indsætte referenceoplysninger, som man traditionelt kan finde links til sidst i artiklen, jeg vil kun sige, at i sin kerne er TFTP en forenklet variation af FTP-protokollen, hvor adgangskontrolindstillingen har blevet fjernet, og faktisk er der intet her undtagen kommandoer til modtagelse og overførsel af en fil . Men for at gøre vores implementering lidt mere elegant og tilpasset de nuværende principper for at skrive kode, er syntaksen blevet ændret lidt - det ændrer ikke på principperne for driften, men grænsefladen, IMHO, bliver lidt mere logisk og kombinerer de positive aspekter ved FTP og TFTP.

Specielt, når den startes, anmoder klienten om serverens IP-adresse og den port, som brugerdefineret TFTP er åben på (på grund af inkompatibilitet med standardprotokollen, fandt jeg det passende at lade brugeren have mulighed for at vælge en port), hvorefter en forbindelse opstår, som et resultat af hvilket klienten kan sende en af ​​kommandoerne - get eller put, for at modtage eller sende en fil til serveren. Alle filer sendes i binær tilstand for at forenkle logikken.

For at implementere protokollen brugte jeg traditionelt 4 klasser:

  • TFTPClient
  • TFTPServer
  • TFTPClientTester
  • TFTPServerTester

På grund af det faktum, at testklasser kun findes til fejlretning af de vigtigste, vil jeg ikke analysere dem, men koden vil være i depotet; et link til det kan findes i slutningen af ​​artiklen. Nu vil jeg se på hovedklasserne.

TFTPClient

Opgaven for denne klasse er at oprette forbindelse til en ekstern server ved hjælp af dens ip og portnummer, læse en kommando fra inputstrømmen (i dette tilfælde tastaturet), analysere den, overføre den til serveren og, afhængigt af om du skal sende eller modtage en fil, overføre den eller hente.

Koden til at starte klienten for at oprette forbindelse til serveren og vente på en kommando fra inputstrømmen ser sådan ud. En række globale variabler, der bruges her, er beskrevet uden for artiklen, i programmets fulde tekst. På grund af deres trivialitet citerer jeg dem ikke for ikke at overbelaste artiklen.

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

Lad os gennemgå de metoder, der kaldes i denne kodeblok:

Her sendes filen - ved hjælp af en scanner præsenterer vi filens indhold som et array af bytes, som vi skriver en efter en til socket, hvorefter vi lukker den og åbner den igen (ikke den mest oplagte løsning, men det garanterer frigivelse af ressourcer), hvorefter vi viser en besked om vellykket overførsel.

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

Dette kodefragment beskriver hentning af data fra serveren. Alt er igen trivielt, kun den første kodeblok er af interesse. For at forstå præcis, hvor mange bytes der skal læses fra soklen, skal du vide, hvor meget den overførte fil vejer. Filstørrelsen på serveren er repræsenteret som et langt heltal, så her accepteres 4 bytes, som efterfølgende konverteres til ét tal. Dette er ikke en meget Java-tilgang, det er ret ens for SI, men det løser sit problem.

Så er alt trivielt - vi modtager et kendt antal bytes fra socket og skriver dem til en fil, hvorefter vi viser en succesmeddelelse.

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

Hvis en anden kommando end get eller put blev indtastet i klientvinduet, vil showErrorMessage-funktionen blive kaldt, hvilket indikerer, at inputtet var forkert. På grund af trivialitet vil jeg ikke citere det. Noget mere interessant er funktionen med at modtage og opdele inputstrengen. Vi sender scanneren ind i den, hvorfra vi forventer at modtage en linje adskilt af to mellemrum og indeholdende kommandoen, kildeadressen og destinationsadressen.

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

Sender en kommando - sender den indtastede kommando fra scanneren til stikkontakten og tvinger den til at blive sendt

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

En vælger er en funktion, der bestemmer programmets handlinger afhængigt af den indtastede streng. Alt her er ikke særlig smukt, og det anvendte trick er ikke det bedste med tvungen exit uden for kodeblokken, men hovedårsagen til dette er fraværet i Java af nogle ting, som delegerede i C#, funktionspointere fra C++ eller kl. mindst den forfærdelige og forfærdelige goto, som giver dig mulighed for at implementere dette smukt. Hvis du ved, hvordan du gør koden lidt mere elegant, glæder jeg mig over kritik i kommentarerne. Det forekommer mig, at der er brug for en streng-delegeret ordbog her, men der er ingen delegeret...

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

TFTPServer

Serverens funktionalitet adskiller sig stort set fra klientens funktionalitet, kun ved at kommandoer ikke kommer til den fra tastaturet, men fra stikkontakten. Nogle af metoderne er generelt de samme, så jeg vil ikke citere dem, jeg vil kun komme ind på forskellene.

Til at starte bruges kørselsmetoden, som modtager en port som input og behandler inputdata fra stikkontakten i en evig løkke.

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

Put-metoden, som ombryder metoden writeToFileFromSocket, der åbner en skrivestrøm til en fil og skriver alle inputbytes fra soklen, viser en meddelelse, der angiver, at overførslen er gennemført, når skrivningen er fuldført.

    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-metoden henter serverfilen. Som allerede nævnt i afsnittet om klientsiden af ​​programmet, for at overføre en fil med succes, skal du kende dens størrelse, gemt i et langt heltal, så jeg opdeler den i en matrix på 4 bytes, overfører dem byte-for-byte til socket, og derefter, efter at have modtaget og samlet dem på klienten til et nummer tilbage, overfører jeg alle de bytes, der udgør filen, læst fra inputstrømmen fra filen.


 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-metoden er den samme som i klienten, den eneste forskel er, at den læser data fra soklen i stedet for fra tastaturet. Koden er i depotet, ligesom selector.
I dette tilfælde placeres initialiseringen i en separat kodeblok, fordi i denne implementering, efter at overførslen er fuldført, frigives ressourcer og optages igen - igen for at yde beskyttelse mod hukommelseslækager.

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

At opsummere:

Vi har lige skrevet vores egen variant af en simpel dataoverførselsprotokol og fundet ud af, hvordan den skal fungere. I princippet opdagede jeg ikke Amerika her og skrev ikke meget nyt, men der var ingen lignende artikler om Habré, og som en del af at skrive en serie artikler om cmd-værktøjer var det umuligt ikke at røre ved det.

referencer:

Kildekodelager
Kort om TFTP
Det samme, men på russisk

Kilde: www.habr.com

Tilføj en kommentar