# 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 ```python import socket ``` 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. ```python 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 :** ``` Nom de la machine : MonPC Adresse IP locale : 192.168.1.42 ``` ### 1.3. Résolution DNS Créer une fonction qui résout un nom de domaine en adresse IP (comme `nslookup`). ```python 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 ```python 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 ```python 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 1. Ouvrir deux terminaux 2. Dans le premier, lancer le serveur 3. Dans le second, lancer le client 4. 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" ```python 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 ```python 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. ```python 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 ```python 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**. ```python 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 1. **Expliquer** la différence entre `SOCK_STREAM` (TCP) et `SOCK_DGRAM` (UDP). 2. **Pourquoi** le serveur utilise-t-il `bind()` alors que le client utilise `connect()` ? 3. **Que se passe-t-il** si deux programmes essaient d'utiliser le même port simultanément ? 4. **Pourquoi** utilise-t-on `127.0.0.1` (localhost) pour les tests ? 5. **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 Licence Creative Commons
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.