Fichier texte: [serveur-cle-valeur.sh] (Codage Latin-1)
#! /bin/bash # -*- coding: latin-1 -*- #------------------------------------------------------------------------------# # Ce script implémente un serveur clé/valeur. La table associative est # # réalisée de la manière suivante: pour chaque entrée (K,V) on créer un # # fichier K dans lequel on met V. # # # # Le script doit être invoqué avec les 3 arguments suivants: # # # # PORT le port sur lequel le serveur attend ses clients # # DIR le répertoire dans lequel on crée les fichiers contenant les # # valeurs de la table # # MAX le nombre maximum d'entrées qu'on veut permettre dans la table # #------------------------------------------------------------------------------# if [ $# -ne 3 ]; then echo "usage: $(basename $0) PORT DIR MAX" exit -1 fi PORT="$1" DIR=$(cd "$2" && pwd) MAX="$3" [ -d "$DIR" ] || exit -1 #------------------------------------------------------------------------------# # Pour réaliser le serveur, nous allons utiliser la fonctionalité "listen" de # # netcat: # # # # netcat -l -p PORT # # # # La commande ci-dessus écoute sur le PORT et attend qu'un client s'y # # connecte. Lorsque cela se produit, tout ce que netcat lit sur son stdin # # est envoyé au client, et tout ce que le client envoie à netcat est écrit # # sur le stdout. # # # # Pour pouvoir interagir avec le client, il faudrait pouvoir lire le stdout # # de netcat pour obtenir les requêtes du client, et pour chaque requête on # # voudrait envoyer une réponse sur le stdin de netcat pour que celui la # # transmette au client. # # # # On peut résoudre une partie de notre problème en utilisant un tube (pipe): # # # # interaction | netcat -l -p PORT # # # # où interaction serait une fonction qui écrirait des réponses sur son stdout. # # Malheureusement, on voudrait aussi que cette fonction reçoive sur son # # stdin les requêtes du client: c'est à dire ce qui est écrit sur le stdout # # de netcat. Il faudrait faire une sorte de boucle pour connecter le stdout # # du netcat sur la droite au stdin d'interaction sur la gauche. # # # # Ceci peut être réalisé grâce à la notion de "tube nommé" (named pipe ou # # fifo). L'idée est de créer un objet tube et de le rendre accessible par un # # fichier dans un répertoire. Un processus peut alors ouvrir ce fichier en # # lecture, un autre en écriture pour ainsi établir un tube entre eux deux. # # # # Un tube nommé est créé par la commande mkfifo (voir ci-après). Supposons # # que $FIFO soit le chemin d'un tube nommé, alors on peut l'utiliser pour # # rediriger la sortie de netcat dans l'entrée d'interaction: # # # # interaction < "$FIFO" | netcat -l -p PORT > "$FIFO" # # # # De cette manière on a créé 2 tubes: # # - un tube d'interaction dans netcat grâce à l'opérateur | # # - un tube de netcat dans interaction au travers de $FIFO # # # # Notez que lorsque "interaction" termine, le stdin de "netcat" est alors # # fermé, mais "netcat" ne clos pas pour autant la connexion internet. Pour # # forcer "netcat" à clore cette connexion dès que son stdin est fermé, il # # faut ajouter l'option -c: # # # # interaction < "$FIFO" | netcat -c -l -p PORT > "$FIFO" # # # #------------------------------------------------------------------------------# # choisissons un nom qui évite les collisions FIFO="/tmp/$USER-fifo-$$" # il faudra aussi détruire le tube quand le serveur termine pour éviter de # polluer /tmp. On utilisera pour cela une instruction trap pour être sur de # nettoyer même si le serveur est interrompu par un signal. function nettoyage() { rm -f "$FIFO"; } trap nettoyage EXIT # on crée le tube nommé [ -e "FIFO" ] || mkfifo "$FIFO" #------------------------------------------------------------------------------# # netcat -c -l -p PORT ne permet de traiter qu'une seule connexion car le # # processus termine après celle-ci. Si on veut pouvoir traiter d'autres # # connexion, il faudra réinvoquer netcat. On se servira donc d'une boucle. # # # # Notez que cette version d'accept-loop ne permet qu'une seule connexion # # client à la fois. Une version autorisant plusieurs connexion clients # # simultanément est possible, mais ATTENTION à la possibilité d'un client # # modifiant la table pendant qu'un autre est en train de la lire. Il faudrait # # ajouter une notion de "verrouillage" pour éviter ces problèmes. # #------------------------------------------------------------------------------# function accept-loop() { while true; do interaction < "$FIFO" | netcat -c -l -p "$PORT" > "$FIFO" done } #------------------------------------------------------------------------------# # La fonction interaction lit les commandes du client sur son stdin et envoie # # les réponses sur son stdout. Elle est implémentée d'une manière un peu # # orientée-objet. Si elle lit: # # # # CMD arg1 arg2 ... argn # # # # alors elle invoque la fonction: # # # # commande-CMD arg1 arg2 ... argn # # # # si elle existe; sinon elle envoie une réponse d'erreur. # #------------------------------------------------------------------------------# function interaction() { local cmd args while true; do read cmd args || exit -1 fun="commande-$cmd" if [ "$(type -t $fun)" = "function" ]; then $fun $args else commande-non-comprise $fun $args fi done } #------------------------------------------------------------------------------# # Les fonctions implémentant les différentes commandes. # #------------------------------------------------------------------------------# function commande-non-comprise () { ??? } function commande-QUIT() { ??? # on fait exit pour terminer le processus executant interaction. Ce # processus n'est pas celui du serveur, mais seulement celui qui exécute # le membre gauche du tube dans la fonction accept-loop ci-dessus. exit 0 } function commande-PUT() { ??? } function commande-GET() { ??? } function commande-DEL() { ??? } # OK, on peut commencer à accepter et traiter les connexions accept-loop