La programmation réseau en Python repose principalement sur le module socket, qui permet d'établir des connexions bidirectionnelles entre deux nœuds. Dans ce guide technique, nous explorons la mise en œuvre d'une architecture client-serveur robuste utilisant le protocole TCP, tout en intégrant une couche de sécurité par encodage de données et manipulation de fichiers.
Architecture du Serveur TCP
Le serveur doit être configuré pour écouter sur une interface réseau et un port spécifiques. Pour garantir l'intégrité des échanges, le serveur reçoit des données encodées, les décode via l'algorithme Base64, puis les stocke localement.
import socket
import base64
def executer_serveur():
# Configuration de l'hôte et du port
adresse_ip = '127.0.0.1'
port_ecoute = 8500
# Initialisation du socket TCP
serveur_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
# Liaison du socket à l'adresse (nécessite un tuple)
serveur_socket.bind((adresse_ip, port_ecoute))
serveur_socket.listen(5)
print(f"Serveur actif sur {adresse_ip}:{port_ecoute}")
while True:
client_conn, addr = serveur_socket.accept()
print(f"Connexion entrante de : {addr}")
# Réception du nom du fichier
nom_distant = client_conn.recv(1024).decode('utf-8')
# Réception des données chiffrées/encodées
donnees_chiffrees = client_conn.recv(4096)
# Décodage Base64 et sauvegarde
contenu_final = base64.b64decode(donnees_chiffrees)
with open(f"copie_{nom_distant}", "wb") as fichier_local:
fichier_local.write(contenu_final)
print(f"Fichier '{nom_distant}' reçu et décodé avec succès.")
client_conn.send("Transfert terminé.".encode('utf-8'))
client_conn.close()
except Exception as e:
print(f"Erreur serveur : {e}")
finally:
serveur_socket.close()
if __name__ == "__main__":
executer_serveur()
Développement du Client Expéditeur
Le client est responsable de la lecture du fichier source, de son encodage pour le transport, et de l'initiation de la connexion avec le serveur distant.
import socket
import base64
import os
def transferer_fichier(chemin_source):
cible_ip = '127.0.0.1'
cible_port = 8500
if not os.path.exists(chemin_source):
print("Erreur : Le fichier source est introuvable.")
return
# Lecture et encodage du contenu
with open(chemin_source, "rb") as f:
contenu_brut = f.read()
contenu_encode = base64.b64encode(contenu_brut)
# Création du socket et connexion
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
client_socket.connect((cible_ip, cible_port))
# Envoi du nom de fichier original
client_socket.send(os.path.basename(chemin_source).encode('utf-8'))
# Temporisation légère ou séparation des flux pour éviter la saturation
client_socket.sendall(contenu_encode)
reponse = client_socket.recv(1024)
print(f"Réponse du serveur : {reponse.decode('utf-8')}")
except ConnectionRefusedError:
print("Erreur : Impossible de joindre le serveur. Vérifiez le port et le pare-feu.")
finally:
client_socket.close()
if __name__ == "__main__":
transferer_fichier("document_confidentiel.txt")
Analyse des points critiques
Gestion des erreurs de liaison
Une erreur fréquente lors de l'utilisation de la méthode bind() est de passer l'IP et le port comme deux argumants distincts. Il est impératif de les encapsuler dans un tuple : socket.bind((ip, port)). Sans cela, l'interpréteur Python lève une exception de type TypeError.
Sécurité et Pare-feu
Lors de tests sur des machines distantes ou des instances cloud (type ECS), la connexion peut être rejetée par le système d'exploitation. Il est nécessaire de configurer les règles de sécurité (Security Groups) ou le pare-feu local (iptables/ufw) pour autoriser le trafic entrant sur le port sélectionné.
Encodage Base64 vs Chiffrement
L'utilisation de base64 permet de transformer des données binaires en texte ASCII, facilitant le transfert via certains protocoles. Bien que cela masque le contenu à l'œil nu, il ne s'agit pas d'un chiffrement robuste. Pour une sécurité accrue en production, l'intégration de bibliothèques comme cryptography (AES) ou l'utilisation de SSL/TLS via le module ssl de Python est recommandée.
Gestion multi-clients
Pour traiter plusieurs requêtes simultanément, le serveur peut être amélioré en utilisant le module threading. Chaque connexion entrante déclenche alors un nouveau thread, permettant au serveur de rester disponible pour d'autres clients pendant que le transfert de fichier s'effectue en arrière-plan.