socketserver
— Un framework para servidores de red¶
Código fuente: Lib/socketserver.py
El módulo socketserver
simplifica la tarea de escribir servidores de red.
Hay cuatro clases básicas de servidores concretos:
-
class
socketserver.
TCPServer
(server_address, RequestHandlerClass, bind_and_activate=True)¶ Utiliza el protocolo TCP de Internet, que proporciona flujos continuos de datos entre el cliente y el servidor. Si bind_and_activate es verdadero, el constructor automáticamente intenta invocar a
server_bind()
yserver_activate()
. Los otros parámetros se pasan a la clase baseBaseServer
.
-
class
socketserver.
UDPServer
(server_address, RequestHandlerClass, bind_and_activate=True)¶ Esto utiliza datagramas, que son paquetes discretos de información que pueden llegar fuera de servicio o perderse durante el tránsito. Los parámetros son los mismos que para
TCPServer
.
-
class
socketserver.
UnixStreamServer
(server_address, RequestHandlerClass, bind_and_activate=True)¶ -
class
socketserver.
UnixDatagramServer
(server_address, RequestHandlerClass, bind_and_activate=True)¶ Estas clases que se usan con menos frecuencia son similares a las clases TCP y UDP, pero usan sockets de dominio Unix; no están disponibles en plataformas que no sean Unix. Los parámetros son los mismos que para
TCPServer
.
Estas cuatro clases procesan solicitudes sincrónicamente; cada solicitud debe completarse antes de que se pueda iniciar la siguiente. Esto no es adecuado si cada solicitud tarda mucho en completarse, porque requiere mucho cálculo o porque retorna muchos datos que el cliente tarda en procesar. La solución es crear un proceso o hilo independiente para manejar cada solicitud; las clases mixtas ForkingMixIn
y ThreadingMixIn
pueden usarse para soportar el comportamiento asincrónico.
La creación de un servidor requiere varios pasos. Primero, debes crear una clase de controlador de solicitudes subclasificando la clase BaseRequestHandler
y anulando su método handle()
; este método procesará las solicitudes entrantes. En segundo lugar, debe crear una instancia de una de las clases de servidor, pasándole la dirección del servidor y la clase del controlador de solicitudes. Se recomienda utilizar el servidor en una declaración with
. Luego llame al método handle_request()
o serve_forever()
del objeto de servidor para procesar una o muchas solicitudes. Finalmente, llame a server_close()
para cerrar el socket (a menos que haya usado una with
declaración).
Al heredar de ThreadingMixIn
para el comportamiento de la conexión con subprocesos, debe declarar explícitamente cómo desea que se comporten sus subprocesos en un cierre abrupto. La clase ThreadingMixIn
define un atributo daemon_threads, que indica si el servidor debe esperar o no la terminación del hilo. Debe establecer la bandera explícitamente si desea que los subprocesos se comporten de forma autónoma; el valor predeterminado es False
, lo que significa que Python no se cerrará hasta que todos los hilos creados por ThreadingMixIn
hayan salido.
Las clases de servidor tienen los mismos métodos y atributos externos, independientemente del protocolo de red que utilicen.
Notas de creación del servidor¶
Hay cinco clases en un diagrama de herencia, cuatro de las cuales representan servidores síncronos de cuatro tipos:
+------------+
| BaseServer |
+------------+
|
v
+-----------+ +------------------+
| TCPServer |------->| UnixStreamServer |
+-----------+ +------------------+
|
v
+-----------+ +--------------------+
| UDPServer |------->| UnixDatagramServer |
+-----------+ +--------------------+
Tenga en cuenta que UnixDatagramServer
deriva de UDPServer
, no de UnixStreamServer
— la única diferencia entre una IP y un servidor de flujo Unix es la familia de direcciones, que simplemente se repite en ambos Clases de servidor Unix.
-
class
socketserver.
ForkingMixIn
¶ -
class
socketserver.
ThreadingMixIn
¶ Se pueden crear versiones de Forking y threading de cada tipo de servidor utilizando estas clases mixtas. Por ejemplo,
ThreadingUDPServer
se crea de la siguiente manera:class ThreadingUDPServer(ThreadingMixIn, UDPServer): pass
La clase de mezcla es lo primero, ya que anula un método definido en
UDPServer
. La configuración de los distintos atributos también cambia el comportamiento del mecanismo del servidor subyacente.ForkingMixIn
y las clases de Forking mencionadas a continuación solo están disponibles en plataformas POSIX que admitenos.fork()
.socketserver.ForkingMixIn.server_close()
espera hasta que se completen todos los procesos secundarios, excepto sisocketserver.ForkingMixIn.block_on_close
atributo es falso.socketserver.ThreadingMixIn.server_close()
espera hasta que se completen todos los subprocesos que no son demonios, excepto si el atributosocketserver.ThreadingMixIn.block_on_close
es falso. Use subprocesos demoníacos configurandoThreadingMixIn.daemon_threads
enVerdadero
para no esperar hasta que se completen los subprocesos.Distinto en la versión 3.7:
socketserver.ForkingMixIn.server_close()
ysocketserver.ThreadingMixIn.server_close()
ahora espera hasta que se completen todos los procesos secundarios y los subprocesos no demoníacos. Agregue un nuevo atributo de clasesocketserver.ForkingMixIn.block_on_close
para optar por el comportamiento anterior a 3.7.
-
class
socketserver.
ForkingTCPServer
¶ -
class
socketserver.
ForkingUDPServer
¶ -
class
socketserver.
ThreadingTCPServer
¶ -
class
socketserver.
ThreadingUDPServer
¶ Estas clases están predefinidas utilizando las clases mixtas.
Para implementar un servicio, debes derivar una clase de BaseRequestHandler
y redefinir su método handle()
. Luego, puede ejecutar varias versiones del servicio combinando una de las clases de servidor con su clase de controlador de solicitudes. La clase del controlador de solicitudes debe ser diferente para los servicios de datagramas o flujos. Esto se puede ocultar usando las subclases del controlador StreamRequestHandler
o DatagramRequestHandler
.
Por supuesto, ¡todavía tienes que usar la cabeza! Por ejemplo, no tiene sentido usar un servidor de bifurcación si el servicio contiene un estado en la memoria que puede ser modificado por diferentes solicitudes, ya que las modificaciones en el proceso hijo nunca alcanzarían el estado inicial que se mantiene en el proceso padre y se pasa a cada hijo. En este caso, puede usar un servidor de subprocesos, pero probablemente tendrá que usar bloqueos para proteger la integridad de los datos compartidos.
Por otro lado, si está creando un servidor HTTP donde todos los datos se almacenan externamente (por ejemplo, en el sistema de archivos), una clase síncrona esencialmente hará que el servicio sea «sordo» mientras se maneja una solicitud, que puede ser durante mucho tiempo si un cliente tarda en recibir todos los datos que ha solicitado. Aquí es apropiado un servidor de enhebrado o bifurcación.
En algunos casos, puede ser apropiado procesar parte de una solicitud de forma síncrona, pero para finalizar el procesamiento en un hijo bifurcado según los datos de la solicitud. Esto se puede implementar usando un servidor sincrónico y haciendo un fork explicito en la clase del controlador de solicitudes el método handle()
.
Otro enfoque para manejar múltiples solicitudes simultáneas en un entorno que no admite subprocesos ni fork()
(o donde estos son demasiado costosos o inapropiados para el servicio) es mantener una tabla explícita de solicitudes parcialmente terminadas y utilizar selectors
para decidir en qué solicitud trabajar a continuación (o si manejar una nueva solicitud entrante). Esto es particularmente importante para los servicios de transmisión en los que cada cliente puede potencialmente estar conectado durante mucho tiempo (si no se pueden utilizar subprocesos o subprocesos). Consulte asyncore
para ver otra forma de gestionar esto.
Objetos de servidor¶
-
class
socketserver.
BaseServer
(server_address, RequestHandlerClass)¶ Esta es la superclase de todos los objetos de servidor en el módulo. Define la interfaz, que se indica a continuación, pero no implementa la mayoría de los métodos, que se realiza en subclases. Los dos parámetros se almacenan en los atributos respectivos
server_address
yRequestHandlerClass
.-
fileno
()¶ Retorna un descriptor de archivo entero para el socket en el que escucha el servidor. Esta función se pasa comúnmente a
selectors
, para permitir monitorear múltiples servidores en el mismo proceso.
-
handle_request
()¶ Procese una sola solicitud. Esta función llama a los siguientes métodos en orden:
get_request()
,verify_request()
, yprocess_request()
. Si el método proporcionado por el usuariohandle()
de la clase del controlador lanza una excepción, se llamará al métodohandle_error()
del servidor. Si no se recibe ninguna solicitud entimeout
segundos, se llamará ahandle_timeout()
yhandle_request()
regresará.
-
serve_forever
(poll_interval=0.5)¶ Manejar solicitudes hasta una solicitud explícita
shutdown()
. Sondeo de apagado cada poll_interval segundos. Ignora el atributotimeout
. También llamaservice_actions()
, que puede ser utilizado por una subclase o mixin para proporcionar acciones específicas para un servicio dado. Por ejemplo, la claseForkingMixIn
usaservice_actions()
para limpiar procesos secundarios zombies.Distinto en la versión 3.3: Se agregó la llamada
service_actions
al métodoserve_forever
.
-
service_actions
()¶ Esto se llama en el ciclo
serve_forever()
. Este método puede ser reemplazado por subclases o clases mixtas para realizar acciones específicas para un servicio dado, como acciones de limpieza.Nuevo en la versión 3.3.
-
shutdown
()¶ Dile al bucle
serve_forever()
que se detenga y espere hasta que lo haga.shutdown()
debe llamarse mientrasserve_forever()
se está ejecutando en un hilo diferente, de lo contrario, se bloqueará.
-
server_close
()¶ Limpiar el servidor. Puede ser sobrescrito.
-
address_family
¶ La familia de protocolos a la que pertenece el socket del servidor. Algunos ejemplos comunes son
socket.AF_INET
ysocket.AF_UNIX
.
-
RequestHandlerClass
¶ La clase de controlador de solicitudes proporcionada por el usuario; se crea una instancia de esta clase para cada solicitud.
-
server_address
¶ La dirección en la que escucha el servidor. El formato de las direcciones varía según la familia de protocolos; consulte la documentación del módulo
socket
para obtener más detalles. Para los protocolos de Internet, esta es una tupla que contiene una cadena que proporciona la dirección y un número de puerto entero:('127.0.0.1', 80)
, por ejemplo.
-
socket
¶ El objeto de socket en el que el servidor escuchará las solicitudes entrantes.
Las clases de servidor admiten las siguientes variables de clase:
-
allow_reuse_address
¶ Si el servidor permitirá la reutilización de una dirección. Este valor predeterminado es
False
, y se puede establecer en subclases para cambiar la política.
-
request_queue_size
¶ El tamaño de la cola de solicitudes. Si toma mucho tiempo procesar una sola solicitud, cualquier solicitud que llegue mientras el servidor está ocupado se coloca en una cola, hasta
request_queue_size
. Una vez que la cola está llena, más solicitudes de clientes obtendrán un error de «Conexión denegada». El valor predeterminado suele ser 5, pero las subclases pueden anularlo.
-
socket_type
¶ El tipo de socket utilizado por el servidor;
socket.SOCK_STREAM
ysocket.SOCK_DGRAM
son dos valores comunes.
-
timeout
¶ Duración del tiempo de espera, medida en segundos, o
None
si no se desea un tiempo de espera. Sihandle_request()
no recibe solicitudes entrantes dentro del período de tiempo de espera, se llama al métodohandle_timeout()
.
Hay varios métodos de servidor que pueden ser anulados por subclases de clases de servidor base como
TCPServer
; estos métodos no son útiles para los usuarios externos del objeto de servidor.-
finish_request
(request, client_address)¶ En realidad, procesa la solicitud creando instancias
RequestHandlerClass
y llamando a su métodohandle()
.
-
get_request
()¶ Debe aceptar una solicitud del socket y retornar una tupla de 2 que contenga el objeto de socket nuevo que se utilizará para comunicarse con el cliente y la dirección del cliente.
-
handle_error
(request, client_address)¶ Esta función se llama si el método
handle()
de una instanciaRequestHandlerClass
lanza una excepción. La acción predeterminada es imprimir el rastreo al error estándar y continuar manejando más solicitudes.Distinto en la versión 3.6: Ahora solo se solicitan excepciones derivadas de la clase
Exception
.
-
handle_timeout
()¶ Esta función se llama cuando el atributo
timeout
se ha establecido en un valor distinto deNone
y el tiempo de espera ha pasado sin que se reciban solicitudes. La acción predeterminada para los servidores de forking es recopilar el estado de cualquier proceso hijo que haya salido, mientras que en los servidores de threading este método no hace nada.
-
process_request
(request, client_address)¶ Llama a
finish_request()
para crear una instancia deRequestHandlerClass
. Si lo desea, esta función puede crear un nuevo proceso o hilo para manejar la solicitud; las clasesForkingMixIn
yThreadingMixIn
hacen esto.
-
server_activate
()¶ Lo llama el constructor del servidor para activar el servidor. El comportamiento predeterminado para un servidor TCP simplemente invoca
listen()
en el socket del servidor. Puede anularse.
-
server_bind
()¶ Lo llama el constructor del servidor para vincular el socket a la dirección deseada. Puede anularse.
-
verify_request
(request, client_address)¶ Debe retornar un valor booleano; si el valor es
True
, la solicitud se procesará, y si esFalse
, la solicitud será denegada. Esta función se puede anular para implementar controles de acceso para un servidor. La implementación predeterminada siempre retornaTrue
.
Distinto en la versión 3.6: Se agregó soporte para el protocolo context manager. Salir del administrador de contexto es equivalente a llamar a
server_close()
.-
Solicitar objetos de controlador¶
-
class
socketserver.
BaseRequestHandler
¶ Esta es la superclase de todos los objetos de manejo de solicitudes. Define la interfaz, que se muestra a continuación. Una subclase de controlador de solicitudes concreta debe definir un nuevo método
handle()
y puede anular cualquiera de los otros métodos. Se crea una nueva instancia de la subclase para cada solicitud.-
setup
()¶ Se llama antes del método
handle()
para realizar las acciones de inicialización necesarias. La implementación predeterminada no hace nada.
-
handle
()¶ Esta función debe realizar todo el trabajo necesario para atender una solicitud. La implementación predeterminada no hace nada. Dispone de varios atributos de instancia; la solicitud está disponible como
self.request
; la dirección del cliente comoself.client_address
; y la instancia del servidor comoself.server
, en caso de que necesite acceder a la información por servidor.El tipo de
self.request
es diferente para datagramas o servicios de flujo. Para los servicios de transmisión,self.request
es un objeto de socket; para servicios de datagramas,self.request
es un par de string y socket.
-
-
class
socketserver.
StreamRequestHandler
¶ -
class
socketserver.
DatagramRequestHandler
¶ Estas subclases
BaseRequestHandler
anulan los métodossetup()
yfinish()
, y proporcionanself.rfile
yself.wfile
atributos. Los atributosself.rfile
yself.wfile
se pueden leer o escribir, respectivamente, para obtener los datos de la solicitud o retornar los datos al cliente.Los atributos
rfile
de ambas clases admiten la interfaz legibleio.BufferedIOBase
, yDatagramRequestHandler.wfile
admite laio.BufferedIOBase
interfaz de escritura.Distinto en la versión 3.6:
StreamRequestHandler.wfile
también admite la interfaz de escrituraio.BufferedIOBase
.
Ejemplos¶
socketserver.TCPServer
Ejemplo¶
Este es el lado del servidor:
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()
Una clase de controlador de solicitudes alternativa que hace uso de secuencias (objetos similares a archivos que simplifican la comunicación al proporcionar la interfaz de archivo estándar):
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 diferencia es que la llamada readline()
en el segundo controlador llamará a recv()
varias veces hasta que encuentre un carácter de nueva línea, mientras que la llamada única recv()
en el primer controlador simplemente retornará lo que se ha enviado desde el cliente en una llamada sendall()
.
Este es el lado del cliente:
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 salida del ejemplo debería verse así:
Servidor:
$ python TCPServer.py
127.0.0.1 wrote:
b'hello world with TCP'
127.0.0.1 wrote:
b'python is nice'
Cliente:
$ 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
socketserver.UDPServer
Ejemplo¶
Este es el lado del servidor:
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()
Este es el lado del cliente:
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 salida del ejemplo debería verse exactamente como en el ejemplo del servidor TCP.
Mixins asincrónicos¶
Para construir controladores asincrónicos, use las clases ThreadingMixIn
y ForkingMixIn
.
Un ejemplo para la clase ThreadingMixIn
class
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 salida del ejemplo debería verse así:
$ 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 clase ForkingMixIn
se usa de la misma manera, excepto que el servidor generará un nuevo proceso para cada solicitud. Disponible solo en plataformas POSIX que admitan fork()
.