Schreiben von Software mit der Funktionalität von Client-Server-Dienstprogrammen Windows, Teil 02

TFTP (Trivial File Transfer Protocol) ist ein einfaches Dateiübertragungsprotokoll, das die Artikelserie über benutzerdefinierte Implementierungen von Konsolendienstprogrammen in Windows fortsetzt.

Wie beim letzten Mal gehen wir kurz auf die Theorie ein, sehen uns einen Code an, der eine ähnliche Funktion wie die erforderliche implementiert, und analysieren ihn. Lesen Sie mehr - unter dem Schnitt

Ich werde die Referenzinformationen, deren Links traditionell am Ende des Artikels zu finden sind, nicht kopieren und einfügen. Ich möchte nur sagen, dass TFTP im Wesentlichen eine vereinfachte Variante des FTP-Protokolls ist, bei dem die Zugriffssteuerungseinstellung entfernt wird. Tatsächlich gibt es hier nichts außer den Befehlen zum Empfangen und Übertragen der Datei . Um unsere Implementierung jedoch etwas eleganter und an die aktuellen Prinzipien des Codeschreibens angepasst zu gestalten, wird die Syntax geringfügig geändert - sie ändert nicht die Arbeitsprinzipien, aber die Schnittstelle IMHO wird etwas logischer und kombiniert die positiven Aspekte von FTP und TFTP.

Insbesondere fragt der Client beim Start nach der IP-Adresse des Servers und dem Port, an dem benutzerdefiniertes TFTP geöffnet ist (aufgrund der Inkompatibilität mit dem Standardprotokoll hielt ich es für angebracht, die Option zur Auswahl des Ports dem Benutzer zu überlassen). Danach erfolgt eine Verbindung, über die der Client einen der Befehle senden kann. abrufen oder ablegen, um eine Datei zu empfangen oder an den Server zu senden. Alle Dateien werden im Binärmodus gesendet - um die Logik zu vereinfachen.

Für die Implementierung des Protokolls habe ich traditionell 4 Klassen verwendet:

  • TFTPClient
  • TFTPServer
  • TFTPClientTester
  • TFTPServerTester

Aufgrund der Tatsache, dass Testklassen nur zum Debuggen der Hauptklassen existieren, werde ich sie nicht analysieren, aber der Code befindet sich im Repository. Einen Link dazu finden Sie am Ende des Artikels. Und jetzt werde ich die Hauptklassen verstehen.

TFTPClient


Die Aufgabe dieser Klasse besteht darin, über ihre IP- und Portnummer eine Verbindung zum Remote-Server herzustellen, einen Befehl aus dem Eingabestream (in diesem Fall der Tastatur) zu lesen, ihn zu analysieren, auf den Server zu übertragen und je nachdem, ob Sie die Datei übertragen oder empfangen möchten, sie zu übertragen oder zu empfangen.

Der Client-Startcode zum Herstellen einer Verbindung zum Server und zum Warten auf einen Befehl aus dem Eingabestream sieht folgendermaßen aus. Eine Reihe von globalen Variablen, die hier verwendet werden, werden außerhalb des Artikels im Volltext des Programms beschrieben. Aufgrund ihrer Trivialität zitiere ich nicht, um den Artikel nicht zu überladen.

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

Lassen Sie uns die in diesem Codeblock aufgerufenen Methoden durchgehen:

Hier wird die Datei gesendet. Mit dem Scanner präsentieren wir den Inhalt der Datei als Array von Bytes, die wir nacheinander in den Socket schreiben, dann schließen und erneut öffnen (nicht die naheliegendste Lösung, aber die Freigabe von Ressourcen garantiert). Anschließend wird eine Meldung über den Erfolg angezeigt Übertragung.

 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("\nDone\n"); } catch (Exception e) { System.out.println(e.getMessage()); } } 

Dieses Codefragment beschreibt den Empfang von Daten vom Server. Alles ist wieder trivial, nur der erste Codeblock ist von Interesse. Um genau zu verstehen, wie viele Bytes Sie aus dem Socket lesen müssen, müssen Sie wissen, wie viel die übertragene Datei wiegt. Die Dateigröße auf dem Server scheint eine lange Ganzzahl zu sein, daher werden hier 4 Bytes akzeptiert, die anschließend in eine einzelne Zahl konvertiert werden. Dies ist kein sehr Java-Ansatz, er ist für SI ziemlich ähnlich, aber er löst sein Problem.

Dann ist alles trivial - wir holen die bekannte Anzahl von Bytes aus dem Socket und schreiben sie in eine Datei, wonach wir eine Erfolgsmeldung anzeigen.

  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("\nDONE\n"); } catch (Exception e){ System.out.println(e.getMessage()); } } 

Wenn ein anderer Befehl als get oder put in das Client-Fenster eingegeben wurde, wird die Funktion showErrorMessage aufgerufen, die die Unrichtigkeit der Eingabe anzeigt. Aus Trivialität zitiere ich nicht. Etwas interessanter ist die Funktion zum Abrufen und Teilen der Eingabezeichenfolge. Wir übergeben ihm einen Scanner, von dem wir eine durch zwei Leerzeichen getrennte Zeile erwarten, die einen Befehl, eine Quelladresse und eine Zieladresse enthält.

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

Befehl senden - Senden des vom Scanner eingegebenen Befehls an den Socket und Erzwingen des Sendens

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

Ein Selektor ist eine Funktion, die die Aktionen eines Programms abhängig von der Eingabezeichenfolge bestimmt. Hier ist nicht alles sehr schön und der nicht so gute Trick wird verwendet, um ihn zu zwingen, über den Codeblock hinauszugehen, aber der Hauptgrund dafür ist das Fehlen einiger Dinge in Java, wie Delegierte in C #, Zeiger auf eine Funktion aus C ++ oder zumindest beängstigend und schrecklich lass es dich schön realisieren. Wenn Sie wissen, wie Sie den Code etwas eleganter gestalten können, warte ich in den Kommentaren auf Kritik. Es scheint mir, dass hier ein String-Delegate-Wörterbuch benötigt wird, aber es gibt keinen Delegaten ...

  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 Funktionalität des Servers unterscheidet sich von der Funktionalität des Clients im Großen und Ganzen nur dadurch, dass die Befehle dazu nicht von der Tastatur, sondern vom Socket stammen. Einige der Methoden stimmen überein, daher werde ich sie nicht angeben. Ich werde nur die Unterschiede erwähnen.

Zu Beginn wird hier die run-Methode verwendet, die einen Port für die Eingabe empfängt und Eingabedaten vom Socket in einem ewigen Zyklus verarbeitet.

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

Die put-Methode, ein Wrapper der writeToFileFromSocket-Methode, die den Schreibstrom in eine Datei öffnet und nach Abschluss der Aufzeichnung alle Eingangsbytes aus dem Socket schreibt, zeigt eine Meldung über den erfolgreichen Abschluss der Übertragung an.

  private void put(String source, String dest){ writeToFileFromSocket(); System.out.print("\nDone\n"); }; 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-Methode stellt eine Serverdatei bereit. Wie bereits im Abschnitt auf der Clientseite des Programms erwähnt, müssen Sie zum erfolgreichen Übertragen einer Datei ihre Größe kennen, die in einer langen Ganzzahl gespeichert ist. Daher habe ich sie in ein Array von 4 Bytes aufgeteilt, sie auf das Socket-Byte übertragen und sie dann nach dem Empfang und Sammeln auf dem Client übertragen Zurück zur Nummer, ich übertrage alle Bytes, aus denen die Datei besteht, aus dem Eingabestream aus der Datei.

 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 Methode getAndParseInput ist dieselbe wie im Client. Der einzige Unterschied besteht darin, dass Daten vom Socket und nicht von der Tastatur gelesen werden. Der Code im Repository, wie Selektor.
In diesem Fall erfolgt die Initialisierung in einem separaten Codeblock, weil Im Rahmen dieser Implementierung werden nach Abschluss der Übertragung die Ressourcen freigegeben und wieder belegt, um erneut vor Speicherlecks zu schützen.

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

Zusammenfassend:

Wir haben gerade unsere Variante eines einfachen Datenübertragungsprotokolls geschrieben und herausgefunden, wie es funktionieren soll. Im Prinzip habe ich Amerika nicht entdeckt und nicht viel Neues geschrieben, aber - es gab keine ähnlichen Artikel über Habré, und als Teil des Schreibens einer Reihe von Artikeln über cmd-Dienstprogramme war es unmöglich, ihn nicht zu berühren.

Referenzen:

Quellcode-Repository
Kurz über TFTP
Das Gleiche, aber auf Russisch

Source: https://habr.com/ru/post/de461083/


All Articles