Auteurs :
- FIONONANA RANDRY Tino (N° 6 - TCO)
- RAFANOMEZANTSOA Holinirina Vahatriniaina (N° 10 - TCO)
- RAKOTOVAO Nantenaina Elvys (N° 18 - TCO)
Licence 3 Télécommunications - Université de l'Itasy
Afin de garantir la portabilité et la robustesse de notre serveur, les tests ont été effectués sur trois environnements distincts :
Environnement 1 : Station de travail (Fedora)
- OS : Fedora 43
- Compilateur : GCC 15.2.1
- Outil d'analyse : Valgrind 3.27.0
Environnement 2 : Station de travail (Ubuntu)
- OS : Ubuntu 24.04.1 LTS
- Compilateur : GCC 13.3.0
- Outil d'analyse : Valgrind 3.22.0
Environnement 3 : Machine Virtuelle (Debian)
- OS : Debian 12
- Compilateur : GCC 12.2.0
- Outil d'analyse : Valgrind 3.19.0
Serveur TCP minimaliste en mode itératif. Il traite un seul client à la fois. La socket est créée avec socket(), configurée avec SO_REUSEADDR, bind() sur le port 9999 et listen() avec un backlog de 10.
make
./tcp_server
$ echo "Bonjour" | nc 127.0.0.1 9999
[Connexion #1] Echo : Bonjour
Terminal 2 : nc 127.0.0.1 9999 (connecté mais silencieux)
Terminal 3 : echo "Hello" | nc 127.0.0.1 9999 (bloqué, pas de réponse)
Quand Terminal 2 fait Ctrl+C :
Terminal 3 reçoit : [Connexion #2] Echo : Hello
Le serveur a une seule boucle accept/read/write. Tant qu'il est bloqué sur read() du client 1, il ne revient pas à accept(). Le client 2 reste dans la file d'attente du noyau (backlog).
Serveur concurrent basé sur fork(). Chaque connexion cliente est prise en charge par un processus fils indépendant. Un gestionnaire SIGCHLD avec waitpid(-1, NULL, WNOHANG) évite les processus zombies. Le compteur de connexions actives est partagé via mmap MAP_SHARED | MAP_ANONYMOUS.
$ for i in $(seq 1 8); do
(echo "Client $i : bonjour" | nc -q 1 127.0.0.1 9999) &
done
wait
[Connexion #1] Echo : Client 2 : bonjour
[Connexion #1] Echo : Client 4 : bonjour
[Connexion #1] Echo : Client 1 : bonjour
[Connexion #1] Echo : Client 7 : bonjour
[Connexion #1] Echo : Client 3 : bonjour
[Connexion #1] Echo : Client 6 : bonjour
[Connexion #1] Echo : Client 5 : bonjour
[Connexion #1] Echo : Client 8 : bonjour
Tous les clients ont terminé
$ pstree -p $(pgrep tcp_server)
tcp_server(9094)
$ ps -C tcp_server -o rss=
1660 KB
$ ps aux | grep Z
Aucun zombie lié à tcp_server.
Le gestionnaire SIGCHLD nettoie correctement les fils.
Les 8 clients sont servis en parallèle dans le désordre, ce qui prouve le parallélisme. Le père reste en écoute. Les fils se terminent rapidement. La solution IPC choisie est mmap car partagée avant fork(), plus rapide qu'un fichier temporaire.
Serveur concurrent basé sur pthreads. Chaque connexion est gérée par un thread indépendant. Un mutex protège le compteur connexions_actives. Pool de MAX_THREADS=16. pthread_detach() libère automatiquement les ressources. Le descripteur connfd est passé via malloc() pour éviter toute race condition.
$ for i in $(seq 1 8); do
(echo "Client $i : bonjour" | nc -q 1 127.0.0.1 9999) &
done
wait
[Connexion #1] Echo : Client 2 : bonjour
[Connexion #2] Echo : Client 5 : bonjour
[Connexion #4] Echo : Client 1 : bonjour
[Connexion #6] Echo : Client 7 : bonjour
[Connexion #7] Echo : Client 4 : bonjour
[Connexion #3] Echo : Client 3 : bonjour
[Connexion #5] Echo : Client 6 : bonjour
[Connexion #8] Echo : Client 8 : bonjour
$ cat /proc/$(pgrep tcp_server)/status | grep VmRSS
VmRSS: 1720 kB
Version fork() : 1660 kB
Version threads : 1720 kB
Les threads partagent la mémoire, les processus la dupliquent.
Sous forte charge, les threads sont plus efficaces.
17 clients servis sans message "Serveur saturé" car les connexions
sont trop courtes pour saturer le pool simultanément.
Compteur stable après 16 connexions simultanées.
Le mutex protège correctement le compteur global.
Serveur mono-thread qui surveille plusieurs clients simultanément avec select(). Un tableau clients[] de FD_SETSIZE descripteurs initialisé à -1. Timeout de 5 secondes. Aucun thread ni processus fils créé.
Terminal 2 : nc 127.0.0.1 9999 (silencieux, ne tape rien)
Terminal 3 : nc 127.0.0.1 9999
Bonjour
Echo : Bonjour
Descripteurs surveillés : 2
Le client silencieux ne bloque pas le client actif. select() surveille tous les descripteurs simultanément. Contrairement à la partie 1, un client inactif ne bloque pas les autres.
- select() est limité à FD_SETSIZE=1024 descripteurs, poll() n'a pas cette limite.
- FD_SETSIZE=1024 est insuffisant en production pour des milliers de connexions simultanées.
- Pour 500 connexions poll() est préférable car son API est plus claire et le fd_set n'est pas à reconstruire à chaque appel.
- Pour 10000+ connexions epoll (Linux) est recommandé avec O(1).
Le serveur est transformé en daemon UNIX complet. Double fork + setsid() détachent le serveur de tout terminal. Logs via syslog dans /var/log/myserverd.log. Détection de double instance via /tmp/myserverd.pid.
- Premier fork : le père quitte, le fils continue
- setsid() : crée une nouvelle session sans terminal de contrôle
- Second fork : empêche toute réacquisition de terminal
- chdir("/") : évite de bloquer un système de fichiers monté
- umask(0) : contrôle total sur les permissions
- Redirection stdin/stdout/stderr vers /dev/null
$ pgrep tcp_server
6190
(fermeture du terminal)
$ pgrep tcp_server
6190 (même PID, daemon toujours actif)
$ ./tcp_server
Log : myserverd[6250]: Daemon déjà en cours (PID=6190)
$ echo "Bonjour daemon" | nc 127.0.0.1 9999
[Connexion #1] Echo : Bonjour daemon
Logs dans /var/log/myserverd.log :
myserverd[6190]: Daemon démarré sur le port 9999
myserverd[6190]: Connexion acceptée de 127.0.0.1:54630
myserverd[6304]: Client traité (fd=5)
myserverd[6250]: Daemon déjà en cours (PID=6190)
LOG_INFO : connexions normales
LOG_WARNING : erreurs récupérables (accept échoué)
LOG_ERR : erreurs fatales (socket, bind, fork échoués)
Ligne ajoutée dans /etc/rsyslog.conf :
daemon.* /var/log/myserverd.log
$ valgrind --leak-check=full --track-origins=yes ./tcp_server
definitely lost: 0 bytes
possibly lost: 816 bytes (interne glibc pthread_create, pas notre code)
Notre code ne présente aucune fuite mémoire.
$ ./tcp_server (Ctrl+C)
$ ./tcp_server
Serveur threads démarré sur le port 9999
Pas de "Address already in use"
Ctrl+C arrête proprement le serveur sur accept()