socketserver — Creación de servidores de red¶
Propósito: | Creación de servidores de red. |
---|
El módulo socketserver
es un marco para crear servidores de red. Define
clases para manejar solicitudes de red síncronas (el controlador de solicitudes
del servidor se bloquea hasta que se complete la solicitud) sobre TCP, UDP,
flujos Unix y datagramas Unix. También proporciona clases de mezcla para
convertir fácilmente servidores para usar un hilo separado o proceso para cada
solicitud.
La responsabilidad de procesar una solicitud se divide entre una clase servidor y una clase manejador de solicitudes. El servidor se ocupa de la comunicación, como escuchar en un conector y aceptar conexiones, y el controlador de solicitudes se ocupa del «protocolo» como la interpretación de los datos entrantes, su procesamiento y envío de vuelta al cliente. Esta división de responsabilidad significa que muchas aplicaciones pueden usar una de las clases de servidor existentes sin ninguna modificación, y proporcionar una clase manejador de solicitudes para que funcione con el protocolo personalizado.
Tipos de servidor¶
Hay cinco clases de servidor diferentes definidas en socketserver
.
BaseServer
define la interfaz de programación, y no está destinado a ser
instanciado y utilizado directamente. TCPServer
utiliza conectores TCP/IP
para comunicarse. UDPServer
usa conectores de datagramas.
UnixStreamServer
y UnixDatagramServer
usan conectores de dominio Unix y
solo están disponibles en plataformas Unix.
Objetos servidor¶
Para construir un servidor, pásale una dirección en la que escuchar solicitudes
y una clase (no instancia) manejador de solicitudes. El formato de dirección
depende del tipo de servidor y la familia de conector que se utiliza.
Revisa a la documentación del módulo socket
para más detalles.
Una vez que se crea una instancia del objeto servidor, usa handle_request()
o serve_forever()
para procesar las peticiones. El método
serve_forever()
llama handle_request()
en un bucle infinito, pero si
una aplicación necesita integrar el servidor con otro bucle de eventos o usar
select()
para monitorear varios conectores para diferentes servidores,
puede llamar a handle_request()
directamente.
Implementar un servidor¶
Al crear un servidor, normalmente es suficiente reutilizar uno de las clases
existentes y proporcionar una clase de controlador de solicitud personalizada.
Para otros casos, BaseServer
incluye varios métodos que pueden ser anulados
en una subclase.
verify_request(request, client_address)
: DevuelveTrue
para procesar la solicitud oFalse
para ignorarla. Por ejemplo, un servidor podría rechazar las solicitudes de un rango de IP o si está sobrecargado.process_request(request, client_address)
: Llama afinish_request()
para hacer el trabajo de manejar la solicitud. También puede crear un hilo o proceso separado, como lo hacen las clases de mezcla.finish_request(request, client_address)
: Crea una instancia de controlador de solicitud utilizando la clase dada al constructor de os servidores. Llama ahandle()
en el controlador de solicitudes para procesar la solicitud.
Manejadores de solicitudes¶
Los manejadores de solicitudes realizan la mayor parte del trabajo de recibir solicitudes entrantes y decidir qué acción tomar. El manejador es responsable de implementar el protocolo en la parte superior de la capa de conector (es decir, HTTP, XML-RPC, o AMQP). El manejador de solicitudes lee la solicitud del canal de datos entrantes, lo procesa y escribe una respuesta de salida. Hay tres métodos disponibles para ser superado.
setup()
: prepara el manejador de solicitudes para la solicitud. En elStreamRequestHandler
el métodosetup()
crea objetos tipo archivo para leer y escribir en el conector.handle()
: Realiza el trabajo real para la solicitud. Analizar la solicitud entrante procesar los datos, y enviar una respuesta.finish()
: Limpia cualquier cosa creada durantesetup()
.
Muchos controladores se pueden implementar con solo un método handle()
.
Ejemplo de eco¶
Este ejemplo implementa un par simple servidor/manejador de solicitudes que acepta conexiones TCP y devuelve cualquier información enviada por el cliente. Comienza con el manejador de solicitudes.
import logging
import sys
import socketserver
logging.basicConfig(level=logging.DEBUG,
format='%(name)s: %(message)s',
)
class EchoRequestHandler(socketserver.BaseRequestHandler):
def __init__(self, request, client_address, server):
self.logger = logging.getLogger('EchoRequestHandler')
self.logger.debug('__init__')
socketserver.BaseRequestHandler.__init__(self, request,
client_address,
server)
return
def setup(self):
self.logger.debug('setup')
return socketserver.BaseRequestHandler.setup(self)
def handle(self):
self.logger.debug('handle')
# Echo the back to the client
data = self.request.recv(1024)
self.logger.debug('recv()->"%s"', data)
self.request.send(data)
return
def finish(self):
self.logger.debug('finish')
return socketserver.BaseRequestHandler.finish(self)
El único método que realmente necesita ser implementado es
EchoRequestHandler.handle()
, pero las versiones de todos los los métodos
descritos anteriormente se incluyen para ilustrar la secuencia de llamadas
hechas. La clase EchoServer
no hace nada diferente de TCPServer
,
excepto el registro cuando cada método es llamado.
class EchoServer(socketserver.TCPServer):
def __init__(self, server_address,
handler_class=EchoRequestHandler,
):
self.logger = logging.getLogger('EchoServer')
self.logger.debug('__init__')
socketserver.TCPServer.__init__(self, server_address,
handler_class)
return
def server_activate(self):
self.logger.debug('server_activate')
socketserver.TCPServer.server_activate(self)
return
def serve_forever(self, poll_interval=0.5):
self.logger.debug('waiting for request')
self.logger.info(
'Handling requests, press <Ctrl-C> to quit'
)
socketserver.TCPServer.serve_forever(self, poll_interval)
return
def handle_request(self):
self.logger.debug('handle_request')
return socketserver.TCPServer.handle_request(self)
def verify_request(self, request, client_address):
self.logger.debug('verify_request(%s, %s)',
request, client_address)
return socketserver.TCPServer.verify_request(
self, request, client_address,
)
def process_request(self, request, client_address):
self.logger.debug('process_request(%s, %s)',
request, client_address)
return socketserver.TCPServer.process_request(
self, request, client_address,
)
def server_close(self):
self.logger.debug('server_close')
return socketserver.TCPServer.server_close(self)
def finish_request(self, request, client_address):
self.logger.debug('finish_request(%s, %s)',
request, client_address)
return socketserver.TCPServer.finish_request(
self, request, client_address,
)
def close_request(self, request_address):
self.logger.debug('close_request(%s)', request_address)
return socketserver.TCPServer.close_request(
self, request_address,
)
def shutdown(self):
self.logger.debug('shutdown()')
return socketserver.TCPServer.shutdown(self)
El último paso es agregar un programa principal que configure el servidor para que se ejecute en un hilo, y le envía datos para ilustrar qué métodos se llaman cuando se repiten los datos.
if __name__ == '__main__':
import socket
import threading
address = ('localhost', 0) # let the kernel assign a port
server = EchoServer(address, EchoRequestHandler)
ip, port = server.server_address # what port was assigned?
# Start the server in a thread
t = threading.Thread(target=server.serve_forever)
t.setDaemon(True) # don't hang on exit
t.start()
logger = logging.getLogger('client')
logger.info('Server on %s:%s', ip, port)
# Connect to the server
logger.debug('creating socket')
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
logger.debug('connecting to server')
s.connect((ip, port))
# Send the data
message = 'Hello, world'.encode()
logger.debug('sending data: %r', message)
len_sent = s.send(message)
# Receive a response
logger.debug('waiting for response')
response = s.recv(len_sent)
logger.debug('response from server: %r', response)
# Clean up
server.shutdown()
logger.debug('closing socket')
s.close()
logger.debug('done')
server.socket.close()
Ejecutar el programa produce la siguiente salida.
$ python3 socketserver_echo.py
EchoServer: __init__
EchoServer: server_activate
EchoServer: waiting for request
EchoServer: Handling requests, press <Ctrl-C> to quit
client: Server on 127.0.0.1:55484
client: creating socket
client: connecting to server
client: sending data: b'Hello, world'
EchoServer: verify_request(<socket.socket fd=7, family=AddressFamily
.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1',
55484), raddr=('127.0.0.1', 55485)>, ('127.0.0.1', 55485))
EchoServer: process_request(<socket.socket fd=7, family=AddressFamil
y.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1',
55484), raddr=('127.0.0.1', 55485)>, ('127.0.0.1', 55485))
EchoServer: finish_request(<socket.socket fd=7, family=AddressFamily
.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1',
55484), raddr=('127.0.0.1', 55485)>, ('127.0.0.1', 55485))
EchoRequestHandler: __init__
EchoRequestHandler: setup
EchoRequestHandler: handle
client: waiting for response
EchoRequestHandler: recv()->"b'Hello, world'"
EchoRequestHandler: finish
client: response from server: b'Hello, world'
EchoServer: shutdown()
EchoServer: close_request(<socket.socket fd=7, family=AddressFamily.
AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 5
5484), raddr=('127.0.0.1', 55485)>)
client: closing socket
client: done
Nota
El número de puerto utilizado cambiará cada vez que se ejecute el programa
porque el kernel asigna un puerto disponible automáticamente. Para hacer
que el servidor escuche en un puerto específico cada vez, provea que número
en la tupla de direcciones en lugar de 0
.
Aquí hay una versión condensada del mismo servidor, sin el registro llamadas.
Solo el método handle()
en la clase de manejador de solicitudes necesita
ser proporcionado.
import socketserver
class EchoRequestHandler(socketserver.BaseRequestHandler):
def handle(self):
# Echo the back to the client
data = self.request.recv(1024)
self.request.send(data)
return
if __name__ == '__main__':
import socket
import threading
address = ('localhost', 0) # let the kernel assign a port
server = socketserver.TCPServer(address, EchoRequestHandler)
ip, port = server.server_address # what port was assigned?
t = threading.Thread(target=server.serve_forever)
t.setDaemon(True) # don't hang on exit
t.start()
# Connect to the server
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((ip, port))
# Send the data
message = 'Hello, world'.encode()
print('Sending : {!r}'.format(message))
len_sent = s.send(message)
# Receive a response
response = s.recv(len_sent)
print('Received: {!r}'.format(response))
# Clean up
server.shutdown()
s.close()
server.socket.close()
En este caso, no se requiere una clase de servidor especial ya que
TCPServer
maneja todos los requerimientos del servidor.
$ python3 socketserver_echo_simple.py
Sending : b'Hello, world'
Received: b'Hello, world'
Cread hilos y bifurcar¶
Para agregar subprocesos o soporte de bifurcación a un servidor, incluye la
mezcla apropiada en la jerarquía de clases para el servidor. Las cases de
mezcla anulan process_request()
para iniciar un nuevo hilo o proceso cuando
una solicitud está lista para ser manejada, y el trabajo se realiza en el nuevo
proceso hijo.
Para hilos, usa ThreadingMixIn
.
import threading
import socketserver
class ThreadedEchoRequestHandler(
socketserver.BaseRequestHandler,
):
def handle(self):
# Echo the back to the client
data = self.request.recv(1024)
cur_thread = threading.currentThread()
response = b'%s: %s' % (cur_thread.getName().encode(),
data)
self.request.send(response)
return
class ThreadedEchoServer(socketserver.ThreadingMixIn,
socketserver.TCPServer,
):
pass
if __name__ == '__main__':
import socket
address = ('localhost', 0) # let the kernel assign a port
server = ThreadedEchoServer(address,
ThreadedEchoRequestHandler)
ip, port = server.server_address # what port was assigned?
t = threading.Thread(target=server.serve_forever)
t.setDaemon(True) # don't hang on exit
t.start()
print('Server loop running in thread:', t.getName())
# Connect to the server
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((ip, port))
# Send the data
message = b'Hello, world'
print('Sending : {!r}'.format(message))
len_sent = s.send(message)
# Receive a response
response = s.recv(1024)
print('Received: {!r}'.format(response))
# Clean up
server.shutdown()
s.close()
server.socket.close()
La respuesta de este servidor con hilos incluye el identificador del hilo donde se maneja la solicitud.
$ python3 socketserver_threaded.py
Server loop running in thread: Thread-1
Sending : b'Hello, world'
Received: b'Thread-2: Hello, world'
Para procesos separados, use el ForkingMixIn
.
import os
import socketserver
class ForkingEchoRequestHandler(socketserver.BaseRequestHandler):
def handle(self):
# Echo the back to the client
data = self.request.recv(1024)
cur_pid = os.getpid()
response = b'%d: %s' % (cur_pid, data)
self.request.send(response)
return
class ForkingEchoServer(socketserver.ForkingMixIn,
socketserver.TCPServer,
):
pass
if __name__ == '__main__':
import socket
import threading
address = ('localhost', 0) # let the kernel assign a port
server = ForkingEchoServer(address,
ForkingEchoRequestHandler)
ip, port = server.server_address # what port was assigned?
t = threading.Thread(target=server.serve_forever)
t.setDaemon(True) # don't hang on exit
t.start()
print('Server loop running in process:', os.getpid())
# Connect to the server
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((ip, port))
# Send the data
message = 'Hello, world'.encode()
print('Sending : {!r}'.format(message))
len_sent = s.send(message)
# Receive a response
response = s.recv(1024)
print('Received: {!r}'.format(response))
# Clean up
server.shutdown()
s.close()
server.socket.close()
En este caso, el ID de proceso del hijo se incluye en la respuesta desde el servidor:
$ python3 socketserver_forking.py
Server loop running in process: 22599
Sending : b'Hello, world'
Received: b'22600: Hello, world'
Ver también
- Documentación de la biblioteca estándar para socketserver
socket
– Comunicación de red de bajo nivelselect
– Herramientas de E/S asíncronas de bajo nivelasyncio
– E/S asíncronas, bucle de eventos y herramientas de concurrenciaSimpleXMLRPCServer
– Servidor XML-RPC construido usandosocketserver
.- Unix Network Programming, Volume 1: The Sockets Networking API, 3/E Por W. Richard Stevens, Bill Fenner, y Andrew M. Rudoff. Publicado por Addison-Wesley Professional, 2004. ISBN-10: 0131411551
- Foundations of Python Network Programminng, 3/E Por Brandon Rhodes y John Goerzen. Publicado por Apress, 2014. ISBN-10: 1430258543