381 lines
9.9 KiB
Markdown
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>.
|