ajout de tous les cours et TP préparés cet été
This commit is contained in:
380
Réseau/TP_MiniChat.md
Normal file
380
Réseau/TP_MiniChat.md
Normal file
@@ -0,0 +1,380 @@
|
||||
# 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>.
|
||||
Reference in New Issue
Block a user