Voltar ao Glossario
Glossario Python

WebSocket: O que É e Como Funciona | Python Brasil

Guia completo sobre WebSocket em Python: protocolo, handshake, heartbeat, Django Channels, escalonamento com Redis e segurança.

O que é WebSocket?

WebSocket é um protocolo de comunicação que permite troca de dados bidirecional entre cliente e servidor em tempo real sobre uma única conexão TCP persistente. Diferente do HTTP tradicional (onde o cliente sempre inicia a comunicação), com WebSocket tanto o servidor quanto o cliente podem enviar dados a qualquer momento, sem necessidade de uma nova requisição.

O protocolo WebSocket foi padronizado pela IETF como RFC 6455 em 2011 e é suportado nativamente por todos os navegadores modernos.

WebSocket vs HTTP

AspectoHTTPWebSocket
ModeloRequest-responseFull-duplex bidirecional
ConexãoNova a cada requisiçãoPersistente
LatênciaAlta (overhead de cabeçalhos)Baixa (frames pequenos)
OverheadCabeçalhos grandes em cada req.Cabeçalhos mínimos após handshake
Ideal paraAPIs, conteúdo estáticoChat, jogos, dashboards ao vivo

O Protocolo em Detalhe: Handshake

A conexão WebSocket começa com um HTTP Upgrade request. O cliente envia uma requisição HTTP especial:

GET /ws/chat HTTP/1.1
Host: exemplo.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13

O servidor responde com:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

A partir desse momento, a conexão HTTP é substituída pelo protocolo WebSocket. Não há mais overhead de cabeçalhos HTTP — os dados trafegam em frames compactos.

Frames WebSocket

Os dados em WebSocket são enviados em frames, que têm:

  • Opcode: tipo do frame (texto, binário, ping, pong, close)
  • Payload: os dados em si
  • Máscara: clientes devem mascarar dados enviados ao servidor (segurança)

Tipos de frames:

  • 0x1: dados de texto (UTF-8)
  • 0x2: dados binários
  • 0x8: encerramento de conexão
  • 0x9: ping
  • 0xA: pong

Ciclo de Vida da Conexão

  1. Handshake: upgrade de HTTP para WebSocket
  2. Conexão aberta: cliente e servidor podem trocar mensagens livremente
  3. Troca de mensagens: bidirecional, sem espera
  4. Heartbeat (ping/pong): mantém a conexão viva
  5. Encerramento: qualquer lado envia frame de close com código de status

Exemplo com a Biblioteca websockets

import asyncio
import websockets
import json

# Servidor WebSocket
clientes_conectados = set()

async def handler(websocket):
    clientes_conectados.add(websocket)
    print(f"Novo cliente conectado. Total: {len(clientes_conectados)}")

    try:
        async for mensagem in websocket:
            dados = json.loads(mensagem)
            print(f"Recebido: {dados}")

            # Broadcast para todos os clientes
            if clientes_conectados:
                await asyncio.gather(
                    *[cliente.send(json.dumps({
                        "tipo": "mensagem",
                        "conteudo": dados.get("texto", ""),
                        "usuario": dados.get("usuario", "Anônimo")
                    })) for cliente in clientes_conectados]
                )
    except websockets.exceptions.ConnectionClosed as e:
        print(f"Cliente desconectado: {e.code} - {e.reason}")
    finally:
        clientes_conectados.discard(websocket)

async def main():
    async with websockets.serve(handler, "localhost", 8765):
        print("Servidor WebSocket em ws://localhost:8765")
        await asyncio.Future()  # Roda indefinidamente

asyncio.run(main())

Heartbeat: Ping/Pong

Conexões WebSocket podem cair silenciosamente (firewall, timeout de rede). O mecanismo de ping/pong mantém a conexão viva e detecta desconexões:

import asyncio
import websockets

async def handler(websocket):
    try:
        async for mensagem in websocket:
            await websocket.send(f"Eco: {mensagem}")
    except websockets.exceptions.ConnectionClosed:
        pass

async def main():
    async with websockets.serve(
        handler,
        "localhost",
        8765,
        ping_interval=20,  # Envia ping a cada 20 segundos
        ping_timeout=10,   # Fecha conexão se pong não chegar em 10s
    ):
        await asyncio.Future()

asyncio.run(main())

No lado do cliente JavaScript:

const ws = new WebSocket("ws://localhost:8765");

ws.onopen = () => console.log("Conectado");
ws.onmessage = (evento) => console.log("Mensagem:", evento.data);
ws.onclose = (evento) => {
    console.log(`Desconectado: ${evento.code}`);
    // Reconectar após 3 segundos
    setTimeout(conectar, 3000);
};

Estratégias de Reconexão

Clientes devem implementar reconexão automática com backoff exponencial:

class WebSocketClient {
    constructor(url) {
        this.url = url;
        this.tentativas = 0;
        this.maxTentativas = 10;
        this.conectar();
    }

    conectar() {
        this.ws = new WebSocket(this.url);
        this.ws.onopen = () => {
            console.log("Conectado!");
            this.tentativas = 0; // Resetar contador
        };
        this.ws.onclose = () => {
            if (this.tentativas < this.maxTentativas) {
                const espera = Math.min(1000 * Math.pow(2, this.tentativas), 30000);
                console.log(`Reconectando em ${espera}ms...`);
                setTimeout(() => this.conectar(), espera);
                this.tentativas++;
            }
        };
    }
}

Exemplo com FastAPI

from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from typing import List

app = FastAPI()

class GerenciadorConexoes:
    def __init__(self):
        self.conexoes: List[WebSocket] = []

    async def conectar(self, websocket: WebSocket):
        await websocket.accept()
        self.conexoes.append(websocket)

    def desconectar(self, websocket: WebSocket):
        self.conexoes.remove(websocket)

    async def broadcast(self, mensagem: str):
        conexoes_ativas = self.conexoes.copy()
        for conexao in conexoes_ativas:
            try:
                await conexao.send_text(mensagem)
            except Exception:
                self.conexoes.remove(conexao)

gerenciador = GerenciadorConexoes()

@app.websocket("/ws/chat/{sala}")
async def chat(websocket: WebSocket, sala: str, usuario: str = "Anônimo"):
    await gerenciador.conectar(websocket)
    await gerenciador.broadcast(f"{usuario} entrou na sala {sala}")
    try:
        while True:
            dados = await websocket.receive_text()
            await gerenciador.broadcast(f"[{sala}] {usuario}: {dados}")
    except WebSocketDisconnect:
        gerenciador.desconectar(websocket)
        await gerenciador.broadcast(f"{usuario} saiu da sala {sala}")

Escalonamento com Redis Pub/Sub

Quando a aplicação roda em múltiplos processos ou servidores, o gerenciador em memória não funciona. A solução é usar Redis Pub/Sub como broker de mensagens:

import asyncio
import json
import redis.asyncio as aioredis
from fastapi import FastAPI, WebSocket, WebSocketDisconnect

app = FastAPI()
redis_client = aioredis.from_url("redis://localhost")

conexoes_locais: set[WebSocket] = set()

async def ouvir_redis():
    """Ouve mensagens do Redis e distribui para conexões locais."""
    pubsub = redis_client.pubsub()
    await pubsub.subscribe("chat")
    async for mensagem in pubsub.listen():
        if mensagem["type"] == "message":
            texto = mensagem["data"].decode()
            conexoes_para_remover = set()
            for ws in conexoes_locais.copy():
                try:
                    await ws.send_text(texto)
                except Exception:
                    conexoes_para_remover.add(ws)
            conexoes_locais -= conexoes_para_remover

@app.on_event("startup")
async def startup():
    asyncio.create_task(ouvir_redis())

@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
    await websocket.accept()
    conexoes_locais.add(websocket)
    try:
        while True:
            dados = await websocket.receive_text()
            # Publica no Redis — todos os servidores receberão
            await redis_client.publish("chat", dados)
    except WebSocketDisconnect:
        conexoes_locais.discard(websocket)

Django Channels

O Django Channels estende o Django para suportar WebSockets e outros protocolos assíncronos:

# consumers.py
import json
from channels.generic.websocket import AsyncWebsocketConsumer

class ChatConsumer(AsyncWebsocketConsumer):
    async def connect(self):
        self.sala = self.scope["url_route"]["kwargs"]["sala_nome"]
        self.grupo = f"chat_{self.sala}"

        # Entrar no grupo do Channel Layer (Redis)
        await self.channel_layer.group_add(self.grupo, self.channel_name)
        await self.accept()

    async def disconnect(self, close_code):
        await self.channel_layer.group_discard(self.grupo, self.channel_name)

    async def receive(self, text_data):
        dados = json.loads(text_data)
        await self.channel_layer.group_send(
            self.grupo,
            {"type": "chat_message", "mensagem": dados["mensagem"]}
        )

    async def chat_message(self, evento):
        await self.send(text_data=json.dumps({"mensagem": evento["mensagem"]}))

Socket.IO

Socket.IO é uma biblioteca que adiciona recursos sobre WebSocket: reconexão automática, namespaces, salas e fallback para long-polling. Em Python, use python-socketio:

import socketio

sio = socketio.AsyncServer(async_mode="asgi", cors_allowed_origins="*")

@sio.event
async def connect(sid, environ):
    print(f"Cliente {sid} conectado")

@sio.event
async def mensagem(sid, dados):
    await sio.emit("resposta", {"texto": dados["texto"]}, room=sid)

@sio.event
async def disconnect(sid):
    print(f"Cliente {sid} desconectado")

# Montar no FastAPI
from fastapi import FastAPI
app = FastAPI()
app.mount("/socket.io", socketio.ASGIApp(sio))

Segurança

  • Autenticação no handshake: passe o token JWT como query param ou em subprotocolo durante o upgrade
  • Validação de mensagens: nunca confie em dados recebidos sem validação (use Pydantic)
  • Rate limiting por conexão: limite mensagens por segundo para evitar flood
  • WSS obrigatório: sempre use wss:// (WebSocket sobre TLS) em produção, equivalente ao HTTPS
  • CORS no handshake: o servidor deve verificar o header Origin durante o handshake
# Autenticação no WebSocket com FastAPI
from fastapi import WebSocket, Query, HTTPException

@app.websocket("/ws")
async def websocket_autenticado(
    websocket: WebSocket,
    token: str = Query(...)
):
    usuario = verificar_token(token)
    if not usuario:
        await websocket.close(code=4001)  # Código customizado para não autorizado
        return
    await websocket.accept()
    # ... lógica do WebSocket

Load Balancing

WebSockets são conexões persistentes — isso complica o uso com load balancers. Soluções:

  • Sticky sessions: o load balancer direciona sempre o mesmo cliente para o mesmo servidor
  • Redis Pub/Sub: qualquer servidor pode receber mensagens e retransmitir para conexões locais
  • Nginx com upgrade: configure corretamente os headers de upgrade
upstream websocket_backend {
    ip_hash;  # Sticky sessions
    server ws1:8000;
    server ws2:8000;
}

server {
    location /ws/ {
        proxy_pass http://websocket_backend;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_read_timeout 3600;  # Timeout longo para conexões persistentes
    }
}

Casos de Uso

WebSockets são ideais para:

  • Chats em tempo real: mensagens instantâneas entre usuários
  • Dashboards ao vivo: métricas e gráficos que se atualizam sem refresh
  • Jogos multiplayer: sincronização de estado entre jogadores
  • Notificações push: alertas enviados pelo servidor sem polling
  • Feeds financeiros: cotações de ações e criptomoedas em tempo real
  • Colaboração em tempo real: editores de documento como Google Docs

Erros Comuns

  • Não tratar WebSocketDisconnect: a conexão pode cair a qualquer momento; sempre use try/except
  • Gerenciador de conexões em memória em produção: não funciona com múltiplos processos; use Redis
  • Não implementar reconexão no cliente: conexões WebSocket caem; o cliente deve reconectar automaticamente
  • Esquecer o WSS: em produção, sempre use TLS

Termos Relacionados

  • FastAPI - Framework com suporte nativo a WebSocket
  • Async/Await - Base da programação com WebSocket em Python
  • API REST - Protocolo complementar ao WebSocket