Logiciel d'écriture avec la fonctionnalité des utilitaires client-serveur Windows, partie 02

Poursuivant la série d'articles sur les implémentations personnalisées des utilitaires de console dans Windows, TFTP (Trivial File Transfer Protocol) est un simple protocole de transfert de fichiers.

Comme la dernière fois, nous passons brièvement en revue la théorie, voyons un code qui implémente une fonctionnalité similaire à celle requise, et l'analysons. En savoir plus - sous la coupe

Je ne copierai pas les informations de référence, dont les liens se trouvent traditionnellement à la fin de l'article, je dirai simplement que TFTP est essentiellement une variante simplifiée du protocole FTP dans lequel le paramètre de contrôle d'accès est supprimé, et en fait il n'y a rien ici, sauf les commandes pour recevoir et transférer le fichier . Cependant, afin de rendre notre mise en œuvre un peu plus élégante et adaptée aux principes actuels de l'écriture de code, la syntaxe est légèrement modifiée - elle ne change pas les principes de travail, mais l'interface, à mon humble avis, devient un peu plus logique et combine les aspects positifs de FTP et TFTP.

En particulier, au démarrage, le client demande l'adresse IP du serveur et le port sur lequel TFTP personnalisé est ouvert (en raison d'une incompatibilité avec le protocole standard, j'ai jugé approprié de laisser l'option de sélectionner le port à l'utilisateur), après quoi une connexion se produit, à la suite de laquelle le client peut envoyer l'une des commandes - get or put, pour recevoir ou envoyer un fichier au serveur. Tous les fichiers sont envoyés en mode binaire - afin de simplifier la logique.

Pour la mise en œuvre du protocole, j'ai traditionnellement utilisé 4 classes:

  • TFTPClient
  • TFTPServer
  • TFTPClientTester
  • TFTPServerTester

Étant donné que les classes de test existent uniquement pour déboguer les principales, je ne les analyserai pas, mais le code sera dans le référentiel, un lien vers celui-ci peut être trouvé à la fin de l'article. Et maintenant je vais comprendre les classes principales.

TFTPClient


La tâche de cette classe est de se connecter au serveur distant par son adresse IP et son numéro de port, de lire une commande du flux d'entrée (dans ce cas, le clavier), de l'analyser, de la transférer sur le serveur, et selon que vous souhaitez transférer ou recevoir le fichier, le transférer ou recevoir.

Le code de lancement client pour se connecter au serveur et attendre une commande du flux d'entrée ressemble à ceci. Un certain nombre de variables globales utilisées ici sont décrites en dehors de l'article, dans le texte intégral du programme. En raison de leur trivialité, je ne cite pas afin de ne pas surcharger l'article.

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

Passons en revue les méthodes appelées dans ce bloc de code:

Ici, le fichier est envoyé - en utilisant le scanner, nous présentons le contenu du fichier sous forme de tableau d'octets, que nous écrivons dans le socket un par un, puis le fermons et le rouvrons (pas la solution la plus évidente, mais il garantit la libération des ressources), après quoi nous affichons un message sur le succès transmission.

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

Ce fragment de code décrit la réception de données du serveur. Tout est à nouveau banal, seul le premier bloc de code est intéressant. Afin de comprendre exactement combien d'octets vous devez lire à partir du socket, vous devez savoir combien pèse le fichier transféré. La taille du fichier sur le serveur semble être un entier long, donc 4 octets sont acceptés ici, qui sont ensuite convertis en un seul nombre. Ce n'est pas une approche très Java, c'est assez similaire pour SI, mais ça résout son problème.

Ensuite, tout est trivial - nous obtenons le nombre connu d'octets du socket et les écrivons dans un fichier, après quoi nous affichons un message de réussite.

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

Si une commande autre que get ou put a été entrée dans la fenêtre client, la fonction showErrorMessage sera appelée, indiquant l'inexactitude de l'entrée. En raison de la trivialité - je ne cite pas. Un peu plus intéressant est la fonction d'obtenir et de diviser la chaîne d'entrée. Nous lui passons un scanner, à partir duquel nous nous attendons à recevoir une ligne séparée par deux espaces et contenant une commande, une adresse source et une adresse de destination.

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

Envoi de commande - envoyer la commande entrée du scanner vers le socket et la forcer à être envoyée

  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 sélecteur est une fonction qui détermine les actions d'un programme en fonction de la chaîne d'entrée. Tout n'est pas très beau ici et l'astuce pas si bonne pour forcer le bloc de code à être utilisé est utilisée, mais la principale raison en est l'absence en Java de certaines choses, comme les délégués en C #, les pointeurs vers une fonction en C ++, ou au moins un goto effrayant et terrible, qui laissez-vous le réaliser magnifiquement. Si vous savez comment rendre le code un peu plus élégant, j'attends les critiques dans les commentaires. Il me semble qu'un dictionnaire String-delegate est nécessaire ici, mais il n'y a pas de délégué ...

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

TFTPServer


La fonctionnalité du serveur diffère de la fonctionnalité du client dans l'ensemble uniquement en ce que les commandes qui lui sont transmises ne proviennent pas du clavier, mais de la prise. Certaines méthodes coïncident, donc je ne les donnerai pas; je ne mentionnerai que les différences.

Pour commencer ici, la méthode d'exécution est utilisée, qui reçoit un port d'entrée et traite les données d'entrée de la socket dans un cycle éternel.

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

La méthode put, qui est un wrapper de la méthode writeToFileFromSocket, qui ouvre le flux d'écriture dans un fichier et écrit tous les octets d'entrée à partir du socket, une fois l'enregistrement terminé, affiche un message sur la réussite du transfert.

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

La méthode get fournit un fichier serveur. Comme déjà mentionné dans la section sur le côté client du programme, pour réussir le transfert d'un fichier, vous devez connaître sa taille, stockée dans un entier long, donc je le divise en un tableau de 4 octets, les transfère vers l'octet de socket, puis, après les avoir reçus et collectés sur le client retour au numéro, je transfère tous les octets qui composent le fichier, lus à partir du flux d'entrée du fichier.

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

La méthode getAndParseInput est la même que dans le client, la seule différence étant qu'elle lit les données depuis le socket et non depuis le clavier. Le code dans le référentiel, comme sélecteur.
Dans ce cas, l'initialisation est effectuée dans un bloc de code séparé, car dans le cadre de cette implémentation, une fois le transfert terminé, les ressources sont à nouveau libérées et réoccupées, toujours dans le but d'assurer une protection contre les fuites de mémoire.

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

En résumé:

Nous venons d'écrire notre variante sur un protocole de transfert de données simple et avons compris comment cela devrait fonctionner. En principe, je n'ai pas découvert l'Amérique et je n'ai pas écrit grand-chose de nouveau, mais - il n'y avait pas d'articles similaires sur Habré, et dans le cadre de la rédaction d'une série d'articles sur les utilitaires cmd, il était impossible de ne pas le toucher.

Références:

Dépôt de code source
En bref sur TFTP
Même chose, mais en russe

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


All Articles