serveur-cle-valeur-utf8.sh


Fichier texte: [serveur-cle-valeur-utf8.sh] (Codage UTF-8)

#! /bin/bash
# -*- coding: utf-8 -*-

#------------------------------------------------------------------------------#
# 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