Servidores, tipicamente, ficam à disposição em tempo integral para que os usuários, através de clientes, usem o serviço em qualquer momento e quantas vezes for necessário. O Hello, World! do exemplo anterior não tem essa característica. Ele encerra a conexão e o serviço como um todo, logo depois de atender o cliente. Neste exemplo, veremos um pequeno servidor que implementa o típico loop de um serviço de rede.
Este exemplo veremos um servidor de Eco. Trata-se de um exemplo
clássico do ensino de programação em rede. O servidor deve
aceitar conexões de clientes e, uma vez estabelecida a conexão,
passa a ecoar (repetir de volta) todas as mensagens que receber
do cliente. Adicionarei apenas dois pequenos detalhes. Nosso
servidor deve devolver as mensagens em caixa
alta e
deve se desconectar quando receber a mensagem tchau. Dessa
forma, o servidor pode ser liberado para atender a novos
clientes. Abaixo, segue o código completo do servidor. Em
seguida, um pequeno vídeo em que você pode ver como o servidor de Eco funciona
(na parte de cima, o servidor; abaixo, dois
clientes). Em seguida, o código completo do servidor, seguido de
alguns comentários.
import socket
# 1. criar um socket
listen_socket = socket.socket()
# 2. configurar o socket como servidor
porta = 9090
listen_socket.bind(('localhost', porta))
listen_socket.listen()
clientes = 0
while True:
# 3. aguardar conexões
print('Aguarda conexão na porta {}'.format(porta))
client_socket, endereco = listen_socket.accept()
clientes += 1
print('Conexão estabelecida de {}:{}'.format(endereco[0], endereco[1]))
# 4. usar e 5. fechar a conexão
client_socket.send('Servidor de eco (cliente {}).\n'.format(clientes).encode('utf-8'))
while True:
mensagem = client_socket.recv(1024).decode('utf-8')
if not mensagem: break
client_socket.send(mensagem.upper().encode('utf-8'))
if mensagem.strip() == "tchau": break
client_socket.close()
# 5. fechar o listening socket
listen_socket.close()

Se você entendeu o exemplo anterior, certamente
este exemplo será facílimo de entender. Há duas novidades apenas:
primeiro, o uso de dois laços while que, embora não precisem de
explicações do ponto de vista de linguagem, merecem uma
observação relativa a seu uso em servidores; segundo, o uso do
método recv().
Um servidor tipicamente requer um laço principal responsável por
“ouvir” e aceitar novas conexões. Esse laço é responsável por
fazer o servidor prover o serviço continuamente. A ideia é que,
tão logo seja possível, o servidor volte a dizer ao sistema que
está disponível para atender uma nova conexão (lembre que isso é
feito pelo método accept()). O sistema, então, deverá
estabelecer uma nova conexão com um dos pedidos que estiver em
fila ou, se não houver, deve bloquear o servidor. É esse o papel
do laço while mais externo.
O segundo laço (o mais interno) tem outro papel. Ao ser executado, já há uma conexão estabelecida com um cliente. Esse laço, portanto, tem o papel de manter a interação com o cliente enquanto durar a conexão. No caso do servidor de eco, a interação consiste em receber as mensagens e ecoá-las. Em um código real, é comum que a tal tipo de laço seja colocado em uma função separada, para garantir a separação de responsabilidades. No código mais abaixo, faço essa adaptação que também nos será útil para o próximo exemplo.
Uma observação importante aqui. O servidor só pode atender a vários clientes de forma sequencial: um depois do outro. E isso só ocorre porque o sistema operacional coloca os pedidos de conexão em fila e os entrega um a um ao processo, sempre que faz um
accept(). O servidor, da forma como está escrito, não tem como atender mais de um cliente por vez. Isso pode ser percebido no vídeo acima. Veja que o segundo cliente só começa a ser atendido (e estabelecer a conexão) depois que o primeiro cliente desconecta. No próximo exemplo, trararemos dessa questão.
Finalmente, há o método recv() cujo papel que é fácil de
compreender. É esse método que permite ler dados de um socket.
Dois detalhes merecem destaque. Primeiro, o método recebe um
inteiro como parâmetro, para indicar o tamanho máximo (em
bytes) da mensagem a ser lida do socket. Observe que neste
nosso pequeno exemplo o tamanho é irrelevante. Contudo, isso
ajuda a ressaltar que a conexão TCP é um stream de bytes e que
não há uma relação um para um entre mensagens enviadas e
mensagens recebidas. Cabe ao protocolo de comunicação acima de
TCP determinar como o stream de bytes deve ser dividido em
mensagens. Em nosso servidor, simplesmente assumiremos que
podemos ler blocos de tamanho menor que
1024 bytes. Na prática, isso está longe de ser adequado.