socketserver — A framework for network servers

Código-fonte: Lib/socketserver.py


O módulo socketserver simplifica a tarefa de escrever servidores de rede.

Existem quatro classes básicas de servidores concretos:

class socketserver.TCPServer(server_address, RequestHandlerClass, bind_and_activate=True)

Isso usa o protocolo TCP da internet, que fornece fluxos contínuos de dados entre o cliente e o servidor. Se bind_and_activate for true, o construtor tenta automaticamente invocar server_bind() e server_activate(). Os outros parâmetros são passados para a classe base BaseServer.

class socketserver.UDPServer(server_address, RequestHandlerClass, bind_and_activate=True)

Isso usa datagramas, que são pacotes discretos de informações que podem chegar fora de ordem ou ser perdidos durante o trânsito. Os parâmetros são os mesmos de TCPServer.

class socketserver.UnixStreamServer(server_address, RequestHandlerClass, bind_and_activate=True)
class socketserver.UnixDatagramServer(server_address, RequestHandlerClass, bind_and_activate=True)

Essas classes usadas com menos frequência são semelhantes às classes TCP e UDP, mas usam soquetes de domínio Unix; elas não estão disponíveis em plataformas não Unix. Os parâmetros são os mesmos de TCPServer.

Essas quatro classes processam solicitações synchronously; cada solicitação deve ser concluída antes que a próxima solicitação possa ser iniciada. Isso não é adequado se cada solicitação demorar muito para ser concluída, porque exige muita computação ou porque retorna muitos dados que o cliente demora a processar. A solução é criar um processo ou thread separado para lidar com cada solicitação; as classes misturadas ForkingMixIn e ThreadingMixIn podem ser usadas para oferecer suporte ao comportamento assíncrono.

Criar um servidor requer várias etapas. Primeiro, você deve criar uma classe de manipulador de solicitação estendendo a classe BaseRequestHandler e substituindo seu método handle(); esse método processará as solicitações recebidas. Segundo, você deve instanciar uma das classes de servidor, passando a ela o endereço do servidor e a classe de manipulador de solicitação. É recomendável usar o servidor em uma instrução with. Em seguida, chame o método handle_request() ou serve_forever() do objeto do servidor para processar uma ou muitas solicitações. Finalmente, chame server_close() para fechar o soquete (a menos que você tenha usado uma instrução with).

Ao herdar de ThreadingMixIn para comportamento de conexão em threads, você deve declarar explicitamente como quer que suas threads se comportem em um desligamento abrupto. A classe ThreadingMixIn define um atributo daemon_threads, que indica se o servidor deve ou não esperar pelo término da thread. Você deve definir o sinalizador explicitamente se quiser que as threads se comportem de forma autônoma; o padrão é False, o que significa que o Python não sairá até que todas as threads criadas por ThreadingMixIn tenham sido encerradas.

As classes de servidor têm os mesmos métodos e atributos externos, não importa qual protocolo de rede eles usam.

Notas sobre criação do servidor

Existem cinco classes em um diagrama de herança, quatro das quais representam servidores síncronos de quatro tipos:

+------------+
| BaseServer |
+------------+
      |
      v
+-----------+        +------------------+
| TCPServer |------->| UnixStreamServer |
+-----------+        +------------------+
      |
      v
+-----------+        +--------------------+
| UDPServer |------->| UnixDatagramServer |
+-----------+        +--------------------+

Observe que UnixDatagramServer deriva de UDPServer, não de UnixStreamServer — a única diferença entre um IP e um servidor Unix é a família de endereços.

class socketserver.ForkingMixIn
class socketserver.ThreadingMixIn

Versões de fork e threads de cada tipo de servidor podem ser criadas usando essas classes mix-in. Por exemplo, ThreadingUDPServer é criado da seguinte forma:

class ThreadingUDPServer(ThreadingMixIn, UDPServer):
    pass

A classe mix-in vem primeiro, pois ela substitui um método definido em UDPServer. Definir os vários atributos também altera o comportamento do mecanismo de servidor subjacente.

ForkingMixIn e as classes Forking mencionadas abaixo estão disponíveis apenas em plataformas POSIX que oferecem suporte a fork().

socketserver.ForkingMixIn.server_close() waits until all child processes complete, except if socketserver.ForkingMixIn.block_on_close attribute is false.

socketserver.ThreadingMixIn.server_close() waits until all non-daemon threads complete, except if socketserver.ThreadingMixIn.block_on_close attribute is false. Use daemonic threads by setting ThreadingMixIn.daemon_threads to True to not wait until threads complete.

Alterado na versão 3.7: socketserver.ForkingMixIn.server_close() and socketserver.ThreadingMixIn.server_close() now waits until all child processes and non-daemonic threads complete. Add a new socketserver.ForkingMixIn.block_on_close class attribute to opt-in for the pre-3.7 behaviour.

class socketserver.ForkingTCPServer
class socketserver.ForkingUDPServer
class socketserver.ThreadingTCPServer
class socketserver.ThreadingUDPServer

Essas classes são predefinidas usando as classes mix-in.

Para implementar um serviço, você deve derivar uma classe de BaseRequestHandler e redefinir seu método handle(). Você pode então executar várias versões do serviço combinando uma das classes de servidor com sua classe de manipulador de solicitação. A classe de manipulador de solicitação deve ser diferente para serviços de datagrama ou fluxo. Isso pode ser ocultado usando as subclasses de manipulador StreamRequestHandler ou DatagramRequestHandler.

Claro, você ainda tem que usar sua cabeça! Por exemplo, não faz sentido usar um servidor de Forking se o serviço contém estado na memória que pode ser modificado por diferentes solicitações, já que as modificações no processo filho nunca atingiriam o estado inicial mantido no processo pai e passado para cada filho. Neste caso, você pode usar um servidor de Threading, mas provavelmente terá que usar travas para proteger a integridade dos dados compartilhados.

Por outro lado, se você estiver construindo um servidor HTTP onde todos os dados são armazenados externamente (por exemplo, no sistema de arquivos), uma classe síncrona essencialmente tornará o serviço “surdo” enquanto uma solicitação estiver sendo manipulada – o que pode ser por um tempo muito longo se um cliente for lento para receber todos os dados que solicitou. Aqui, um servidor de Threading ou Forking é apropriado.

Em alguns casos, pode ser apropriado processar parte de uma solicitação de forma síncrona, mas terminar o processamento em um filho bifurcado dependendo dos dados da solicitação. Isso pode ser implementado usando um servidor síncrono e fazendo uma bifurcação explícita no método da classe do manipulador de solicitações handle().

Another approach to handling multiple simultaneous requests in an environment that supports neither threads nor fork() (or where these are too expensive or inappropriate for the service) is to maintain an explicit table of partially finished requests and to use selectors to decide which request to work on next (or whether to handle a new incoming request). This is particularly important for stream services where each client can potentially be connected for a long time (if threads or subprocesses cannot be used). See asyncore for another way to manage this.

Objetos Server

class socketserver.BaseServer(server_address, RequestHandlerClass)

Esta é a superclasse de todos os objetos Server no módulo. Ela define a interface, dada abaixo, mas não implementa a maioria dos métodos, o que é feito em subclasses. Os dois parâmetros são armazenados nos respectivos atributos server_address e RequestHandlerClass.

fileno()

Retorna um descritor de arquivo inteiro para o soquete no qual o servidor está escutando. Esta função é mais comumente passada para selectors, para permitir o monitoramento de múltiplos servidores no mesmo processo.

handle_request()

Processa uma única solicitação. Esta função chama os seguintes métodos em ordem: get_request(), verify_request() e process_request(). Se o método handle() fornecido pelo usuário da classe manipuladora levantar uma exceção, o método handle_error() do servidor será chamado. Se nenhuma solicitação for recebida dentro de timeout segundos, handle_timeout() será chamado e handle_request() retornará.

serve_forever(poll_interval=0.5)

Manipula solicitações até uma solicitação explícita shutdown(). Sonda para desligamento a cada poll_interval segundos. Ignora o atributo timeout. Ele também chama service_actions(), que pode ser usado por uma subclasse ou mixin para fornecer ações específicas para um determinado serviço. Por exemplo, a classe ForkingMixIn usa service_actions() para limpar processos filhos zumbis.

Alterado na versão 3.3: Adicionada chamada service_actions ao método serve_forever.

service_actions()

Isso é chamado no laço serve_forever(). Este método pode ser substituído por subclasses ou classes mixin para executar ações específicas para um determinado serviço, como ações de limpeza.

Novo na versão 3.3.

shutdown()

Diz ao laço serve_forever() para parar e esperar até que isso aconteça. shutdown() deve ser chamado enquanto serve_forever() estiver em execução em um thread diferente, caso contrário, ocorrerá um impasse.

server_close()

Limpa o servidor. Pode ser substituído.

address_family

The family of protocols to which the server’s socket belongs. Common examples are socket.AF_INET and socket.AF_UNIX.

RequestHandlerClass

A classe de manipulador de solicitações fornecida pelo usuário; uma instância dessa classe é criada para cada solicitação.

server_address

O endereço no qual o servidor está escutando. O formato dos endereços varia dependendo da família do protocolo; veja a documentação do módulo socket para detalhes. Para protocolos de internet, esta é uma tupla contendo uma string dando o endereço e um número de porta inteiro: ('127.0.0.1', 80), por exemplo.

socket

O objeto soquete no qual o servidor ouve para solicitações recebidas.

As classes de servidor oferecem suporte às seguintes variáveis de classe:

allow_reuse_address

Se o servidor permitirá a reutilização de um endereço. O padrão é False, e pode ser definido em subclasses para alterar a política.

request_queue_size

O tamanho da fila de requisições. Se levar muito tempo para processar uma única requisição, todas as requisições que chegarem enquanto o servidor estiver ocupado serão colocadas em uma fila, até request_queue_size requisições. Quando a fila estiver cheia, outras requisições de clientes receberão um erro “Conexão negada”. O valor padrão é geralmente 5, mas isso pode ser substituído por subclasses.

socket_type

O tipo de soquete usado pelo servidor; socket.SOCK_STREAM e socket.SOCK_DGRAM são dois valores comuns.

timeout

Duração do tempo limite, medida em segundos, ou None se nenhum tempo limite for desejado. Se handle_request() não receber nenhuma requisição de entrada dentro do período de tempo limite, o método handle_timeout() será chamado.

Existem vários métodos de servidor que podem ser substituídos por subclasses de classes de servidor base, como TCPServer; esses métodos não são úteis para usuários externos do objeto de servidor.

finish_request(request, client_address)

Na verdade, processa a solicitação instanciando RequestHandlerClass e chamando seu método handle().

get_request()

Deve aceitar uma solicitação do soquete e retornar uma tupla de 2 elementos contendo o novo objeto de soquete a ser usado para se comunicar com o cliente e o endereço do cliente.

handle_error(request, client_address)

Esta função é chamada se o método handle() de uma instância RequestHandlerClass levanta uma exceção. A ação padrão é exibir o traceback para erro padrão e continuar manipulando solicitações futuras.

Alterado na versão 3.6: Agora é chamada apenas para exceções derivadas da classe Exception.

handle_timeout()

Esta função é chamada quando o atributo timeout foi definido para um valor diferente de None e o período de timeout passou sem que nenhuma solicitação tenha sido recebida. A ação padrão para servidores de bifurcação é coletar o status de quaisquer processos filhos que tenham saído, enquanto em servidores de threading esse método não faz nada.

process_request(request, client_address)

Chama finish_request() para criar uma instância de RequestHandlerClass. Se desejado, esta função pode criar um novo processo ou thread para manipular a solicitação; as classes ForkingMixIn e ThreadingMixIn fazem isso.

server_activate()

Chamado pelo construtor do servidor para ativar o servidor. O comportamento padrão para um servidor TCP apenas invoca listen() no soquete do servidor. Pode ser substituído.

server_bind()

Chamada pelo construtor do servidor para vincular o soquete ao endereço desejado. Pode ser substituída.

verify_request(request, client_address)

Deve retornar um valor booleano; se o valor for True, a solicitação será processada, e se for False, a solicitação será negada. Esta função pode ser substituída para implementar controles de acesso para um servidor. A implementação padrão sempre retorna True.

Alterado na versão 3.6: Foi adicionado suporte para o protocolo gerenciador de contexto. Sair do gerenciador de contexto é equivalente a chamar server_close().

Objetos manipulador de requisições

class socketserver.BaseRequestHandler

Esta é a superclasse de todos os objetos manipulador de requisições. Ela define a interface, dada abaixo. Uma subclasse concreta do manipulador de requisições deve definir um novo método handle() e pode substituir qualquer um dos outros métodos. Uma nova instância da subclasse é criada para cada requisição.

setup()

Chamada antes do método handle() para executar quaisquer ações de inicialização necessárias. A implementação padrão não faz nada.

handle()

This function must do all the work required to service a request. The default implementation does nothing. Several instance attributes are available to it; the request is available as self.request; the client address as self.client_address; and the server instance as self.server, in case it needs access to per-server information.

The type of self.request is different for datagram or stream services. For stream services, self.request is a socket object; for datagram services, self.request is a pair of string and socket.

finish()

Chamada após o método handle() para executar quaisquer ações de limpeza necessárias. A implementação padrão não faz nada. Se setup() levanta uma exceção, esta função não será chamada.

class socketserver.StreamRequestHandler
class socketserver.DatagramRequestHandler

These BaseRequestHandler subclasses override the setup() and finish() methods, and provide self.rfile and self.wfile attributes. The self.rfile and self.wfile attributes can be read or written, respectively, to get the request data or return data to the client. The rfile attributes support the io.BufferedIOBase readable interface, and wfile attributes support the io.BufferedIOBase writable interface.

Alterado na versão 3.6: StreamRequestHandler.wfile also supports the io.BufferedIOBase writable interface.

Exemplos

Exemplo de socketserver.TCPServer

Este é o lado do 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()

Uma classe de manipulador de solicitações alternativa que faz uso de fluxos (objetos arquivo ou similar que simplificam a comunicação ao fornecer a interface de arquivo padrão):

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())

The difference is that the readline() call in the second handler will call recv() multiple times until it encounters a newline character, while the single recv() call in the first handler will just return what has been sent from the client in one sendall() call.

Este é o lado do 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))

A saída do exemplo deve se parecer com algo como isso:

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

Exemplo socketserver.UDPServer

Este é o lado do 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 é o lado do 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))

A saída do exemplo deve ser exatamente igual à do exemplo do servidor TCP.

Mixins assíncronos

Para construir manipuladores assíncronos, use as classes ThreadingMixIn e ForkingMixIn.

Um exemplo para a classe 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()

A saída do exemplo deve se parecer com algo como isso:

$ 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

A classe ForkingMixIn é usada da mesma forma, exceto que o servidor gerará um novo processo para cada solicitação. Disponível apenas em plataformas POSIX que oferecem suporte a fork().