socketserver
— Um framework para servidores de rede¶
Código-fonte: Lib/socketserver.py
O módulo socketserver
simplifica a tarefa de escrever servidores de rede.
Disponibilidade: not WASI.
Este módulo não funciona ou não está disponível em WebAssembly. Veja Plataformas WebAssembly para mais informações.
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()
eserver_activate()
. Os outros parâmetros são passados para a classe baseBaseServer
.
- 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 afork()
.- block_on_close¶
ForkingMixIn.server_close
aguarda até que todos os processos filho sejam concluídos, exceto se o atributoblock_on_close
forFalse
.ThreadingMixIn.server_close
aguarda até que todos as threads não daemon sejam concluídos, exceto se o atributoblock_on_close
forFalse
.
- daemon_threads¶
Para
ThreadingMixIn
, use threads como daemon definindoThreadingMixIn.daemon_threads
comoTrue
para não esperar até que as threads sejam concluídas.
Alterado na versão 3.7:
ForkingMixIn.server_close
eThreadingMixIn.server_close
agora esperam até que todos os processos filhos e threads não daemon sejam concluídos. Adicione um novo atributo de classeForkingMixIn.block_on_close
para optar pelo comportamento pré-3.7.
- class socketserver.ForkingTCPServer¶
- class socketserver.ForkingUDPServer¶
- class socketserver.ThreadingTCPServer¶
- class socketserver.ThreadingUDPServer¶
- class socketserver.ForkingUnixStreamServer¶
- class socketserver.ForkingUnixDatagramServer¶
- class socketserver.ThreadingUnixStreamServer¶
- class socketserver.ThreadingUnixDatagramServer¶
Essas classes são predefinidas usando as classes mix-in.
Adicionado na versão 3.12: As classes ForkingUnixStreamServer
e ForkingUnixDatagramServer
foram adicionadas.
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()
.
Outra abordagem para lidar com múltiplas requisições simultâneas em um ambiente que não oferece suporte a threads nem fork()
(ou onde estes são muito caros ou inapropriados para o serviço) é manter uma tabela explícita de requisições parcialmente finalizadas e usar selectors
para decidir em qual requisição trabalhar em seguida (ou se deve lidar com uma nova requisição recebida). Isto é particularmente importante para serviços de fluxo onde cada cliente pode potencialmente ficar conectado por um longo tempo (se threads ou subprocessos não puderem ser usados).
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
eRequestHandlerClass
.- 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()
eprocess_request()
. Se o métodohandle()
fornecido pelo usuário da classe manipuladora levantar uma exceção, o métodohandle_error()
do servidor será chamado. Se nenhuma solicitação for recebida dentro detimeout
segundos,handle_timeout()
será chamado ehandle_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 atributotimeout
. Ele também chamaservice_actions()
, que pode ser usado por uma subclasse ou mixin para fornecer ações específicas para um determinado serviço. Por exemplo, a classeForkingMixIn
usaservice_actions()
para limpar processos filhos zumbis.Alterado na versão 3.3: Adicionada chamada
service_actions
ao métodoserve_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.Adicionado 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 enquantoserve_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¶
A família de protocolos à qual o soquete do servidor pertence. Exemplos comuns são
socket.AF_INET
,socket.AF_INET6
esocket.AF_UNIX
. Subclasse as classes de servidor TCP ou UDP neste módulo com o atributo de classeaddress_family = AF_INET6
definido se você quiser classes de servidor IPv6.
- 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
esocket.SOCK_DGRAM
são dois valores comuns.
- timeout¶
Duração do tempo limite, medida em segundos, ou
None
se nenhum tempo limite for desejado. Sehandle_request()
não receber nenhuma requisição de entrada dentro do período de tempo limite, o métodohandle_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étodohandle()
.
- 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ânciaRequestHandlerClass
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 deNone
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 deRequestHandlerClass
. Se desejado, esta função pode criar um novo processo ou thread para manipular a solicitação; as classesForkingMixIn
eThreadingMixIn
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 forFalse
, 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 retornaTrue
.
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()¶
Esta função deve fazer todo o trabalho necessário para atender a uma solicitação. A implementação padrão não faz nada. Vários atributos de instância estão disponíveis para ela; a solicitação está disponível como
request
; o endereço do cliente comoclient_address
; e a instância do servidor comoserver
, caso precise de acesso a informações por servidor.O tipo de
request
é diferente para serviços de datagrama ou fluxo. Para serviços de fluxo,request
é um objeto soquete; para serviços de datagrama,request
é um par de string e soquete.
- 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. Sesetup()
levanta uma exceção, esta função não será chamada.
- request¶
O novo objeto
socket.socket
a ser usado para se comunicar com o cliente.
- client_address¶
Endereço do cliente retornado por
BaseServer.get_request()
.
- server¶
Objeto
BaseServer
usado para manipular a solicitação.
- class socketserver.StreamRequestHandler¶
- class socketserver.DatagramRequestHandler¶
Essas subclasses
BaseRequestHandler
substituem os métodossetup()
efinish()
e fornecem os atributosrfile
ewfile
.- rfile¶
Um objeto arquivo do qual recebe a solicitação é lido. Oferece suporte à interface legível
io.BufferedIOBase
.
- wfile¶
Um objeto arquivo no qual a resposta é escrita. Oferece suporte à interface gravável
io.BufferedIOBase
Alterado na versão 3.6:
wfile
também oferece suporte à interface gravávelio.BufferedIOBase
.
Exemplos¶
Exemplo de socketserver.TCPServer
¶
Este é o lado do servidor:
import socketserver
class MyTCPHandler(socketserver.BaseRequestHandler):
"""
A classe de manipulador de solicitação para nosso servidor.
Ela é instanciada uma vez por conexão com o servidor e deve
substituir o método handle() para implementar a comunicação
com o cliente.
"""
def handle(self):
# self.request é o soquete TCP conectado ao cliente
pieces = [b'']
total = 0
while b'\n' not in pieces[-1] and total < 10_000:
pieces.append(self.request.recv(2000))
total += len(pieces[-1])
self.data = b''.join(pieces)
print(f"Received from {self.client_address[0]}:")
print(self.data.decode("utf-8"))
# só envia de volta os mesmos dados, mas em caixa alta
self.request.sendall(self.data.upper())
# após retornarmos, o soquete será fechado.
if __name__ == "__main__":
HOST, PORT = "localhost", 9999
# Cria o servidor, ligando-o ao localhost na porta 9999
with socketserver.TCPServer((HOST, PORT), MyTCPHandler) as server:
# Ativa o servidor; isso vai mantê-lo em execução até você
# interromper o programa com 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 é um objeto arquivo ou similar criado pelo manipulador.
# Agora podemos usar, por exemplo, readline() em vez de chamadas
# brutas a recv().
# Nos limitamos a 10000 bytes para evitar abuso pelo remetente.
self.data = self.rfile.readline(10000).rstrip()
print(f"{self.client_address[0]} wrote:")
print(self.data.decode("utf-8"))
# Da mesma forma, self.wfile é um objeto arquivo ou similar para
# escrever de volta para o cliente
self.wfile.write(self.data.upper())
A diferença é que a chamada readline()
no segundo manipulador chamará recv()
várias vezes até encontrar um caractere de nova linha, enquanto o primeiro manipulador teve que usar um laço recv()
para acumular dados até uma nova linha. Se tivesse usado apenas um único recv()
sem o laço, ele teria retornado apenas o que foi recebido até agora do cliente. O TCP é baseado em fluxo: os dados chegam na ordem em que foram enviados, mas não há correlação entre as chamadas send()
ou sendall()
do cliente e o número de chamadas recv()
no servidor necessárias para recebê-los.
Este é o lado do cliente:
import socket
import sys
HOST, PORT = "localhost", 9999
data = " ".join(sys.argv[1:])
# Cria um soquete (SOCK_STREAM significa um soquete TCP)
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
# Conecta a um servidor e envia dados
sock.connect((HOST, PORT))
sock.sendall(bytes(data, "utf-8"))
sock.sendall(b"\n")
# Recebe dados do servidor e desliga
received = str(sock.recv(1024), "utf-8")
print("Sent: ", data)
print("Received:", 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):
"""
Esta classe funciona de forma semelhante à classe do manipulador TCP,
exceto que self.request consiste em um par de dados e soquete do cliente
e, como não há conexão, o endereço do cliente deve ser fornecido
explicitamente ao enviar dados de volta via sendto().
"""
def handle(self):
data = self.request[0].strip()
socket = self.request[1]
print(f"{self.client_address[0]} wrote:")
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 é o tipo de soquete para usar para soquetes UDP
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# Como você pode ver, não há uma chamada connect(); UDP não tem conexão.
# Em vez disso, dados são enviados diretamente para o destinatário via sendto().
sock.sendto(bytes(data + "\n", "utf-8"), (HOST, PORT))
received = str(sock.recv(1024), "utf-8")
print("Sent: ", data)
print("Received:", 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__":
# Porta 0 significa selecionar uma porta não usada arbitrária
HOST, PORT = "localhost", 0
server = ThreadedTCPServer((HOST, PORT), ThreadedTCPRequestHandler)
with server:
ip, port = server.server_address
# Inicia uma thread com o servidor -- essa thread vai então
# iniciar mais um thread para cada requisição
server_thread = threading.Thread(target=server.serve_forever)
# Sai da thread do servidor quando a thread principal é encerrada
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()
.