Introduction
Le module de communication socket de Python est un outil puissant pour la construction d'applications réseau. Cependant, travailler avec des connexions réseau introduit souvent divers défis et erreurs potentielles qui peuvent affecter la fiabilité de votre application. Dans ce laboratoire pratique, nous allons explorer les fondamentaux de la programmation socket en Python et vous guider à travers la mise en œuvre de techniques efficaces de gestion des erreurs.
À la fin de ce tutoriel, vous comprendrez les erreurs de communication réseau courantes et saurez comment construire des applications basées sur les sockets qui peuvent gérer avec élégance les problèmes de connexion, les délais d'attente (timeouts) et autres problèmes liés au réseau.
Comprendre les Sockets Python et la Communication de Base
Commençons par comprendre ce que sont les sockets et comment ils fonctionnent en Python.
Qu'est-ce qu'un Socket ?
Un socket est un point de terminaison (endpoint) pour l'envoi et la réception de données sur un réseau. Considérez-le comme un point de connexion virtuel par lequel circule la communication réseau. Le module socket intégré de Python fournit les outils pour créer, configurer et utiliser des sockets pour la communication réseau.
Flux de Communication Socket de Base
La communication socket suit généralement ces étapes :
- Créer un objet socket
- Lier le socket à une adresse (pour les serveurs)
- Écouter les connexions entrantes (pour les serveurs)
- Accepter les connexions (pour les serveurs) ou se connecter à un serveur (pour les clients)
- Envoyer et recevoir des données
- Fermer le socket lorsque vous avez terminé
Créons notre premier programme socket simple pour mieux comprendre ces concepts.
Création de Votre Premier Serveur Socket
Tout d'abord, créons un serveur socket de base qui écoute les connexions et renvoie toutes les données qu'il reçoit.
Ouvrez le WebIDE et créez un nouveau fichier nommé server.py dans le répertoire /home/labex/project avec le contenu suivant :
import socket
## Define server address and port
HOST = '127.0.0.1' ## Standard loopback interface address (localhost)
PORT = 65432 ## Port to listen on (non-privileged ports are > 1023)
## Create a socket object
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print(f"Socket created successfully")
## Bind the socket to the specified address and port
server_socket.bind((HOST, PORT))
print(f"Socket bound to {HOST}:{PORT}")
## Listen for incoming connections
server_socket.listen(1)
print(f"Socket is listening for connections")
## Accept a connection
print(f"Waiting for a connection...")
connection, client_address = server_socket.accept()
print(f"Connected to client: {client_address}")
## Receive and echo data
try:
while True:
## Receive data from the client
data = connection.recv(1024)
if not data:
## If no data is received, the client has disconnected
print(f"Client disconnected")
break
print(f"Received: {data.decode('utf-8')}")
## Echo the data back to the client
connection.sendall(data)
print(f"Sent: {data.decode('utf-8')}")
finally:
## Clean up the connection
connection.close()
server_socket.close()
print(f"Socket closed")
Création de Votre Premier Client Socket
Maintenant, créons un client pour nous connecter à notre serveur. Créez un nouveau fichier nommé client.py dans le même répertoire avec le contenu suivant :
import socket
## Define server address and port
HOST = '127.0.0.1' ## The server's hostname or IP address
PORT = 65432 ## The port used by the server
## Create a socket object
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print(f"Socket created successfully")
## Connect to the server
client_socket.connect((HOST, PORT))
print(f"Connected to server at {HOST}:{PORT}")
## Send and receive data
try:
## Send data to the server
message = "Hello, Server!"
client_socket.sendall(message.encode('utf-8'))
print(f"Sent: {message}")
## Receive data from the server
data = client_socket.recv(1024)
print(f"Received: {data.decode('utf-8')}")
finally:
## Clean up the connection
client_socket.close()
print(f"Socket closed")
Tester Vos Programmes Socket
Maintenant, testons nos programmes socket. Ouvrez deux fenêtres de terminal dans la VM LabEx.
Dans le premier terminal, exécutez le serveur :
cd ~/project
python3 server.py
Vous devriez voir une sortie similaire à :
Socket created successfully
Socket bound to 127.0.0.1:65432
Socket is listening for connections
Waiting for a connection...
Gardez le serveur en cours d'exécution et ouvrez un second terminal pour exécuter le client :
cd ~/project
python3 client.py
Vous devriez voir une sortie similaire à :
Socket created successfully
Connected to server at 127.0.0.1:65432
Sent: Hello, Server!
Received: Hello, Server!
Socket closed
Et dans le terminal du serveur, vous devriez voir :
Connected to client: ('127.0.0.1', XXXXX)
Received: Hello, Server!
Sent: Hello, Server!
Client disconnected
Socket closed
Félicitations ! Vous venez de créer et de tester votre première application client-serveur basée sur les sockets en Python. Cela fournit les bases pour comprendre comment fonctionne la communication socket et comment implémenter la gestion des erreurs dans les prochaines étapes.
Erreurs de Socket Courantes et Gestion des Erreurs de Base
Dans l'étape précédente, nous avons créé un serveur et un client socket simples, mais nous n'avons pas abordé ce qui se passe lorsque des erreurs se produisent pendant la communication socket. La communication réseau est intrinsèquement peu fiable, et divers problèmes peuvent survenir, des échecs de connexion aux déconnexions inattendues.
Erreurs de Socket Courantes
Lorsque vous travaillez avec la programmation socket, vous pouvez rencontrer plusieurs erreurs courantes :
- Connection refused (Connexion refusée) : Se produit lorsqu'un client tente de se connecter à un serveur qui n'est pas en cours d'exécution ou qui n'écoute pas sur le port spécifié.
- Connection timeout (Délai d'attente de la connexion) : Se produit lorsqu'une tentative de connexion prend trop de temps à se terminer.
- Address already in use (Adresse déjà utilisée) : Se produit lorsque vous essayez de lier un socket à une adresse et un port déjà en cours d'utilisation.
- Connection reset (Réinitialisation de la connexion) : Se produit lorsque la connexion est fermée de manière inattendue par le pair.
- Network unreachable (Réseau inaccessible) : Se produit lorsque l'interface réseau ne peut pas atteindre le réseau de destination.
Gestion des Erreurs de Base avec try-except
Le mécanisme de gestion des exceptions de Python fournit un moyen robuste de gérer les erreurs dans la communication socket. Mettons à jour nos programmes client et serveur pour inclure une gestion des erreurs de base.
Serveur Socket Amélioré avec Gestion des Erreurs
Mettez à jour votre fichier server.py avec le code suivant :
import socket
import sys
## Define server address and port
HOST = '127.0.0.1'
PORT = 65432
## Create a socket object
try:
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print(f"Socket created successfully")
## Allow reuse of address
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
## Bind the socket to the specified address and port
server_socket.bind((HOST, PORT))
print(f"Socket bound to {HOST}:{PORT}")
## Listen for incoming connections
server_socket.listen(1)
print(f"Socket is listening for connections")
## Accept a connection
print(f"Waiting for a connection...")
connection, client_address = server_socket.accept()
print(f"Connected to client: {client_address}")
## Receive and echo data
try:
while True:
## Receive data from the client
data = connection.recv(1024)
if not data:
## If no data is received, the client has disconnected
print(f"Client disconnected")
break
print(f"Received: {data.decode('utf-8')}")
## Echo the data back to the client
connection.sendall(data)
print(f"Sent: {data.decode('utf-8')}")
except socket.error as e:
print(f"Socket error occurred: {e}")
finally:
## Clean up the connection
connection.close()
print(f"Connection closed")
except socket.error as e:
print(f"Socket error occurred: {e}")
except KeyboardInterrupt:
print(f"\nServer shutting down...")
finally:
## Clean up the server socket
if 'server_socket' in locals():
server_socket.close()
print(f"Server socket closed")
sys.exit(0)
Client Socket Amélioré avec Gestion des Erreurs
Mettez à jour votre fichier client.py avec le code suivant :
import socket
import sys
import time
## Define server address and port
HOST = '127.0.0.1'
PORT = 65432
## Create a socket object
try:
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print(f"Socket created successfully")
## Set a timeout for connection attempts
client_socket.settimeout(5)
print(f"Socket timeout set to 5 seconds")
## Connect to the server
try:
print(f"Attempting to connect to server at {HOST}:{PORT}...")
client_socket.connect((HOST, PORT))
print(f"Connected to server at {HOST}:{PORT}")
## Send and receive data
try:
## Send data to the server
message = "Hello, Server!"
client_socket.sendall(message.encode('utf-8'))
print(f"Sent: {message}")
## Receive data from the server
data = client_socket.recv(1024)
print(f"Received: {data.decode('utf-8')}")
except socket.error as e:
print(f"Error during data exchange: {e}")
except socket.timeout:
print(f"Connection attempt timed out")
except ConnectionRefusedError:
print(f"Connection refused. Make sure the server is running.")
except socket.error as e:
print(f"Connection error: {e}")
except socket.error as e:
print(f"Socket creation error: {e}")
except KeyboardInterrupt:
print(f"\nClient shutting down...")
finally:
## Clean up the connection
if 'client_socket' in locals():
client_socket.close()
print(f"Socket closed")
sys.exit(0)
Tester la Gestion des Erreurs
Testons maintenant notre gestion des erreurs. Nous allons démontrer une erreur courante : essayer de se connecter à un serveur qui n'est pas en cours d'exécution.
Tout d'abord, assurez-vous que le serveur n'est pas en cours d'exécution (fermez-le s'il est en cours d'exécution).
Exécutez le client :
cd ~/project python3 client.pyVous devriez voir une sortie similaire à :
Socket created successfully Socket timeout set to 5 seconds Attempting to connect to server at 127.0.0.1:65432... Connection refused. Make sure the server is running. Socket closedMaintenant, démarrez le serveur dans un terminal :
cd ~/project python3 server.pyDans un autre terminal, exécutez le client :
cd ~/project python3 client.pyLa connexion devrait réussir, et vous devriez voir la sortie attendue du client et du serveur.
Comprendre le Code de Gestion des Erreurs
Examinons les principaux composants de gestion des erreurs que nous avons ajoutés :
- Bloc try-except externe : Gère la création du socket et les erreurs générales.
- Bloc try-except de connexion : Gère spécifiquement les erreurs liées à la connexion.
- Bloc try-except d'échange de données : Gère les erreurs lors de l'envoi et de la réception de données.
- Bloc finally : Assure que les ressources sont correctement nettoyées, qu'une erreur se soit produite ou non.
- socket.settimeout() : Définit une période de délai d'attente pour les opérations comme connect() afin d'éviter une attente indéfinie.
- socket.setsockopt() : Définit les options du socket, comme SO_REUSEADDR pour permettre la réutilisation de l'adresse immédiatement après la fermeture du serveur.
Ces améliorations rendent nos programmes socket plus robustes en gérant correctement les erreurs et en garantissant que les ressources sont correctement nettoyées.
Techniques Avancées de Gestion des Erreurs
Maintenant que nous comprenons la gestion des erreurs de base, explorons quelques techniques avancées pour rendre nos applications socket encore plus robustes. Dans cette étape, nous allons implémenter :
- Des mécanismes de nouvelle tentative (retry) pour les échecs de connexion
- Une gestion gracieuse des déconnexions inattendues
- L'intégration de la journalisation (logging) pour un meilleur suivi des erreurs
Création d'un Client avec Mécanisme de Nouvelle Tentative
Créons un client amélioré qui relance automatiquement la connexion en cas d'échec. Créez un nouveau fichier nommé retry_client.py dans le répertoire /home/labex/project :
import socket
import sys
import time
## Define server address and port
HOST = '127.0.0.1'
PORT = 65432
## Configure retry parameters
MAX_RETRIES = 3
RETRY_DELAY = 2 ## seconds
def connect_with_retry(host, port, max_retries, retry_delay):
"""Attempt to connect to a server with retry mechanism"""
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_socket.settimeout(5) ## Set timeout for connection attempts
print(f"Socket created successfully")
print(f"Socket timeout set to 5 seconds")
attempt = 0
while attempt < max_retries:
attempt += 1
try:
print(f"Connection attempt {attempt}/{max_retries}...")
client_socket.connect((host, port))
print(f"Connected to server at {host}:{port}")
return client_socket
except socket.timeout:
print(f"Connection attempt timed out")
except ConnectionRefusedError:
print(f"Connection refused. Make sure the server is running.")
except socket.error as e:
print(f"Connection error: {e}")
if attempt < max_retries:
print(f"Retrying in {retry_delay} seconds...")
time.sleep(retry_delay)
## If we get here, all connection attempts failed
print(f"Failed to connect after {max_retries} attempts")
client_socket.close()
return None
try:
## Attempt to connect with retry
client_socket = connect_with_retry(HOST, PORT, MAX_RETRIES, RETRY_DELAY)
## Proceed if connection was successful
if client_socket:
try:
## Send data to the server
message = "Hello, Server with Retry!"
client_socket.sendall(message.encode('utf-8'))
print(f"Sent: {message}")
## Receive data from the server
data = client_socket.recv(1024)
print(f"Received: {data.decode('utf-8')}")
except socket.error as e:
print(f"Error during data exchange: {e}")
finally:
## Clean up the connection
client_socket.close()
print(f"Socket closed")
except KeyboardInterrupt:
print(f"\nClient shutting down...")
if 'client_socket' in locals() and client_socket:
client_socket.close()
print(f"Socket closed")
sys.exit(0)
Création d'un Serveur qui Gère Plusieurs Clients et Déconnexions
Créons un serveur amélioré qui peut gérer plusieurs clients et gérer gracieusement les déconnexions. Créez un nouveau fichier nommé robust_server.py dans le même répertoire :
import socket
import sys
import time
## Define server address and port
HOST = '127.0.0.1'
PORT = 65432
def handle_client(client_socket, client_address):
"""Handle a client connection"""
print(f"Handling connection from {client_address}")
try:
## Set a timeout for receiving data
client_socket.settimeout(30) ## 30 seconds timeout for inactivity
## Receive and echo data
while True:
try:
## Receive data from the client
data = client_socket.recv(1024)
if not data:
## If no data is received, the client has disconnected
print(f"Client {client_address} disconnected gracefully")
break
print(f"Received from {client_address}: {data.decode('utf-8')}")
## Echo the data back to the client
client_socket.sendall(data)
print(f"Sent to {client_address}: {data.decode('utf-8')}")
except socket.timeout:
print(f"Connection with {client_address} timed out due to inactivity")
break
except ConnectionResetError:
print(f"Connection with {client_address} was reset by the client")
break
except socket.error as e:
print(f"Error with client {client_address}: {e}")
break
finally:
## Clean up the connection
client_socket.close()
print(f"Connection with {client_address} closed")
try:
## Create a socket object
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print(f"Socket created successfully")
## Allow reuse of address
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
## Bind the socket to the specified address and port
server_socket.bind((HOST, PORT))
print(f"Socket bound to {HOST}:{PORT}")
## Listen for incoming connections
server_socket.listen(5) ## Allow up to 5 pending connections
print(f"Socket is listening for connections")
## Set timeout for accept operation
server_socket.settimeout(60) ## 60 seconds timeout for accept
## Accept connections and handle them
while True:
try:
print(f"Waiting for a connection...")
client_socket, client_address = server_socket.accept()
print(f"Connected to client: {client_address}")
## Handle this client
handle_client(client_socket, client_address)
except socket.timeout:
print(f"No connections received in the last 60 seconds, still waiting...")
except socket.error as e:
print(f"Error accepting connection: {e}")
## Small delay to prevent CPU hogging in case of persistent errors
time.sleep(1)
except socket.error as e:
print(f"Socket error occurred: {e}")
except KeyboardInterrupt:
print(f"\nServer shutting down...")
finally:
## Clean up the server socket
if 'server_socket' in locals():
server_socket.close()
print(f"Server socket closed")
sys.exit(0)
Intégration de la Journalisation pour un Meilleur Suivi des Erreurs
Créons un serveur avec des capacités de journalisation appropriées. Créez un nouveau fichier nommé logging_server.py dans le même répertoire :
import socket
import sys
import time
import logging
## Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler("server_log.txt"),
logging.StreamHandler()
]
)
## Define server address and port
HOST = '127.0.0.1'
PORT = 65432
def handle_client(client_socket, client_address):
"""Handle a client connection with logging"""
logging.info(f"Handling connection from {client_address}")
try:
## Set a timeout for receiving data
client_socket.settimeout(30) ## 30 seconds timeout for inactivity
## Receive and echo data
while True:
try:
## Receive data from the client
data = client_socket.recv(1024)
if not data:
## If no data is received, the client has disconnected
logging.info(f"Client {client_address} disconnected gracefully")
break
logging.info(f"Received from {client_address}: {data.decode('utf-8')}")
## Echo the data back to the client
client_socket.sendall(data)
logging.info(f"Sent to {client_address}: {data.decode('utf-8')}")
except socket.timeout:
logging.warning(f"Connection with {client_address} timed out due to inactivity")
break
except ConnectionResetError:
logging.error(f"Connection with {client_address} was reset by the client")
break
except socket.error as e:
logging.error(f"Error with client {client_address}: {e}")
break
finally:
## Clean up the connection
client_socket.close()
logging.info(f"Connection with {client_address} closed")
try:
## Create a socket object
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
logging.info(f"Socket created successfully")
## Allow reuse of address
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
## Bind the socket to the specified address and port
server_socket.bind((HOST, PORT))
logging.info(f"Socket bound to {HOST}:{PORT}")
## Listen for incoming connections
server_socket.listen(5) ## Allow up to 5 pending connections
logging.info(f"Socket is listening for connections")
## Set timeout for accept operation
server_socket.settimeout(60) ## 60 seconds timeout for accept
## Accept connections and handle them
while True:
try:
logging.info(f"Waiting for a connection...")
client_socket, client_address = server_socket.accept()
logging.info(f"Connected to client: {client_address}")
## Handle this client
handle_client(client_socket, client_address)
except socket.timeout:
logging.info(f"No connections received in the last 60 seconds, still waiting...")
except socket.error as e:
logging.error(f"Error accepting connection: {e}")
## Small delay to prevent CPU hogging in case of persistent errors
time.sleep(1)
except socket.error as e:
logging.critical(f"Socket error occurred: {e}")
except KeyboardInterrupt:
logging.info(f"Server shutting down...")
finally:
## Clean up the server socket
if 'server_socket' in locals():
server_socket.close()
logging.info(f"Server socket closed")
sys.exit(0)
Tester la Gestion Avancée des Erreurs
Testons nos implémentations avancées de gestion des erreurs :
Testez le mécanisme de nouvelle tentative en exécutant le client de nouvelle tentative sans serveur :
cd ~/project python3 retry_client.pyVous devriez voir le client tenter de se connecter plusieurs fois :
Socket created successfully Socket timeout set to 5 seconds Connection attempt 1/3... Connection refused. Make sure the server is running. Retrying in 2 seconds... Connection attempt 2/3... Connection refused. Make sure the server is running. Retrying in 2 seconds... Connection attempt 3/3... Connection refused. Make sure the server is running. Failed to connect after 3 attemptsDémarrez le serveur robuste et essayez de vous connecter avec le client de nouvelle tentative :
## Terminal 1 cd ~/project python3 robust_server.py ## Terminal 2 cd ~/project python3 retry_client.pyVous devriez voir une connexion réussie et un échange de données.
Testez le serveur de journalisation pour voir comment les journaux sont enregistrés :
## Terminal 1 cd ~/project python3 logging_server.py ## Terminal 2 cd ~/project python3 client.pyAprès l'échange, vous pouvez vérifier le fichier journal :
cat ~/project/server_log.txtVous devriez voir des journaux détaillés de la connexion et de l'échange de données.
Techniques Clés de Gestion Avancée des Erreurs
Dans ces exemples, nous avons implémenté plusieurs techniques avancées de gestion des erreurs :
- Mécanismes de nouvelle tentative : Relance automatiquement les opérations ayant échoué un nombre défini de fois avec des délais entre les tentatives.
- Paramètres de délai d'attente : Définir des délais d'attente sur les opérations socket pour éviter une attente indéfinie.
- Gestion détaillée des erreurs : Intercepter des exceptions socket spécifiques et les gérer de manière appropriée.
- Journalisation structurée : Utiliser le module de journalisation de Python pour enregistrer des informations détaillées sur les erreurs et les opérations.
- Nettoyage des ressources : S'assurer que toutes les ressources sont correctement fermées, même en cas d'erreurs.
Ces techniques aident à créer des applications socket plus robustes qui peuvent gérer un large éventail de conditions d'erreur avec élégance.
Création d'une Application Socket Complète et Résistante aux Erreurs
Dans cette dernière étape, nous allons combiner tout ce que nous avons appris pour créer une application socket complète et résistante aux erreurs. Nous allons construire un système de chat simple avec une gestion des erreurs appropriée à tous les niveaux.
Architecture de l'Application de Chat
Notre application de chat se composera de :
- Un serveur capable de gérer plusieurs clients
- Des clients capables d'envoyer et de recevoir des messages
- Une gestion robuste des erreurs tout au long
- Une gestion appropriée des ressources
- Une journalisation pour les diagnostics
Création du Serveur de Chat
Créez un nouveau fichier nommé chat_server.py dans le répertoire /home/labex/project :
import socket
import sys
import threading
import logging
import time
## Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler("chat_server_log.txt"),
logging.StreamHandler()
]
)
## Define server address and port
HOST = '127.0.0.1'
PORT = 65432
## Store active client connections
clients = {}
clients_lock = threading.Lock()
def broadcast(message, sender_address=None):
"""Send a message to all connected clients except the sender"""
with clients_lock:
for client_address, client_socket in list(clients.items()):
## Don't send the message back to the sender
if client_address != sender_address:
try:
client_socket.sendall(message)
except socket.error:
## If sending fails, the client will be removed in the client handler
pass
def handle_client(client_socket, client_address):
"""Handle a client connection"""
client_id = f"{client_address[0]}:{client_address[1]}"
logging.info(f"New client connected: {client_id}")
## Register the new client
with clients_lock:
clients[client_address] = client_socket
## Notify all clients about the new connection
broadcast(f"SERVER: Client {client_id} has joined the chat.".encode('utf-8'))
try:
## Set a timeout for receiving data
client_socket.settimeout(300) ## 5 minutes timeout for inactivity
## Handle client messages
while True:
try:
## Receive data from the client
data = client_socket.recv(1024)
if not data:
## If no data is received, the client has disconnected
break
message = data.decode('utf-8')
logging.info(f"Message from {client_id}: {message}")
## Broadcast the message to all other clients
broadcast_message = f"{client_id}: {message}".encode('utf-8')
broadcast(broadcast_message, client_address)
except socket.timeout:
logging.warning(f"Client {client_id} timed out due to inactivity")
client_socket.sendall("SERVER: You have been disconnected due to inactivity.".encode('utf-8'))
break
except ConnectionResetError:
logging.error(f"Connection with client {client_id} was reset")
break
except socket.error as e:
logging.error(f"Error with client {client_id}: {e}")
break
finally:
## Remove client from active clients
with clients_lock:
if client_address in clients:
del clients[client_address]
## Close the client socket
client_socket.close()
logging.info(f"Connection with client {client_id} closed")
## Notify all clients about the disconnection
broadcast(f"SERVER: Client {client_id} has left the chat.".encode('utf-8'))
def main():
"""Main server function"""
try:
## Create a socket object
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
logging.info("Socket created successfully")
## Allow reuse of address
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
## Bind the socket to the specified address and port
server_socket.bind((HOST, PORT))
logging.info(f"Socket bound to {HOST}:{PORT}")
## Listen for incoming connections
server_socket.listen(5) ## Allow up to 5 pending connections
logging.info("Socket is listening for connections")
## Accept connections and handle them
while True:
try:
## Accept a new client connection
client_socket, client_address = server_socket.accept()
## Start a new thread to handle the client
client_thread = threading.Thread(
target=handle_client,
args=(client_socket, client_address)
)
client_thread.daemon = True
client_thread.start()
except socket.error as e:
logging.error(f"Error accepting connection: {e}")
time.sleep(1) ## Small delay to prevent CPU hogging
except socket.error as e:
logging.critical(f"Socket error occurred: {e}")
except KeyboardInterrupt:
logging.info("Server shutting down...")
finally:
## Clean up and close all client connections
with clients_lock:
for client_socket in clients.values():
try:
client_socket.close()
except:
pass
clients.clear()
## Close the server socket
if 'server_socket' in locals():
server_socket.close()
logging.info("Server socket closed")
logging.info("Server shutdown complete")
if __name__ == "__main__":
main()
Création du Client de Chat
Créez un nouveau fichier nommé chat_client.py dans le même répertoire :
import socket
import sys
import threading
import logging
import time
## Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler("chat_client_log.txt"),
logging.StreamHandler(sys.stdout)
]
)
## Define server address and port
HOST = '127.0.0.1'
PORT = 65432
## Flag to indicate if the client is running
running = True
def receive_messages(client_socket):
"""Receive and display messages from the server"""
global running
while running:
try:
## Receive data from the server
data = client_socket.recv(1024)
if not data:
logging.warning("Server has closed the connection")
running = False
break
## Display the received message
message = data.decode('utf-8')
print(f"\n{message}")
print("Your message: ", end='', flush=True)
except socket.timeout:
## Socket timeout - just continue and check if we're still running
continue
except ConnectionResetError:
logging.error("Connection was reset by the server")
running = False
break
except socket.error as e:
logging.error(f"Socket error: {e}")
running = False
break
logging.info("Message receiver stopped")
def connect_to_server(host, port, max_retries=3, retry_delay=2):
"""Connect to the chat server with retry mechanism"""
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_socket.settimeout(5) ## Set timeout for connection attempts
logging.info("Socket created successfully")
attempt = 0
while attempt < max_retries:
attempt += 1
try:
logging.info(f"Connection attempt {attempt}/{max_retries}...")
client_socket.connect((host, port))
logging.info(f"Connected to server at {host}:{port}")
return client_socket
except socket.timeout:
logging.warning("Connection attempt timed out")
except ConnectionRefusedError:
logging.warning("Connection refused. Make sure the server is running.")
except socket.error as e:
logging.error(f"Connection error: {e}")
if attempt < max_retries:
logging.info(f"Retrying in {retry_delay} seconds...")
time.sleep(retry_delay)
## If we get here, all connection attempts failed
logging.error(f"Failed to connect after {max_retries} attempts")
client_socket.close()
return None
def main():
"""Main client function"""
global running
try:
## Connect to the server
client_socket = connect_to_server(HOST, PORT)
if not client_socket:
logging.error("Could not connect to server. Exiting.")
return
## Set a longer timeout for normal operation
client_socket.settimeout(1) ## 1 second timeout for receiving
## Start a thread to receive messages
receive_thread = threading.Thread(target=receive_messages, args=(client_socket,))
receive_thread.daemon = True
receive_thread.start()
## Print welcome message
print("\nWelcome to the Chat Client!")
print("Type your messages and press Enter to send.")
print("Type 'exit' to quit the chat.")
## Send messages
while running:
try:
message = input("Your message: ")
## Check if the user wants to exit
if message.lower() == 'exit':
logging.info("User requested to exit")
running = False
break
## Send the message to the server
client_socket.sendall(message.encode('utf-8'))
except EOFError:
## Handle EOF (Ctrl+D)
logging.info("EOF received, exiting")
running = False
break
except KeyboardInterrupt:
## Handle Ctrl+C
logging.info("Keyboard interrupt received, exiting")
running = False
break
except socket.error as e:
logging.error(f"Error sending message: {e}")
running = False
break
except Exception as e:
logging.error(f"Unexpected error: {e}")
finally:
## Clean up
running = False
if 'client_socket' in locals() and client_socket:
try:
client_socket.close()
logging.info("Socket closed")
except:
pass
logging.info("Client shutdown complete")
print("\nDisconnected from the chat server. Goodbye!")
if __name__ == "__main__":
main()
Tester l'Application de Chat
Maintenant, testons notre application de chat :
Tout d'abord, démarrez le serveur de chat :
cd ~/project python3 chat_server.pyDans un deuxième terminal, démarrez un client de chat :
cd ~/project python3 chat_client.pyDans un troisième terminal, démarrez un autre client de chat :
cd ~/project python3 chat_client.pyEnvoyez des messages des deux clients et observez comment ils sont diffusés à tous les clients connectés.
Essayez de terminer l'un des clients (en utilisant Ctrl+C ou en tapant 'exit') et observez comment le serveur gère la déconnexion.
Redémarrez l'un des clients pour voir le processus de reconnexion.
Principales Fonctionnalités Implémentées
Notre application de chat complète implémente plusieurs fonctionnalités importantes de gestion des erreurs et de robustesse :
- Mécanisme de nouvelle tentative de connexion : Le client tente de se reconnecter au serveur si la connexion initiale échoue.
- Gestion appropriée des threads : Le serveur utilise des threads pour gérer plusieurs clients simultanément.
- Gestion des délais d'attente : Le serveur et le client implémentent des délais d'attente pour éviter une attente indéfinie.
- Nettoyage des ressources : Toutes les ressources (sockets, threads) sont correctement nettoyées, même en cas d'erreurs.
- Gestion complète des erreurs : Des types d'erreurs spécifiques sont interceptés et gérés de manière appropriée.
- Journalisation : Le serveur et le client implémentent la journalisation pour les diagnostics.
- Messages conviviaux : Des messages clairs informent les utilisateurs de l'état de la connexion.
- Arrêt en douceur : L'application peut s'arrêter en douceur sur demande.
Bonnes Pratiques pour la Gestion des Erreurs de Socket
Basées sur notre implémentation, voici quelques bonnes pratiques pour la gestion des erreurs de socket en Python :
- Utilisez toujours des blocs try-except autour des opérations socket pour intercepter et gérer les erreurs.
- Implémentez des délais d'attente pour toutes les opérations socket afin d'éviter une attente indéfinie.
- Utilisez des types d'exceptions spécifiques pour gérer différents types d'erreurs de manière appropriée.
- Fermez toujours les sockets dans les blocs finally pour assurer un nettoyage approprié des ressources.
- Implémentez des mécanismes de nouvelle tentative pour les opérations importantes comme les connexions.
- Utilisez la journalisation pour enregistrer les erreurs et les opérations à des fins de diagnostic.
- Gérez correctement la synchronisation des threads lorsque vous travaillez avec plusieurs clients.
- Fournissez des messages d'erreur significatifs aux utilisateurs lorsque des problèmes surviennent.
- Implémentez des procédures d'arrêt en douceur pour le client et le serveur.
- Testez les scénarios d'erreur pour vous assurer que votre gestion des erreurs fonctionne correctement.
Le respect de ces bonnes pratiques vous aidera à créer des applications basées sur des sockets robustes et fiables en Python.
Résumé
Dans ce laboratoire, vous avez appris à implémenter une gestion robuste des erreurs dans la communication socket en Python. En commençant par les bases de la programmation socket, vous avez progressé en identifiant les erreurs courantes liées aux sockets et en implémentant des techniques de gestion des erreurs appropriées.
Les principaux apprentissages de ce laboratoire incluent :
Comprendre les bases des sockets : Vous avez appris comment fonctionne la communication socket en Python, y compris la création de sockets, l'établissement de connexions et l'échange de données.
Identifier les erreurs courantes : Vous avez exploré les erreurs courantes liées aux sockets, telles que les refus de connexion, les délais d'attente (timeouts) et les déconnexions inattendues.
Implémenter une gestion des erreurs de base : Vous avez appris à utiliser des blocs try-except pour intercepter et gérer les erreurs de socket avec élégance.
Techniques avancées de gestion des erreurs : Vous avez implémenté des mécanismes de nouvelle tentative (retry), la gestion des délais d'attente (timeout handling) et un nettoyage approprié des ressources.
Intégration de la journalisation : Vous avez appris à utiliser le module de journalisation (logging) de Python pour enregistrer les opérations et les erreurs à des fins de diagnostic.
Construction d'applications complètes : Vous avez créé une application de chat complète qui démontre une gestion complète des erreurs dans un scénario réel.
En appliquant ces techniques dans vos propres projets de programmation socket en Python, vous serez en mesure de créer des applications réseau plus robustes et fiables, capables de gérer avec élégance diverses conditions d'erreur.
Rappelez-vous qu'une gestion appropriée des erreurs ne consiste pas seulement à intercepter les erreurs, mais aussi à fournir des commentaires significatifs, à implémenter des mécanismes de récupération et à garantir que votre application reste stable et sécurisée, même face à des problèmes liés au réseau.



