Skriveprogramvare med funksjonaliteten til Windows klient-server-verktøy, del 02

Vi fortsetter den pågående serien med artikler som er viet tilpassede implementeringer av Windows-konsollverktøy, og vi kan ikke unngå å berøre TFTP (Trivial File Transfer Protocol) - en enkel filoverføringsprotokoll.

Som forrige gang, la oss kort gå gjennom teorien, se koden som implementerer funksjonalitet som ligner på den nødvendige, og analysere den. Flere detaljer - under kuttet

Jeg vil ikke kopiere og lime inn referanseinformasjon, som man tradisjonelt finner lenker til på slutten av artikkelen, jeg vil bare si at i kjernen er TFTP en forenklet variant av FTP-protokollen, der tilgangskontrollinnstillingen har blitt fjernet, og det er faktisk ingenting her bortsett fra kommandoer for å motta og overføre en fil . Men for å gjøre implementeringen vår litt mer elegant og tilpasset dagens prinsipper for å skrive kode, har syntaksen blitt litt endret - dette endrer ikke operasjonsprinsippene, men grensesnittet, IMHO, blir litt mer logisk og kombinerer de positive sidene ved FTP og TFTP.

Spesielt, når den startes, ber klienten om serverens IP-adresse og porten som tilpasset TFTP er åpen på (på grunn av inkompatibilitet med standardprotokollen anså jeg det som hensiktsmessig å la brukeren velge en port), hvoretter en tilkobling oppstår, som et resultat av at klienten kan sende en av kommandoene - get eller put, for å motta eller sende en fil til serveren. Alle filer sendes i binær modus for å forenkle logikken.

For å implementere protokollen brukte jeg tradisjonelt 4 klasser:

  • TFTPClient
  • TFTPServer
  • TFTPClientTester
  • TFTPServerTester

På grunn av det faktum at testklasser kun eksisterer for å feilsøke de viktigste, vil jeg ikke analysere dem, men koden vil være i depotet; en lenke til den finner du på slutten av artikkelen. Nå skal jeg se på hovedklassene.

TFTPClient

Oppgaven til denne klassen er å koble til en ekstern server ved hjelp av dens ip og portnummer, lese en kommando fra inngangsstrømmen (i dette tilfellet tastaturet), analysere den, overføre den til serveren, og avhengig av om du trenger å sende eller motta en fil, overføre den eller hente.

Koden for å starte klienten for å koble til serveren og vente på en kommando fra inngangsstrømmen ser slik ut. En rekke globale variabler som brukes her er beskrevet utenfor artikkelen, i hele programmets tekst. På grunn av deres trivialitet siterer jeg dem ikke for ikke å overbelaste artikkelen.

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

La oss gå over metodene som kalles i denne kodeblokken:

Her sendes filen - ved hjelp av en skanner presenterer vi innholdet i filen som en rekke byte, som vi skriver en etter en til stikkontakten, hvoretter vi lukker den og åpner den igjen (ikke den mest åpenbare løsningen, men det garanterer frigjøring av ressurser), hvoretter vi viser en melding om vellykket overføring.

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 kodefragmentet beskriver henting av data fra serveren. Alt er igjen trivielt, bare den første blokken med kode er av interesse. For å forstå nøyaktig hvor mange byte som må leses fra stikkontakten, må du vite hvor mye den overførte filen veier. Filstørrelsen på serveren er representert som et langt heltall, så 4 byte aksepteres her, som deretter konverteres til ett tall. Dette er ikke en veldig Java-tilnærming, den er ganske lik for SI, men den løser problemet.

Da er alt trivielt - vi mottar et kjent antall byte fra stikkontakten og skriver dem til en fil, hvoretter vi viser en suksessmelding.

   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 annen kommando enn get eller put ble lagt inn i klientvinduet, vil showErrorMessage-funksjonen bli kalt, noe som indikerer at inndataene var feil. På grunn av trivialitet vil jeg ikke sitere det. Noe mer interessant er funksjonen til å motta og dele inndatastrengen. Vi sender skanneren inn i den, hvorfra vi forventer å motta en linje atskilt med to mellomrom og som inneholder kommandoen, kildeadressen og destinasjonsadressen.

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

Sende en kommando – overfører kommandoen som er lagt inn fra skanneren til stikkontakten og tvinger den til å sendes

    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 velger er en funksjon som bestemmer programmets handlinger avhengig av den angitte strengen. Alt her er ikke veldig vakkert og trikset som brukes er ikke det beste med tvungen utgang utenfor kodeblokken, men hovedårsaken til dette er fraværet i Java av noen ting, som delegater i C#, funksjonspekere fra C++, eller kl. minst den forferdelige og forferdelige gotoen, som lar deg implementere dette vakkert. Hvis du vet hvordan du kan gjøre koden litt mer elegant, tar jeg gjerne imot kritikk i kommentarfeltet. Det ser ut til at det er behov for en ordbok for strengdelegater her, men det er ingen 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

Funksjonaliteten til serveren skiller seg stort sett fra klientens funksjonalitet, bare ved at kommandoer ikke kommer til den fra tastaturet, men fra stikkontakten. Noen av metodene er generelt de samme, så jeg vil ikke sitere dem, jeg vil bare berøre forskjellene.

For å starte brukes kjøremetoden, som mottar en port som inngang og behandler inngangsdataene fra stikkontakten i en evig sløyfe.

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

Put-metoden, som omslutter writeToFileFromSocket-metoden som åpner en skrivestrøm til en fil og skriver alle input-byte fra sokkelen, viser en melding som indikerer vellykket fullføring av overføringen når skrivingen er fullfø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 nevnt i delen på klientsiden av programmet, for å overføre en fil må du vite størrelsen, lagret i et langt heltall, så jeg deler den inn i en rekke 4 byte, overfører dem byte-for-byte til kontakten, og deretter, etter å ha mottatt og satt dem sammen på klienten til et nummer tilbake, overfører jeg alle bytene som utgjør filen, lest fra inngangsstrø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 forskjellen er at den leser data fra kontakten i stedet for fra tastaturet. Koden er i depotet, akkurat som selector.
I dette tilfellet blir initialiseringen plassert i en egen kodeblokk, fordi i denne implementeringen, etter at overføringen er fullført, frigjøres ressurser og okkuperes igjen - igjen for å gi beskyttelse mot minnelekkasjer.

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

Å oppsummere:

Vi har nettopp skrevet vår egen variant av en enkel dataoverføringsprotokoll og funnet ut hvordan den skal fungere. I prinsippet oppdaget jeg ikke Amerika her og skrev ikke mye nytt, men det var ingen lignende artikler om Habré, og som en del av å skrive en serie artikler om cmd-verktøy var det umulig å ikke berøre det.

referanser:

Kildekodelager
Kort om TFTP
Det samme, men på russisk

Kilde: www.habr.com

Legg til en kommentar