socketserver
— Cadriciel pour serveurs réseaux¶
Code source : Lib/socketserver.py
Le module socketserver
permet de simplifier le développement de serveurs réseaux.
Il existe quatre classes concrètes fondamentales :
-
class
socketserver.
TCPServer
(server_address, RequestHandlerClass, bind_and_activate=True)¶ Cette classe permet de créer des flux continus de données entre un client et un serveur en utilisant le protocole internet TCP. Si bind_and_activate est vrai, le constructeur tente automatiquement d'invoquer
server_bind()
etserver_activate()
. Les autres paramètres sont passés à la classe de baseBaseServer
.
-
class
socketserver.
UDPServer
(server_address, RequestHandlerClass, bind_and_activate=True)¶ Cette classe utilise des datagrammes, qui sont des paquets d'information pouvant arriver dans le désordre, voire être perdus, durant le transport. Les paramètres sont identiques à
TCPServer
.
-
class
socketserver.
UnixStreamServer
(server_address, RequestHandlerClass, bind_and_activate=True)¶ -
class
socketserver.
UnixDatagramServer
(server_address, RequestHandlerClass, bind_and_activate=True)¶ Ces classes, moins fréquemment utilisées, sont similaires aux classes pour TCP et UDP mais utilisent des connecteurs UNIX ; ces connecteurs ne sont disponibles que sur les plateformes UNIX. Les paramètres sont identiques à
TCPServer
.
Ces quatre classes traitent les requêtes de façon synchrone : chaque requête doit être terminée avant de pouvoir traiter la suivante. Cette méthode n'est pas adaptée si les requêtes prennent beaucoup de temps à être traitées. Cela peut arriver si les requêtes demandent beaucoup de calcul, ou si elles renvoient beaucoup de données que le client peine à traiter. Dans ce cas, la solution est de créer un processus ou un fil d’exécution séparé pour chaque requête ; les classes de mélange ForkingMixIn
et ThreadingMixIn
peuvent être utilisées pour réaliser ce comportement asynchrone.
La création d'un serveur requiert plusieurs étapes. Premièrement, vous devez créer une classe pour gérer les requêtes en héritant de BaseRequestHandler
et surcharger sa méthode handle()
, laquelle traitera les requêtes entrantes. Deuxièmement, vous devez instancier l'une des classes serveurs et lui passer l’adresse du serveur ainsi que la classe gérant les requêtes. Il est recommandé de faire ceci dans une instruction with
. Ensuite, appelez la méthode handle_request()
ou serve_forever()
de l'objet serveur afin de traiter une ou plusieurs requêtes. Enfin, appelez la méthode server_close()
pour fermer le connecteur (à moins que vous n'ayez utilisé une instruction with
).
Lorsque vous héritez de ThreadingMixIn
pour déléguer les connexions à différent fils d’exécution, vous devez déclarer explicitement comment les fils d’exécution doivent se comporter en cas d'arrêt abrupt. La classe ThreadingMixIn
définit un attribut daemon_threads, indiquant si le serveur doit attendre la fin des fils d’exécution ou non. Vous pouvez utiliser cet attribut si vous souhaitez que les fils d’exécution soient autonomes. La valeur par défaut est False
, indiquant que Python ne doit pas quitter avant que tous les fils d'exécution créés par ThreadingMixIn
ne soient terminés.
Toutes les classes de serveurs exposent les mêmes méthodes et attributs, peu importe le protocole réseau utilisé.
Notes sur la création de serveurs¶
Il y a cinq classes dans la hiérarchie. Quatre d'entre elles représentent des serveurs synchrones de quatre types différents :
+------------+
| BaseServer |
+------------+
|
v
+-----------+ +------------------+
| TCPServer |------->| UnixStreamServer |
+-----------+ +------------------+
|
v
+-----------+ +--------------------+
| UDPServer |------->| UnixDatagramServer |
+-----------+ +--------------------+
Note that UnixDatagramServer
derives from UDPServer
, not from
UnixStreamServer
--- the only difference between an IP and a Unix
server is the address family.
-
class
socketserver.
ForkingMixIn
¶ -
class
socketserver.
ThreadingMixIn
¶ Des versions utilisant des fils d’exécution ou des processus peuvent être créées pour chaque type de serveur, en utilisant ces classes de mélange. Par exemple,
ThreadingUDPServer
est créé comme suit :class ThreadingUDPServer(ThreadingMixIn, UDPServer): pass
La classe de mélange est en premier car elle surcharge une méthode définie dans
UDPServer
. Configurer les différents attributs changera également le comportement du serveur.La classe
ForkingMixIn
et les classes créant des processus mentionnées ci-dessous sont uniquement disponibles sur les plateformes POSIX prenant en chargefork()
.La méthode
socketserver.ForkingMixIn.server_close()
attend jusqu'à ce que tous les processus enfants soient terminés, sauf si l'attributsocketserver.ForkingMixIn.block_on_close
est faux.La méthode
socketserver.ThreadingMixIn.server_close()
attend que tous les fils d'exécution non-daemon soit terminés, sauf si l'attributsocketserver.ThreadingMixIn.block_on_close
est faux. Utilisez des fils d'exécution daemon en réglantThreadingMixIn.daemon_threads
àTrue
afin de ne pas attendre que les fils d’exécution soit terminés.Modifié dans la version 3.7: Désormais,
socketserver.ForkingMixIn.server_close()
etsocketserver.ThreadingMixIn.server_close()
attendent que tous les processus enfants et les fils d’exécution non-daemon soit terminés. Ajout desocketserver.ForkingMixIn.block_on_close
, un nouvel attribut de classe permettant de conserver le comportement pré-3.7.
-
class
socketserver.
ForkingTCPServer
¶ -
class
socketserver.
ForkingUDPServer
¶ -
class
socketserver.
ThreadingTCPServer
¶ -
class
socketserver.
ThreadingUDPServer
¶ Ces classes sont prédéfinies en utilisant les classes de mélange.
Pour implémenter un service, vous devez créer une classe héritant de BaseRequestHandler
et redéfinir sa méthode handle()
. Ensuite, vous pourrez créer différentes versions de votre service en combinant les classes serveurs avec votre classe de gestion des requêtes. Cette classe de gestion des requêtes doit être différente pour les services utilisant des datagrammes ou des flux de données. Cette contrainte peut être dissimulée en utilisant les classes de gestion dérivées StreamRequestHandler
ou DatagramRequestHandler
.
Bien entendu, vous devrez toujours utiliser votre tête ! Par exemple, utiliser un serveur utilisant des processus clonés (forking) n'aurait aucun sens si le serveur garde en mémoire des états pouvant être modifiés par les requêtes reçues. En effet, un processus enfant traitant une requête n'aurait alors aucun moyen de propager le nouvel état à son parent. Dans ce cas, vous devez utiliser un serveur utilisant des fils d'exécution, mais cela demande probablement d'utiliser des verrous pour protéger l’intégrité des données partagées.
D'un autre côté, si vous développez un serveur HTTP qui a toutes ses données stockées hors de la mémoire (sur un système de fichiers par exemple), une classe synchrone rendrait le service sourd à toute nouvelle requête aussi longtemps qu'une précédente soit en cours de traitement. Cette situation pourrait perdurer pendant un long moment si le client prend du temps à recevoir toutes les données demandées. Dans ce cas, un serveur utilisant des processus ou des fils d'exécutions est approprié.
Dans certains cas, il peut être judicieux de commencer à traiter une requête de façon synchrone mais de pouvoir déléguer le reste du traitement à un processus enfant si besoin. Ce comportement peut être implémenté en utilisant un serveur synchrone et en laissant à la méthode handle()
, de la classe gérant les requêtes, le soin de créer le processus enfant explicitement.
Une autre méthode pour gérer plusieurs requêtes simultanément dans un environnement ne prenant en charge ni les fils d’exécution ni fork()
(ou si cela est trop coûteux ou inapproprié compte tenu de la nature du service) est de maintenir une table des requêtes en cours de traitement et d’utiliser selectors
pour décider sur quelle requête travailler (et quand accepter une nouvelle requête). Cela est particulièrement important pour les services utilisant des flux de données où chaque client peut rester connecté pour longtemps. Pour une autre façon de gérer cela, voir asyncore
.
Objets serveur¶
-
class
socketserver.
BaseServer
(server_address, RequestHandlerClass)¶ Il s'agit de la classe parente de tous les objets serveur du module. Elle déclare l'interface, définie ci-dessous, mais laisse aux classes filles le soin d'implémenter la plupart des méthodes. Les deux paramètres sont stockés respectivement dans les attributs
server_address
etRequestHandlerClass
.-
fileno
()¶ Renvoie un entier représentant le descripteur de fichier pour le connecteur que le serveur écoute. Cette fonction est, la plupart du temps, passée à
selectors
afin de pouvoir surveiller plusieurs serveurs dans le même processus.
-
handle_request
()¶ Traite une seule requête. Cette fonction appelle, dans l'ordre, les méthodes
get_request()
,verify_request()
etprocess_request()
. Si la méthodehandle()
de la classe de gestion des requêtes lève une exception, alors la méthodehandle_error()
du serveur est appelée. Si aucune requête n'est reçue avant «timeout
» secondes,handle_timeout()
est appelée ethandle_request()
rend la main.
-
serve_forever
(poll_interval=0.5)¶ Gère les requêtes indéfiniment jusqu'à ce que
shutdown()
soit appelée. Vérifie si une demande d’arrêt (shutdown) a été émise toutes les poll_interval secondes. Ignore l'attributtimeout
. Appelle égalementservice_actions()
, qui peut être utilisée par une classe enfant ou une classe de mélange afin d'implémenter une action spécifique pour un service donné. Par exemple, la classeForkingMixIn
utiliseservice_actions()
pour supprimer les processus enfants zombies.Modifié dans la version 3.3: La méthode
serve_forever
appelle dorénavantservice_actions
.
-
service_actions
()¶ Cette méthode est appelée dans la boucle de
serve_forever()
. Cette méthode peut être surchargée par une classe fille ou une classe de mélange afin d'effectuer une action spécifique à un service donné, comme une action de nettoyage.Nouveau dans la version 3.3.
-
shutdown
()¶ Demande l'arrêt de la boucle de
serve_forever()
et attend jusqu'à ce que ce soit fait.shutdown()
doit être appelée dans un fil d’exécution différent deserve_forever()
sous peine d'interblocage.
-
server_close
()¶ Nettoie le serveur. Peut être surchargée.
-
address_family
¶ La famille de protocoles auquel le connecteur appartient. Les exemples les plus communs sont
socket.AF_INET
etsocket.AF_UNIX
.
-
RequestHandlerClass
¶ La classe de gestion des requêtes, fournie par l'utilisateur. Une instance de cette classe est créée pour chaque requête.
-
server_address
¶ Renvoie l’adresse sur laquelle le serveur écoute. Le format de l’adresse dépend de la famille de protocoles utilisée ; pour plus de détails, voir la documentation du module
socket
. Pour les protocoles Internet, cette valeur est une paire formée d'une chaine de caractère pour l’adresse et d'un entier pour le port. Exemple :('127.0.0.1', 80)
.
-
socket
¶ L'objet connecteur utilisé par le serveur pour écouter les nouvelles requêtes.
Les classes serveurs prennent en charge les variables de classe suivantes :
-
allow_reuse_address
¶ Indique si le serveur autorise la réutilisation d'une adresse. La valeur par défaut est
False
mais cela peut être changé dans les classes enfants.
-
request_queue_size
¶ La taille de la file des requêtes. Lorsque traiter une requête prend du temps, toute nouvelle requête arrivant pendant que le serveur est occupé est placé dans une file jusqu'à atteindre «
request_queue_size
» requêtes. Si la queue est pleine, les nouvelles requêtes clientes se voient renvoyer une erreur Connection denied. La valeur par défaut est habituellement 5 mais peut être changée par les classes filles.
-
socket_type
¶ Le type de connecteur utilisé par le serveur ;
socket.SOCK_STREAM
etsocket.SOCK_DGRAM
sont deux valeurs usuelles.
-
timeout
¶ Délai d'attente en secondes, ou
None
si aucune limite n'est demandée. Sihandle_request()
ne reçoit aucune requête entrante pendant le délai d'attente, la méthodehandle_timeout()
est appelée.
Il existe plusieurs méthodes serveur pouvant être surchargées par des classes dérivant de classes de base comme
TCPServer
; ces méthodes ne sont pas utiles aux utilisateurs externes de l'objet serveur.-
finish_request
(request, client_address)¶ Méthode en charge de traiter la requête en instanciant
RequestHandlerClass
et en appelant sa méthodehandle()
.
-
get_request
()¶ Accepte obligatoirement une requête depuis le connecteur et renvoie une paire contenant le nouvel objet connecteur utilisé pour communiquer avec le client et l'adresse du client.
-
handle_error
(request, client_address)¶ Cette fonction est appelée si la méthode
handle()
de l'objetRequestHandlerClass
lève une exception. Par défaut, la méthode imprime la trace d'appels sur la sortie d'erreur standard et continue de traiter les requêtes suivantes.Modifié dans la version 3.6: N'est maintenant appelée que sur les exceptions dérivant de la classe
Exception
.
-
handle_timeout
()¶ Cette fonction est appelée lorsque l'attribut
timeout
est réglé à autre chose queNone
et que le délai d'attente expire sans qu'aucune requête ne soit reçue. Par défaut, cette fonction récupère le statut de tous les processus enfants ayant terminé pour les serveurs utilisant des processus ou ne fait rien pour le cas des serveurs utilisant des fils d’exécution.
-
process_request
(request, client_address)¶ Appelle
finish_request()
pour instancierRequestHandlerClass
. Si désiré, cette fonction peut créer des processus fils ou des fils d’exécution pour traiter les requêtes ; les classes de mélangeForkingMixIn
etThreadingMixIn
implémentent cela.
-
server_activate
()¶ Appelée par le constructeur du serveur afin de l'activer. Le comportement par défaut pour un serveur TCP est de seulement invoquer
listen()
sur le connecteur du serveur. Peut être surchargée.
-
server_bind
()¶ Appelée par le constructeur du serveur afin d'assigner (bind) l'adresse requise au connecteur du serveur. Peut être surchargée.
-
verify_request
(request, client_address)¶ Doit renvoyer un booléen. Si la valeur est
True
, la requête sera traitée. Si la valeur estFalse
, la requête sera refusée. Cette fonction peut être surchargée afin d'implémenter une stratégie de contrôle d'accès au serveur. L'implémentation par défaut renvoie toujoursTrue
.
Modifié dans la version 3.6: La gestion du protocole context manager a été ajoutée. Sortir du gestionnaire de contexte revient à appeler
server_close()
.-
Objets gestionnaire de requêtes¶
-
class
socketserver.
BaseRequestHandler
¶ Classe de base de tous les objets gestionnaire de requêtes. Elle déclare l'interface, définie ci-dessous, pour tous les gestionnaires. Une implémentation concrète doit définir une nouvelle méthode
handle()
et peut surcharger n'importe quelle autre méthode. Cette classe concrète est instanciée pour chaque requête.-
setup
()¶ Appelée avant la méthode
handle()
afin d'effectuer toutes les opérations d'initialisation requises. L'implémentation par défaut ne fait rien.
-
handle
()¶ Cette fonction doit faire tout le nécessaire pour traiter une requête. L'implémentation par défaut ne fait rien. La fonction peut accéder à plusieurs attributs d'instance : la requête elle-même se trouve dans
self.request
, l'adresse du client dansself.client_address
et l'instance du serveur dansself.server
(dans le cas où il aurait besoin d'accéder aux informations du serveur).Le type de
self.request
est différent pour les services utilisant des datagrammes ou des flux de données. Pour les services à flux de données,self.request
renvoie l'objet connecteur ; pour les services à datagrammes,self.request
est une paire constituée d'une chaîne de caractères et du connecteur.
-
-
class
socketserver.
StreamRequestHandler
¶ -
class
socketserver.
DatagramRequestHandler
¶ These
BaseRequestHandler
subclasses override thesetup()
andfinish()
methods, and provideself.rfile
andself.wfile
attributes. Theself.rfile
andself.wfile
attributes can be read or written, respectively, to get the request data or return data to the client. Therfile
attributes support theio.BufferedIOBase
readable interface, andwfile
attributes support theio.BufferedIOBase
writable interface.Modifié dans la version 3.6:
StreamRequestHandler.wfile
prend également en charge l'interface d'écriture deio.BufferedIOBase
.
Exemples¶
Exemple pour socketserver.TCPServer
¶
Implémentation côté serveur :
import socketserver
class MyTCPHandler(socketserver.BaseRequestHandler):
"""
The request handler class for our server.
It is instantiated once per connection to the server, and must
override the handle() method to implement communication to the
client.
"""
def handle(self):
# self.request is the TCP socket connected to the client
self.data = self.request.recv(1024).strip()
print("{} wrote:".format(self.client_address[0]))
print(self.data)
# just send back the same data, but upper-cased
self.request.sendall(self.data.upper())
if __name__ == "__main__":
HOST, PORT = "localhost", 9999
# Create the server, binding to localhost on port 9999
with socketserver.TCPServer((HOST, PORT), MyTCPHandler) as server:
# Activate the server; this will keep running until you
# interrupt the program with Ctrl-C
server.serve_forever()
Une implémentation alternative du gestionnaire de requêtes utilisant les flux de données (avec des objets fichier-compatibles simplifiant la communication en fournissant l'interface fichier standard) :
class MyTCPHandler(socketserver.StreamRequestHandler):
def handle(self):
# self.rfile is a file-like object created by the handler;
# we can now use e.g. readline() instead of raw recv() calls
self.data = self.rfile.readline().strip()
print("{} wrote:".format(self.client_address[0]))
print(self.data)
# Likewise, self.wfile is a file-like object used to write back
# to the client
self.wfile.write(self.data.upper())
La différence est que l'appel à readline()
dans le second gestionnaire permet d'appeler recv()
jusqu'à rencontrer un caractère de fin de ligne alors que dans le premier gestionnaire appelle directement recv()
, renvoyant toutes les données envoyées par le client en un seul appel à sendall()
.
Implémentation côté client :
import socket
import sys
HOST, PORT = "localhost", 9999
data = " ".join(sys.argv[1:])
# Create a socket (SOCK_STREAM means a TCP socket)
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
# Connect to server and send data
sock.connect((HOST, PORT))
sock.sendall(bytes(data + "\n", "utf-8"))
# Receive data from the server and shut down
received = str(sock.recv(1024), "utf-8")
print("Sent: {}".format(data))
print("Received: {}".format(received))
La sortie de cet exemple devrait ressembler à ça :
Serveur :
$ python TCPServer.py
127.0.0.1 wrote:
b'hello world with TCP'
127.0.0.1 wrote:
b'python is nice'
Client :
$ python TCPClient.py hello world with TCP
Sent: hello world with TCP
Received: HELLO WORLD WITH TCP
$ python TCPClient.py python is nice
Sent: python is nice
Received: PYTHON IS NICE
Exemple pour socketserver.UDPServer
¶
Implémentation côté serveur :
import socketserver
class MyUDPHandler(socketserver.BaseRequestHandler):
"""
This class works similar to the TCP handler class, except that
self.request consists of a pair of data and client socket, and since
there is no connection the client address must be given explicitly
when sending data back via sendto().
"""
def handle(self):
data = self.request[0].strip()
socket = self.request[1]
print("{} wrote:".format(self.client_address[0]))
print(data)
socket.sendto(data.upper(), self.client_address)
if __name__ == "__main__":
HOST, PORT = "localhost", 9999
with socketserver.UDPServer((HOST, PORT), MyUDPHandler) as server:
server.serve_forever()
Implémentation côté client :
import socket
import sys
HOST, PORT = "localhost", 9999
data = " ".join(sys.argv[1:])
# SOCK_DGRAM is the socket type to use for UDP sockets
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# As you can see, there is no connect() call; UDP has no connections.
# Instead, data is directly sent to the recipient via sendto().
sock.sendto(bytes(data + "\n", "utf-8"), (HOST, PORT))
received = str(sock.recv(1024), "utf-8")
print("Sent: {}".format(data))
print("Received: {}".format(received))
La sortie de cet exemple devrait ressembler exactement à la sortie de l'exemple pour le serveur TCP.
Classes de mélange asynchrone¶
Pour développer des gestionnaires asynchrones, utilisez les classes ThreadingMixIn
et ForkingMixIn
.
Exemple pour ThreadingMixIn
:
import socket
import threading
import socketserver
class ThreadedTCPRequestHandler(socketserver.BaseRequestHandler):
def handle(self):
data = str(self.request.recv(1024), 'ascii')
cur_thread = threading.current_thread()
response = bytes("{}: {}".format(cur_thread.name, data), 'ascii')
self.request.sendall(response)
class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
pass
def client(ip, port, message):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
sock.connect((ip, port))
sock.sendall(bytes(message, 'ascii'))
response = str(sock.recv(1024), 'ascii')
print("Received: {}".format(response))
if __name__ == "__main__":
# Port 0 means to select an arbitrary unused port
HOST, PORT = "localhost", 0
server = ThreadedTCPServer((HOST, PORT), ThreadedTCPRequestHandler)
with server:
ip, port = server.server_address
# Start a thread with the server -- that thread will then start one
# more thread for each request
server_thread = threading.Thread(target=server.serve_forever)
# Exit the server thread when the main thread terminates
server_thread.daemon = True
server_thread.start()
print("Server loop running in thread:", server_thread.name)
client(ip, port, "Hello World 1")
client(ip, port, "Hello World 2")
client(ip, port, "Hello World 3")
server.shutdown()
La sortie de cet exemple devrait ressembler à ça :
$ python ThreadedTCPServer.py
Server loop running in thread: Thread-1
Received: Thread-2: Hello World 1
Received: Thread-3: Hello World 2
Received: Thread-4: Hello World 3
La classe ForkingMixIn
est utilisable de la même façon à la différence près que le serveur crée un nouveau processus fils pour chaque requête. Disponible uniquement sur les plateformes POSIX prenant en charge fork()
.