Skriuwsoftware mei de funksjonaliteit fan Windows-client-server-helpprogramma's, diel 02

Trochgean fan 'e oanhâldende searje artikels wijd oan oanpaste ymplemintaasjes fan Windows-konsole-hulpprogramma's, kinne wy ​​​​net oars as TFTP (Trivial File Transfer Protocol) oanreitsje - in ienfâldich protokol foar bestânferfier.

Lykas de lêste kear, litte wy koart gean oer de teory, sjoch de koade dy't funksjonaliteit ymplementearret fergelykber mei de fereaske, en analysearje it. Mear details - ûnder de besuniging

Ik sil gjin referinsjeynformaasje kopiearje-plakke, keplingen wêrnei't tradisjoneel oan 'e ein fan it artikel te finen binne, ik sil allinich sizze dat yn' e kearn TFTP in ferienfâldige fariaasje is fan it FTP-protokol, wêryn de tagongskontrôle ynstelling hat is fuortsmiten, en yn feite is d'r hjir neat, útsein kommando's foar it ûntfangen en oerdragen fan in bestân. Om ús ymplemintaasje lykwols in bytsje eleganter te meitsjen en oanpast oan 'e hjoeddeistige prinsipes fan it skriuwen fan koade, is de syntaksis wat feroare - dit feroaret de prinsipes fan operaasje net, mar de ynterface, IMHO, wurdt in bytsje logysker en kombinearret de positive aspekten fan FTP en TFTP.

Yn it bysûnder freget de client by lansearring it IP-adres fan de tsjinner en de poarte wêrop oanpaste TFTP iepen is (fanwege ynkompatibiliteit mei it standertprotokol, achte ik it passend om de brûker de mooglikheid te litten om in poarte te selektearjen), wêrnei't in ferbining optreedt, as gefolch wêrfan de kliïnt ien fan 'e kommando's kin stjoere - krije of sette, om in bestân te ûntfangen of te stjoeren nei de tsjinner. Alle bestannen wurde ferstjoerd yn binêre modus om de logika te ferienfâldigjen.

Om it protokol út te fieren, brûkte ik tradisjoneel 4 klassen:

  • TFTPClient
  • TFTPServer
  • TFTPClientTester
  • TFTPServerTester

Fanwegen it feit dat testklassen allinich besteane foar it debuggen fan 'e wichtichste, sil ik se net analysearje, mar de koade sil yn' e repository wêze; in keppeling nei it kin fûn wurde oan 'e ein fan it artikel. No sjoch ik nei de haadklassen.

TFTPClient

De taak fan dizze klasse is om te ferbinen mei in tsjinner op ôfstân troch syn ip- en poartenûmer, in kommando te lêzen fan 'e ynfierstream (yn dit gefal it toetseboerd), it te parsearjen, it oer te bringen nei de tsjinner, en, ôfhinklik fan oft jo in bestân ferstjoere of ûntfange, oerdrage of krije.

De koade foar it starten fan de kliïnt om te ferbinen mei de tsjinner en wachtsje op in kommando fan 'e ynfierstream sjocht der sa út. In oantal globale fariabelen dy't hjir brûkt wurde wurde beskreaun bûten it artikel, yn 'e folsleine tekst fan it programma. Fanwegen har trivialiteit sit ik se net om it artikel net te oerladen.

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

Litte wy gean oer de metoaden neamd yn dit blok fan koade:

Hjir wurdt it bestân ferstjoerd - mei in scanner presintearje wy de ynhâld fan 'e bestân as in array fan bytes, dy't wy ien foar ien skriuwe nei de socket, wêrnei't wy it slute en opnij iepenje (net de meast foar de hân lizzende oplossing, mar it garandearret de frijlitting fan boarnen), wêrnei't wy in berjocht werjaan oer suksesfolle oerdracht.

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

Dit koadefragmint beskriuwt it opheljen fan gegevens fan de tsjinner. Alles is wer triviaal, allinich it earste blok koade is fan belang. Om krekt te begripen hoefolle bytes moatte wurde lêzen fan 'e socket, moatte jo witte hoefolle it oerdroegen bestân waacht. De triemgrutte op 'e tsjinner wurdt fertsjintwurdige as in lang hiel getal, dus wurde hjir 4 bytes akseptearre, dy't dêrnei wurde omset yn ien getal. Dit is net in heul Java-oanpak, it is earder ferlykber foar SI, mar it lost syn probleem op.

Dan is alles triviaal - wy krije in bekend oantal bytes út 'e socket en skriuwe se nei in bestân, wêrnei't wy in súksesberjocht werjaan.

   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 in oar kommando dan get or put yn it kliïntfinster ynfierd is, sil de showErrorMessage-funksje oanroppen wurde, wat oanjout dat de ynfier ferkeard wie. Fanwegen trivialiteit sil ik it net oanhelje. Wat nijsgjirriger is de funksje fan it ûntfangen en splitsen fan de ynfierstring. Wy passe de scanner deryn troch, wêrfan wy ferwachtsje in rigel te ûntfangen skieden troch twa spaasjes en mei it kommando, boarneadres en bestimmingsadres.

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

In kommando ferstjoere - stjoert it fan 'e scanner ynfierde kommando oer nei de socket en twingt it te ferstjoeren

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

In selector is in funksje dy't de aksjes fan it programma bepaalt ôfhinklik fan 'e ynfierde tekenrige. Alles hjir is net hiel moai en de trúk brûkt is net de bêste ien mei twongen útgong bûten it koade blok, mar de wichtichste reden hjirfoar is de ôfwêzigens yn Java fan guon dingen, lykas ôffurdigen yn C #, funksje pointers fan C ++, of at op syn minst de skriklike en ferskriklike goto, wêrtroch jo dit prachtich kinne útfiere. As jo ​​​​witte hoe't jo de koade in bytsje eleganter meitsje, ferwolkom ik krityk yn 'e kommentaren. It liket my dat hjir in String-delegate-wurdboek nedich is, mar d'r is gjin delegate ...

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

TFTPServer

De funksjonaliteit fan 'e tsjinner ferskilt fan' e funksjonaliteit fan 'e kliïnt, yn' t algemien, allinich yn dat kommando's net fan it toetseboerd komme, mar fan 'e socket. Guon fan 'e metoaden binne oer it generaal itselde, dus ik sil se net oanhelje, ik sil allinich de ferskillen oanreitsje.

Om te begjinnen wurdt de runmetoade brûkt, dy't in poarte as ynfier ûntfangt en de ynfiergegevens fan 'e socket ferwurket yn in ivige lus.

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

De put-metoade, dy't de writeToFileFromSocket-metoade omfettet dy't in skriuwstream nei in bestân iepenet en alle ynfierbytes fan 'e socket skriuwt, toant in berjocht dat de suksesfolle foltôging fan 'e oerdracht oanjout as it skriuwen foltôge 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());
        }
    }

De get-metoade hellet it serverbestân op. Lykas al neamd yn 'e seksje oan' e kliïntkant fan it programma, om in bestân mei súkses oer te bringen, moatte jo de grutte witte, opslein yn in lang heul getal, dus ik splitst it yn in array fan 4 bytes, oerdrage se byte-by-byte nei de socket, en dan, hawwen ûntfongen en gearstald se op 'e kliïnt yn in nûmer werom, Ik oerdrage alle bytes dy't meitsje de triem, lêzen út de ynfier stream út de triem.


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

De getAndParseInput-metoade is itselde as yn 'e kliïnt, it ienige ferskil is dat it gegevens lêst fan' e socket ynstee fan fan it toetseboerd. De koade is yn it repository, krekt as selector.
Yn dit gefal, de inisjalisaasje wurdt pleatst yn in apart blok fan koade, omdat binnen dizze ymplemintaasje, neidat de oerdracht is foltôge, boarnen wurde frijjûn en opnij beset - wer te foarsjen beskerming tsjin ûnthâld lekken.

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

Gearfetsje:

Wy hawwe krekt ús eigen fariaasje skreaun op in ienfâldich gegevensferfierprotokol en útfûn hoe't it moat wurkje. Yn prinsipe haw ik Amearika hjir net ûntdutsen en net folle nije dingen skreaun, mar der wiene gjin ferlykbere artikels oer Habré, en as ûnderdiel fan it skriuwen fan in searje artikels oer cmd-nutsbedriuwen wie it ûnmooglik om it net oan te reitsjen.

Ferwizings:

Boarnekoade repository
Koart oer TFTP
Itselde ding, mar yn it Russysk

Boarne: www.habr.com

Add a comment