Page prec.      Fin page      Page suiv.

Architectures générales des serveurs

    L'architecture d'un serveur dépend de nombreux paramètres qui sont eux-mêmes fonction de la nature du ou des services qu'il doit rendre : service unique ou multiple, constitué d'une ou de plusieurs requêtes, volume de données  transmettre réduit ou important, etc. Les contraintes que doit respecter un serveur sont :     Plusieurs schémas différents peuvent être envisagés. Un serveur itératif est un serveur qui traite les requêtes l'une après l'autre. Un serveur concurrent est au contraire un serveur qui traite plusieurs requêtes simultanément. En général un serveur itératif n'est suffisant que pour des applications très simples. Le temps de réponse du serveur à une requête est le temps mis par le serveur pour extraire, une requête, la traiter et renvoyer au client la réponse. Le temps de réponse observé par un client est le temps qui s'écoule entre l'émission d'une requête et la réception de la réponse du serveur. Lorsque le serveur est itératif, chaque requête est placée en file d'attente et traitée séquentiellement. Le temps de réponse observé par le client est d'autant plus long que la file d'attente est longue. La solution itérative ne peut être choisie que si le temps d'attente d'un client est inférieur à une valeur fixée, pour une file d'attente de taille moyenne. Elle est en général mise en œuvre avec un protocole d'accès non connecté qui se prête plus facilement à l'implémentation. Un serveur concurrent est nécessaire dans les cas suivants :

Serveur itératif non connecté

    Après avoir créé la socket (socket()) et l'avoir liée à un port défini (bind()), le serveur attend un datagramme sur la socket, élabore la réponse et la renvoie au client dont il a reçu l'adresse en même temps que le datagramme, puis retourne lire une nouvelle requête. Il traite les requêtes l'une après l'autre mais sert en pseudo-parallélisme différents clients

Serveur itératif connecté

Après avoir créé la socket (socket()) et l'avoir liée à un port défini (bind()), le serveur attend une demande de connexion au moyen de l'appel à la fonction accept(). Lorsqu'il l'a reçu, il échange avec le client, attendant toutes ses requêtes et lui envoyant les réponses. Lorsque le client a terminé, le serveur ferme la socket de communication et se remet en attente de connexion par accept().

Serveur concurrent non connecté

Lorsque le serveur reçoit une requête, il crée un processus fils (un esclave), lui communique l'adresse du client, la socket par laquelle l'esclave doit envoyer la réponse et retourne en attente d'une nouvelle requête. L'esclave pour sa part prépare la réponse, la renvoie et se termine. La création d'un nouveau processus pour répondre à une requête est en général une opération lourde, longue et consommatrice de ressources du système. Cette architecture doit donc être réservée à des cas très particuliers où le service se réduit à seule requête dont le traitement peut être très long par rapport à la création d'un esclave.

Serveur concurrent connecté

    Comme dans le cas précédent, chaque fois que le serveur reçoit une demande de connexion (par la fonction accept()), il crée un processus esclave (par appel à la fonction système fork()) auquel il transmet le socket descriptor de la socket de communication puis retourne attendre une nouvelle demande de connexion. Le processus esclave pour sa part peut entamer un échange avec le client puisque la communication est établie de bout en bout entre ces deux processus. Aucun autre ne peut interférer. La figure suivante illustre cette architecture.
    L'état de la table des descripteurs après exécution de la fonction système socket() est le suivant :

    La figure suivante illustre l'état de la table des descripteurs après exécution de la fonction système accept().

    La figure suivante illustre l'état de la table des descripteurs après exécution de la fonction système fork().

    L'état de la table des descripteurs après mise en place complète du processus esclave et fermeture des descripteurs inutiles est le suivant :

    Il y a deux possibilités lors de la création du processus esclave :

    Une dernière possibilité est offerte si le serveur est écrit dans un langage permettant le multitâche (ADA par exemple) ou si le système autorise l'utilisation de threads.

    Cette structure de serveur n'est justifiée que si l'on veut profiter des possibilités de temps partagé qu'offre le système UNIX : si un service est très long, le processus esclave épuisera son quantum de temps et le système le pénalisera.

Serveur concurrent connecté mono-processus

    Le serveur concurrent connecté multi-processus présenté ci-dessus présente plusieurs inconvénients majeurs : en premier lieu celui de surcharger le système d'exploitation et de consommer des ressources en nombre limité (le nombre de processus). En second lieu le passage d'un processus à l'autre se fait par commutation de contexte qui prend du temps. Si le traitement d'une requête ne fait pas appel à des E/S et s'il est assez court pour pouvoir être terminé pendant un quantum de temps, il est préférable de se limiter à un seul processus serveur et d'utiliser la fonction système select(), déjà étudiée avec les autres fonctions système consacrées aux fichiers. Grâce à cette fonction il est possible de mettre en place une attente généralisée sur plusieurs événements :  lecture, écriture, exception (OOB), ou donner un délai d'attente maximal. Le serveur peut offrir une socket de connexion, puis gérer simultanément plusieurs sockets de communication avec des clients différents. Il se présente sous la forme d'une boucle (infinie) qui attend, au moyen de la fonction système select(), un événement sur toutes les sockets, de connexion et/ou de communication. La sortie de cette fonction bloquante indique qu'au moins un événement est arrivé sur une des sockets. Les masques sont analysés et les événements sont traités, après quoi le serveur retourne à l'appel de la fonction select().

Serveur multi-protocoles

    De nombreux services standard sont offerts en mode connecté (TCP) ou non connecté (UDP). Afin d'éviter la multiplication des processus, dont la partie "service" serait identique, il est préférable de n'en faire qu'un seul qui offre les deux protocoles : une socket de connexion pour le protocole TCP et une socket UDP pour recevoir les datagrammes de demande de service. De nouveau on utilise la fonction select() qui permet d'attendre à la fois sur ces deux sockets et sur toutes les sockets de communication créées par les appels de la fonction accept().

Serveur multi-services

    Un serveur peut offrir plusieurs services simultanément. Il s'agit d'une généralisation du serveur ci-dessus : plusieurs sockets, liées à différents numéros de port, sont créées pour les différents services.

Page prec.      Début page      Page suiv.