7.1 O que são sockets?
Vamos aprofundar um pouco mais. Primeiro aprendemos a trabalhar com
request
, depois com http.client
, e depois com proxies. O que vem a seguir? A seguir, vamos dar uma olhada sob o capô de todas essas bibliotecas…
Socket (literalmente — tomada) é um ponto em uma rede, através do qual os dados são enviados e recebidos. Você pode imaginar um socket como o ponto final de um canal de comunicação bidirecional entre dois programas, operando na mesma máquina ou em máquinas diferentes.
Os sockets suportam vários protocolos de rede, mas os mais usados são dois:
-
TCP
(Transmission Control Protocol): Protocolo confiável, que garante a conexão, a integridade dos dados e a sua sequência correta. -
UDP
(User Datagram Protocol): Protocolo não orientado à conexão, que não garante a entrega dos dados, mas é mais rápido e eficiente para certos tipos de aplicações.
Para identificar um socket são usados um endereço IP (que identifica o dispositivo na rede) e uma porta (que identifica um aplicativo ou serviço específico no dispositivo).
Importante!
O endereço IP e a porta identificam de forma única
um programa na rede. É como o endereço de uma casa e o número do
apartamento. O endereço da casa (endereço IP) — é o endereço do seu computador na
rede, e a porta — é o número do apartamento que o programa usa para
enviar e receber mensagens.
Mais detalhes sobre o que é um endereço IP e uma porta, você pode descobrir nas palestras dedicadas à estrutura da rede.
As principais etapas do trabalho de um programa com sockets:
- Criação de um socket: O programa cria um socket, especificando o tipo de protocolo (TCP ou UDP).
- Ligação a um endereço: O socket é ligado a um endereço IP e um número de porta, para estar disponível para conexões ou envio/recebimento de dados.
-
Escuta e estabelecimento de conexão (para TCP):
- Escuta: O socket no lado do servidor é colocado no modo de escuta, aguardando por conexões de entrada.
- Estabelecimento de conexão: O cliente inicia a conexão com o servidor. O servidor aceita a conexão, criando um novo socket para interagir com o cliente.
- Troca de dados: Os dados são transmitidos entre o cliente e o servidor. No caso do TCP, os dados são enviados de forma confiável.
- Fechamento da conexão: Após a troca de dados ser concluída, a conexão é fechada.
Vantagens de usar sockets:
- Flexibilidade: Os sockets permitem que os aplicativos troquem dados independentemente de sua localização e plataforma.
- Desempenho: Os sockets oferecem uma maneira rápida e eficiente de transmitir dados, especialmente no caso de usar UDP.
- Confiabilidade (no caso do TCP): O protocolo TCP garante a entrega confiável dos dados com verificação de integridade e recuperação de pacotes perdidos.
7.2 Criando um socket-servidor
O trabalho com sockets em Python é feito através do módulo socket
embutido, que
fornece uma interface
para programação de rede de baixo nível.
Com os sockets, você pode criar um socket-servidor
— um aplicativo/objeto que receberá
solicitações de clientes e
responderá a elas. Além disso, um socket-cliente
— um aplicativo/objeto que enviará solicitações
ao socket-servidor
e
receberá respostas dele.
Para criar um socket-servidor
, é necessário realizar três ações:
- Criar um objeto socket-servidor.
- Vinculá-lo (
bind
) a algum IP e porta. - Ativar o modo de escuta (
listen
) para as mensagens de entrada.
Esse código ficaria mais ou menos assim:
import socket
# Criando um socket
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Ligando o socket a um endereço e porta
server_socket.bind(('localhost', 12345))
# Escutando conexões de entrada
server_socket.listen(5)
print("Servidor esperando por conexões...")
Aqui, socket.AF_INET
indica que estamos usando IPv4 para o protocolo de rede,
e socket.SOCK_STREAM
significa que estamos usando TCP. Esses parâmetros são os mais
usados para criar aplicativos de rede.
Depois que uma mensagem de entrada é recebida, é necessário fazer mais quatro ações:
- Estabelecer
(accept)
uma conexão com o cliente. - Receber
(receive)
a solicitação (dados) do cliente. - Enviar
(send)
uma resposta para o cliente — também alguns dados. - Fechar
(close)
a conexão.
Esse código ficaria assim:
# Aceitando uma nova conexão
client_socket, client_address = server_socket.accept()
print(f"Conexão estabelecida com {client_address}")
# Recebendo dados do cliente
data = client_socket.recv(1024)
print(f"Recebido: {data.decode('utf-8')}")
# Enviando dados para o cliente
client_socket.sendall(b'Hello, client!')
# Fechando a conexão com o cliente
client_socket.close()
O método sendall()
é usado em vez do send()
porque ele garante que
todos os dados sejam enviados. O método send()
pode enviar apenas uma parte dos dados,
se o buffer ficar cheio.
O número 1024 em recv(1024)
indica a quantidade máxima de bytes que pode
ser recebida de uma vez. Isso ajuda a controlar a quantidade de dados processados em uma operação.
O código do último exemplo geralmente é executado em um loop infinito — o servidor processa uma solicitação, depois espera por uma nova, então a processa, e assim por diante indefinidamente.
Se você quiser rodá-lo, ou apenas ver o exemplo completo, aqui está ele:
import socket
# Criando um socket
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Ligando o socket a um endereço e porta
server_socket.bind(('localhost', 12345))
# Escutando conexões de entrada
server_socket.listen(5)
print("Servidor esperando por conexões...")
while True:
# Aceitando uma nova conexão
client_socket, client_address = server_socket.accept()
print(f"Conexão estabelecida com {client_address}")
# Recebendo dados do cliente
data = client_socket.recv(1024)
print(f"Recebido: {data.decode('utf-8')}")
# Enviando dados para o cliente
client_socket.sendall(b'Hello, client!')
# Fechando a conexão com o cliente
client_socket.close()
7.3 Criando um socket-cliente
Já criamos um socket-servidor, agora vamos escrever nosso socket-cliente, que se conectará ao servidor e receberá dados dele em resposta.
Para isso, é necessário realizar cinco ações:
- Criar um objeto
socket-cliente
. - Estabelecer uma conexão
(connect)
com o endereço IP e porta do nossosocket-servidor
. - Enviar
(send)
uma mensagem para o servidor. - Receber
(receive)
dados do servidor. - Fechar
(close)
a conexão.
Na verdade, é mais simples do que parece. Veja como esse código ficaria:
import socket
# Criando um socket
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Estabelecendo conexão com o servidor
client_socket.connect(('localhost', 12345))
# Enviando dados para o servidor
client_socket.sendall(b'Hello, server!')
# Recebendo dados do servidor
data = client_socket.recv(1024)
print(f"Recebido do servidor: {data.decode('utf-8')}")
# Fechando o socket
client_socket.close()
Se do outro lado não houver um socket-servidor
em execução ou a conexão for interrompida, ocorrerá uma exceção do tipo
socket.error
. Então, não esqueça de tratar exceções.
Hoje vamos terminar nosso trabalho com sockets.
Em qualquer trabalho com a rede, vocês vão se deparar repetidamente com hosts, portas, endereços IP, estabelecimento de conexões, escuta de solicitações e coisas do tipo. Então, entender como isso funciona profundamente vai facilitar muito sua vida e ajudar a unir conhecimentos dispersos em um quadro unificado.
GO TO FULL VERSION