Scrittura di software cù e funziunalità di l'utilità client-server Windows, parte 02

Continuendu a serie di articuli in corso dedicati à l'implementazioni persunalizate di l'utilità di a cunsola Windows, ùn pudemu aiutà ma toccu TFTP (Protocolu di Trasferimentu di File Trivial) - un protokollu simplice di trasferimentu di file.

Cum'è l'ultima volta, andemu brevemente nantu à a teoria, vede u codice chì implementa funziunalità simili à quellu necessariu, è analizà. More details - sottu u cut

Ùn aghju micca copià-incollà l'infurmazioni di riferimentu, i ligami à i quali ponu esse tradizionalmente truvati à a fine di l'articulu, diceraghju solu chì in u so core, TFTP hè una variazione simplificata di u protocolu FTP, in quale u paràmetru di cuntrollu di accessu. hè statu sguassatu, è in fattu ùn ci hè nunda quì, salvu cumandamenti per riceve è trasferisce un schedariu . In ogni casu, per fà a nostra implementazione un pocu più elegante è adattata à i principii attuali di u codice di scrittura, a sintassi hè stata ligeramente cambiata - questu ùn cambia micca i principii di funziunamentu, ma l'interfaccia, IMHO, diventa un pocu più logica è combina l'aspetti pusitivi di FTP è TFTP.

In particulare, quandu hè lanciatu, u cliente dumanda l'indirizzu IP di u servitore è u portu nantu à quale TFTP persunalizatu hè apertu (per via di l'incompatibilità cù u protokollu standard, aghju cunsideratu appruvatu di lascià l'utilizatori a capacità di selezziunà un portu), dopu chì un cunnessione accade, cum'è u risultatu di quale u cliente pò mandà unu di i cumandamenti - uttene o mette, per riceve o mandà un schedariu à u servitore. Tutti i schedari sò mandati in modu binariu per simplificà a logica.

Per implementà u protocolu, aghju tradizionalmente utilizatu 4 classi:

  • TFTPClient
  • TFTPServer
  • TFTPClientTester
  • TFTPServerTester

A causa di u fattu chì e classi di teste esistenu solu per debugging i principali, ùn l'analizà micca, ma u codice serà in u repositoriu un ligame pò esse truvatu à a fine di l'articulu. Avà fighjulà i classi principali.

TFTPClient

U compitu di sta classa hè di cunnette à un servitore remoto utilizendu u so ip è u numeru di portu, leghje un cumandamentu da u flussu di input (in questu casu, u teclatu), analizà, trasferisce à u servitore, è, secondu s'ellu avete. bisognu di mandà o riceve un schedariu, trasferimentu o uttene.

U codice per lancià u cliente per cunnette à u servitore è aspittà un cumandamentu da u flussu di input pare cusì. Un numeru di variabili glubale chì sò usati quì sò discrittu fora di l'articulu, in u testu sanu di u prugramma. Per via di a so trivialità, ùn li cite micca per ùn sopracarricà l'articulu.

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

Andemu nantu à i metudi chjamati in stu bloccu di codice:

Quì u schedariu hè mandatu - usendu un scanner, prisintà u cuntenutu di u schedariu cum'è un array di bytes, chì scrivimu unu à unu à u socket, dopu chì u chjudemu è apremu di novu (micca a suluzione più ovvia, ma guarantisci a liberazione di risorse), dopu chì avemu mostratu un missaghju nantu à u trasferimentu successu.

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

Stu frammentu di codice descrive a ricuperazione di dati da u servitore. Tuttu hè torna trivial, solu u primu bloccu di codice hè di interessu. Per capisce esattamente quantu byte deve esse lettu da u socket, avete bisognu di sapè quantu pesa u schedariu trasferitu. A dimensione di u schedariu nantu à u servitore hè rapprisintatu cum'è un integer longu, cusì 4 bytes sò accettati quì, chì sò successivamente cunvertiti in un numeru. Questu ùn hè micca un approcciu assai Java, hè piuttostu simile per SI, ma risolve u so prublema.

Allora tuttu hè trivial - ricevemu un numeru cunnisciutu di bytes da u socket è scrivite in un schedariu, dopu chì mostramu un missaghju di successu.

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

Se un cumandamentu altru ch'è ottene o mette hè statu intrutu in a finestra di u cliente, a funzione showErrorMessage serà chjamata, indicà chì l'input era incorrectu. Per via di trivialità, ùn l'aghju micca citatu. Un pocu più interessante hè a funzione di riceve è splitting the input string. Passemu u scanner in questu, da quale aspittemu di riceve una linea siparata da dui spazii è chì cuntene u cumandamentu, l'indirizzu fonte è l'indirizzu di destinazione.

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

Mandà un cumandamentu - trasmette u cumandamentu intrutu da u scanner à u socket è u furzà per esse mandatu

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

Un selettore hè una funzione chì determina l'azzioni di u prugramma secondu a stringa entrata. Tuttu quì ùn hè micca assai bellu è u truccu utilizatu ùn hè micca u megliu cù l'uscita forzata fora di u bloccu di codice, ma u mutivu principale per questu hè l'assenza in Java di alcune cose, cum'è delegati in C#, puntatori di funzione da C++, o à almenu u terribili è terribili goto, chì vi permettenu di implementà stu bellu. Se sapete cumu fà u codice un pocu più eleganti, aghju accoltu a critica in i cumenti. Mi pare chì un dizziunariu String-delegatu hè necessariu quì, ma ùn ci hè micca delegatu...

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

TFTPServer

A funziunalità di u servitore difiere da a funziunalità di u cliente, in generale, solu in chì i cumandamenti venenu micca da u teclatu, ma da u socket. Certi di i metudi sò generalmente listessi, per quessa, ùn aghju micca citatu, solu toccu à e differenzi.

Per principià, u metudu run hè utilizatu, chì riceve un portu cum'è input è processa i dati di input da u socket in un ciclu eternu.

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

U metudu put, chì impaccheghja u metudu writeToFileFromSocket chì apre un flussu di scrittura à un schedariu è scrive tutti i bytes di input da u socket, mostra un missaghju chì indica a cumpiimentu successu di u trasferimentu quandu a scrittura hè finita.

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

U metudu get recupera u schedariu di u servitore. Cum'è digià citatu in a rùbbrica nantu à u latu di u cliente di u prugramma, per trasfirià successu un schedariu, avete bisognu di cunnosce a so dimensione, almacenata in un integer longu, cusì l'aghju divisu in un array di 4 byte, trasfiriri byte-by-byte. à u socket, è dopu, dopu avè ricivutu è assemblatu nantu à u cliente in un numeru torna, trasfiriu tutti i bytes chì custituiscenu u schedariu, leghje da u flussu di input da u schedariu.


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

U metudu getAndParseInput hè u listessu cum'è in u cliente, l'unica diferenza hè chì leghje e dati da u socket invece di u teclatu. U codice hè in u repository, cum'è u selettore.
In questu casu, l'inizializazione hè posta in un bloccu separatu di codice, perchè in questa implementazione, dopu chì u trasferimentu hè finitu, i risorse sò liberati è rioccupati di novu - di novu per furnisce a prutezzione contra i perdite di memoria.

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

Per riassume:

Avemu ghjustu scrittu a nostra propria variazione nantu à un protocolu simplice di trasferimentu di dati è hà capitu cumu si deve travaglià. In principiu, ùn aghju micca scupertu l'America quì è ùn hà micca scrittu assai cose novi, ma ùn ci era micca articuli simili nantu à Habré, è cum'è parte di scrive una seria d'articuli nantu à l'utilità cmd ùn era micca impussibile di tuccà.

Referenze:

Repositoriu di codice fonte
In breve nantu à TFTP
U listessu, ma in Russian

Source: www.habr.com

Add a comment