""" Corrigé du TP Mini-Chat en Python """ import socket import threading # ============================================================================= # PARTIE 1 : Découverte des sockets # ============================================================================= def afficher_info_reseau(): """Affiche les informations réseau de la machine.""" nom_machine = socket.gethostname() adresse_ip = socket.gethostbyname(nom_machine) print(f"Nom de la machine : {nom_machine}") print(f"Adresse IP locale : {adresse_ip}") 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 """ try: adresse_ip = socket.gethostbyname(domaine) return adresse_ip except socket.gaierror: return f"Erreur : impossible de résoudre {domaine}" # ============================================================================= # PARTIE 2 : Serveur TCP simple # ============================================================================= 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}...") # Accepter une connexion connexion, adresse = serveur.accept() print(f"Connexion acceptée de {adresse[0]}:{adresse[1]}") # Recevoir le message donnees = connexion.recv(1024) message = donnees.decode('utf-8') print(f"Message reçu : {message}") # Fermer les connexions connexion.close() serveur.close() 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) # Se connecter au serveur client.connect((adresse_serveur, port)) print(f"Connecté à {adresse_serveur}:{port}") # Envoyer le message client.send(message.encode('utf-8')) print(f"Message envoyé : {message}") # Fermer la connexion client.close() # ============================================================================= # PARTIE 3 : Chat bidirectionnel # ============================================================================= 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]}") print("Chat démarré ! (le client envoie 'quit' pour terminer)\n") while True: # Recevoir un message du client donnees = connexion.recv(1024) if not donnees: break message_client = donnees.decode('utf-8') print(f"[Client] {message_client}") # Vérifier si le client veut quitter if message_client.lower() == "quit": print("Le client a quitté.") break # Envoyer une réponse reponse = input("[Serveur] Votre réponse : ") connexion.send(reponse.encode('utf-8')) connexion.close() serveur.close() 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") while True: # Envoyer un message message = input("[Vous] ") client.send(message.encode('utf-8')) # Quitter si demandé if message.lower() == "quit": print("Déconnexion...") break # Recevoir la réponse du serveur reponse = client.recv(1024).decode('utf-8') print(f"[Serveur] {reponse}") client.close() # ============================================================================= # PARTIE 4 : Comparaison TCP vs UDP # ============================================================================= 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() print("Serveur UDP arrêté.") def client_udp(adresse_serveur, port): """ Client UDP simple. """ client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) print(f"Client UDP prêt à envoyer vers {adresse_serveur}:{port}") print("Tapez 'quit' pour quitter.\n") while True: message = input("Message : ") # sendto envoie directement sans connexion préalable client.sendto(message.encode('utf-8'), (adresse_serveur, port)) if message.lower() == "quit": break client.close() print("Client UDP arrêté.") # ============================================================================= # PARTIE 5 : Mini-serveur multi-clients (Bonus) # ============================================================================= clients = [] clients_lock = threading.Lock() def gerer_client(connexion, adresse): """Gère la communication avec un client.""" print(f"[+] Nouveau client : {adresse}") with clients_lock: clients.append(connexion) # Envoyer un message de bienvenue connexion.send("Bienvenue sur le chat ! Tapez 'quit' pour quitter.\n".encode('utf-8')) while True: try: message = connexion.recv(1024).decode('utf-8') if not message or message.lower() == "quit": break print(f"[{adresse[0]}:{adresse[1]}] {message}") # Diffuser le message à tous les autres clients with clients_lock: for client in clients: if client != connexion: try: client.send(f"[{adresse[0]}:{adresse[1]}] {message}".encode('utf-8')) except: pass except: break with clients_lock: if connexion in clients: 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}") print("En attente de connexions... (Ctrl+C pour arrêter)\n") try: while True: connexion, adresse = serveur.accept() thread = threading.Thread(target=gerer_client, args=(connexion, adresse)) thread.daemon = True thread.start() except KeyboardInterrupt: print("\nArrêt du serveur...") finally: serveur.close() def client_multi(adresse_serveur, port, pseudo): """Client pour le serveur multi-clients avec réception asynchrone.""" client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client.connect((adresse_serveur, port)) print(f"Connecté au serveur {adresse_serveur}:{port} en tant que {pseudo}") def recevoir(): """Thread de réception des messages.""" while True: try: message = client.recv(1024).decode('utf-8') if message: print(f"\r{message}\n[{pseudo}] ", end='') except: break # Lancer le thread de réception thread_reception = threading.Thread(target=recevoir) thread_reception.daemon = True thread_reception.start() # Boucle d'envoi while True: message = input(f"[{pseudo}] ") if message.lower() == "quit": client.send("quit".encode('utf-8')) break client.send(f"{pseudo}: {message}".encode('utf-8')) client.close() # ============================================================================= # PARTIE 6 : Réponses aux questions de synthèse # ============================================================================= """ RÉPONSES AUX QUESTIONS DE SYNTHÈSE 1. Différence entre SOCK_STREAM (TCP) et SOCK_DGRAM (UDP) : - SOCK_STREAM (TCP) : Communication orientée connexion, fiable, ordonnée. Les données arrivent dans l'ordre, sans perte ni duplication. - SOCK_DGRAM (UDP) : Communication sans connexion, non fiable. Les datagrammes peuvent arriver dans le désordre, être perdus ou dupliqués. 2. Pourquoi bind() pour le serveur et connect() pour le client ? - Le serveur utilise bind() pour s'attacher à une adresse/port spécifique et attendre les connexions entrantes (il écoute). - Le client utilise connect() pour initier activement une connexion vers un serveur connu (il se connecte à quelque chose qui existe déjà). 3. Que se passe-t-il si deux programmes utilisent le même port ? - Le second programme recevra une erreur "Address already in use". - Un port ne peut être utilisé que par un seul processus à la fois (sauf configuration spéciale comme SO_REUSEADDR). 4. Pourquoi 127.0.0.1 (localhost) pour les tests ? - C'est l'adresse de loopback qui pointe vers la machine locale. - Les paquets ne quittent jamais la machine, idéal pour les tests. - Pas besoin de configuration réseau ni de pare-feu. 5. Quand préférer UDP à TCP pour un chat ? - Pour un chat vocal/vidéo en temps réel : la latence est critique, perdre quelques paquets est acceptable (on n'entend pas un mot). - Pour un chat textuel : TCP est préférable car chaque caractère compte. """ # ============================================================================= # TABLEAU COMPARATIF TCP vs UDP # ============================================================================= """ | Critère | TCP | UDP | |------------------------|--------------------------|-------------------------| | Connexion préalable | Oui (connect/accept) | Non (envoi direct) | | Fonction d'envoi | send() | sendto() | | Garantie de livraison | Oui (retransmission) | Non | | Ordre des messages | Garanti | Non garanti | | Utilisation typique | Web, email, fichiers | Streaming, jeux, VoIP | """ # ============================================================================= # TESTS # ============================================================================= if __name__ == "__main__": import sys print("=" * 60) print("CORRIGÉ DU TP MINI-CHAT") print("=" * 60) print("\n--- Partie 1 : Découverte des sockets ---\n") print("Test afficher_info_reseau():") afficher_info_reseau() print("\nTest resoudre_dns():") print(f"www.google.fr -> {resoudre_dns('www.google.fr')}") print(f"www.wikipedia.org -> {resoudre_dns('www.wikipedia.org')}") print("\n" + "=" * 60) print("Pour tester les parties 2 à 5, lancez le script avec un argument :") print(" python Corrige_TP_MiniChat.py serveur_tcp") print(" python Corrige_TP_MiniChat.py client_tcp") print(" python Corrige_TP_MiniChat.py serveur_chat") print(" python Corrige_TP_MiniChat.py client_chat") print(" python Corrige_TP_MiniChat.py serveur_udp") print(" python Corrige_TP_MiniChat.py client_udp") print(" python Corrige_TP_MiniChat.py serveur_multi") print(" python Corrige_TP_MiniChat.py client_multi ") print("=" * 60) if len(sys.argv) > 1: commande = sys.argv[1] if commande == "serveur_tcp": creer_serveur_tcp(12345) elif commande == "client_tcp": creer_client_tcp('127.0.0.1', 12345, "Bonjour serveur !") elif commande == "serveur_chat": serveur_chat(12345) elif commande == "client_chat": client_chat('127.0.0.1', 12345) elif commande == "serveur_udp": serveur_udp(12345) elif commande == "client_udp": client_udp('127.0.0.1', 12345) elif commande == "serveur_multi": serveur_multi_clients(12345) elif commande == "client_multi": pseudo = sys.argv[2] if len(sys.argv) > 2 else "Anonyme" client_multi('127.0.0.1', 12345, pseudo) else: print(f"Commande inconnue : {commande}")