TP : Mini-Chat en Python
Contexte
Vous êtes développeur chez une startup qui souhaite créer une alternative légère à Discord. Votre mission : implémenter un système de messagerie instantanée en utilisant les sockets Python pour comprendre concrètement le fonctionnement des protocoles TCP et UDP.
Ce TP vous permettra de manipuler les concepts vus en cours : adresses IP, ports, protocoles de transport, et modèle client-serveur.
Objectifs
- Comprendre le fonctionnement des sockets réseau
- Implémenter une communication client-serveur en TCP
- Comparer TCP et UDP en pratique
- Manipuler les adresses IP et les ports
Prérequis
Le module socket est inclus dans Python, aucune installation nécessaire.
Partie 1 : Découverte des sockets
1.1. Qu'est-ce qu'un socket ?
Un socket est un point de terminaison pour envoyer ou recevoir des données sur un réseau. C'est comme une prise électrique : vous branchez votre programme dessus pour communiquer.
Un socket est identifié par : - Une adresse IP (quelle machine) - Un port (quelle application sur cette machine)
1.2. Récupérer son adresse IP
Créer un programme qui affiche votre nom d'hôte et votre adresse IP locale.
import socket
def afficher_info_reseau():
"""Affiche les informations réseau de la machine."""
# À compléter
pass
# Test
afficher_info_reseau()
Fonctions utiles :
- socket.gethostname() : retourne le nom de la machine
- socket.gethostbyname(nom) : retourne l'adresse IP associée au nom
Sortie attendue :
1.3. Résolution DNS
Créer une fonction qui résout un nom de domaine en adresse IP (comme nslookup).
def resoudre_dns(domaine):
"""
Résout un nom de domaine en adresse IP.
:param domaine: (str) Le nom de domaine à résoudre
:return: (str) L'adresse IP correspondante
"""
# À compléter
pass
# Tests
print(resoudre_dns("www.google.fr"))
print(resoudre_dns("www.wikipedia.org"))
Partie 2 : Serveur TCP simple
2.1. Création du serveur
Un serveur TCP fonctionne en plusieurs étapes :
1. Créer un socket
2. Lier le socket à une adresse et un port (bind)
3. Écouter les connexions (listen)
4. Accepter une connexion (accept)
5. Recevoir/Envoyer des données
6. Fermer la connexion
import socket
def creer_serveur_tcp(port):
"""
Crée un serveur TCP qui attend une connexion et affiche le message reçu.
:param port: (int) Le port d'écoute
"""
# Créer le socket TCP
serveur = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Permettre la réutilisation de l'adresse
serveur.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# Lier à toutes les interfaces sur le port spécifié
serveur.bind(('', port))
# Écouter (max 1 connexion en attente)
serveur.listen(1)
print(f"Serveur en écoute sur le port {port}...")
# À compléter : accepter une connexion et recevoir un message
pass
# Lancer le serveur sur le port 12345
creer_serveur_tcp(12345)
Fonctions à utiliser :
- connexion, adresse = serveur.accept() : accepte une connexion
- donnees = connexion.recv(1024) : reçoit jusqu'à 1024 octets
- donnees.decode('utf-8') : convertit les octets en texte
- connexion.close() : ferme la connexion
2.2. Création du client
import socket
def creer_client_tcp(adresse_serveur, port, message):
"""
Crée un client TCP qui envoie un message au serveur.
:param adresse_serveur: (str) L'adresse IP du serveur
:param port: (int) Le port du serveur
:param message: (str) Le message à envoyer
"""
# Créer le socket TCP
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# À compléter : se connecter et envoyer le message
pass
# Envoyer un message au serveur local
creer_client_tcp('127.0.0.1', 12345, "Bonjour serveur !")
Fonctions à utiliser :
- client.connect((adresse, port)) : se connecte au serveur
- client.send(message.encode('utf-8')) : envoie le message
2.3. Test
- Ouvrir deux terminaux
- Dans le premier, lancer le serveur
- Dans le second, lancer le client
- Observer le message reçu par le serveur
Partie 3 : Chat bidirectionnel
3.1. Serveur de chat
Modifier le serveur pour qu'il puisse : - Recevoir un message du client - Répondre au client - Continuer la conversation jusqu'à ce que le client envoie "quit"
def serveur_chat(port):
"""
Serveur de chat interactif.
"""
serveur = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
serveur.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
serveur.bind(('', port))
serveur.listen(1)
print(f"Serveur de chat en attente sur le port {port}...")
connexion, adresse = serveur.accept()
print(f"Connexion de {adresse[0]}:{adresse[1]}")
# À compléter : boucle de conversation
# - Recevoir un message
# - Si le message est "quit", terminer
# - Sinon, demander une réponse et l'envoyer
pass
3.2. Client de chat
def client_chat(adresse_serveur, port):
"""
Client de chat interactif.
"""
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect((adresse_serveur, port))
print(f"Connecté au serveur {adresse_serveur}:{port}")
print("Tapez 'quit' pour quitter.\n")
# À compléter : boucle de conversation
# - Demander un message à l'utilisateur
# - Envoyer le message
# - Si "quit", terminer
# - Sinon, attendre et afficher la réponse
pass
Partie 4 : Comparaison TCP vs UDP
4.1. Serveur UDP
UDP est "connectionless" : pas besoin d'établir une connexion avant d'envoyer des données.
def serveur_udp(port):
"""
Serveur UDP simple.
"""
serveur = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # DGRAM = UDP
serveur.bind(('', port))
print(f"Serveur UDP en écoute sur le port {port}...")
while True:
# recvfrom retourne les données ET l'adresse de l'expéditeur
donnees, adresse = serveur.recvfrom(1024)
message = donnees.decode('utf-8')
print(f"[{adresse[0]}:{adresse[1]}] {message}")
if message.lower() == "quit":
break
serveur.close()
4.2. Client UDP
def client_udp(adresse_serveur, port):
"""
Client UDP simple.
"""
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
while True:
message = input("Message (quit pour quitter) : ")
# sendto envoie directement sans connexion préalable
client.sendto(message.encode('utf-8'), (adresse_serveur, port))
if message.lower() == "quit":
break
client.close()
4.3. Analyse comparative
Compléter le tableau suivant après avoir testé les deux versions :
| Critère | TCP | UDP |
|---|---|---|
| Connexion préalable | ... | ... |
| Fonction d'envoi | ... | ... |
| Garantie de livraison | ... | ... |
| Ordre des messages | ... | ... |
| Utilisation typique | ... | ... |
Partie 5 : Mini-serveur multi-clients (Bonus)
5.1. Problème
Le serveur actuel ne peut gérer qu'un seul client. Pour accepter plusieurs clients simultanément, il faut utiliser les threads.
import socket
import threading
clients = []
def gerer_client(connexion, adresse):
"""Gère la communication avec un client."""
print(f"[+] Nouveau client : {adresse}")
clients.append(connexion)
while True:
try:
message = connexion.recv(1024).decode('utf-8')
if not message or message.lower() == "quit":
break
# Diffuser le message à tous les clients
for client in clients:
if client != connexion:
client.send(f"[{adresse[0]}] {message}".encode('utf-8'))
except:
break
clients.remove(connexion)
connexion.close()
print(f"[-] Client déconnecté : {adresse}")
def serveur_multi_clients(port):
"""Serveur acceptant plusieurs clients."""
serveur = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
serveur.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
serveur.bind(('', port))
serveur.listen(5)
print(f"Serveur multi-clients sur le port {port}")
while True:
connexion, adresse = serveur.accept()
thread = threading.Thread(target=gerer_client, args=(connexion, adresse))
thread.start()
5.2. Client amélioré
Créer un client qui peut recevoir des messages tout en permettant d'en envoyer (utiliser un thread pour la réception).
Partie 6 : Questions de synthèse
-
Expliquer la différence entre
SOCK_STREAM(TCP) etSOCK_DGRAM(UDP). -
Pourquoi le serveur utilise-t-il
bind()alors que le client utiliseconnect()? -
Que se passe-t-il si deux programmes essaient d'utiliser le même port simultanément ?
-
Pourquoi utilise-t-on
127.0.0.1(localhost) pour les tests ? -
Dans quel cas préféreriez-vous UDP à TCP pour une application de chat ?
Barème indicatif
| Partie | Points |
|---|---|
| Partie 1 : Découverte | 3 |
| Partie 2 : Serveur/Client TCP | 5 |
| Partie 3 : Chat bidirectionnel | 4 |
| Partie 4 : Comparaison TCP/UDP | 4 |
| Partie 5 : Multi-clients (Bonus) | 2 |
| Partie 6 : Questions | 2 |
| Total | 20 |
Auteur : Florian Mathieu
Licence CC BY NC
Ce cours est mis à disposition selon les termes de la Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International.