Files
TermNSI/Réseau/TP_MiniChat.md

381 lines
9.9 KiB
Markdown

# 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
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Licence Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a> <br />Ce cours est mis à disposition selon les termes de la <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International</a>.