425 lines
13 KiB
Python
425 lines
13 KiB
Python
"""
|
|
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 <pseudo>")
|
|
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}")
|