Подписывайтесь:

Блог AST-SoftPro

WebSocket в Python: реальный-time уведомления и чаты

28.05.2026 12 мин чтения
WebSocket в Python: реальный-time уведомления и чаты

Введение в WebSocket и real-time коммуникации

WebSocket — это протокол передачи данных между клиентом и сервером, обеспечивающий двустороннюю связь по одному TCP-соединению. В отличие от HTTP (который работает по модели запрос-ответ), WebSocket позволяет отправлять данные без явного запроса клиента, что делает его идеальным для приложений с real-time функционалом: чатов, уведомлений, онлайн-мониторинга и других сценариев.

Python предоставляет несколько библиотек для работы с WebSocket, но наиболее популярной на сегодняшний день остаётся websockets (для асинхронных серверов) и интеграция через FastAPI. FastAPI — это фреймворк для создания API в Python, который поддерживает WebSocket нативно благодаря асинхронной архитектуре. Это позволяет эффективно обрабатывать множество одновременных подключений без значительных затрат ресурсов.

В данной статье рассматриваются основы реализации real-time систем на Python с использованием WebSockets и FastAPI: создание сервера, управление соединениями, рассылка сообщений (broadcast) всем или группе пользователей, обработка ошибок и безопасность соединений.


Установка зависимостей

Для начала необходимо установить необходимые библиотеки:

pip install fastapi uvicorn websockets python-multipart

FastAPI используется как основной фреймворк, uvicorn — ASGI-сервер для запуска приложения (поддерживает WebSocket), websockets может использоваться в дополнении или заменяться через встроенный механизм FastAPI.


Создание базового WebSocket-сервера на FastAPI

Пример простого сервера с возможностью отправки сообщений всем подключённым клиентам:

from fastapi import FastAPI, WebSocket, WebSocketDisconnect

app = FastAPI()

manager = ConnectionManager()


class ConnectionManager:
    def __init__(self):
        self.active_connections: list[WebSocket] = []

    async def connect(self, connection: WebSocket):
        await connection.accept()
        self.active_connections.append(connection)

    async def disconnect(self, connection: WebSocket):
        if connection in self.active_connections:
            self.active_connections.remove(connection)

    async def send_message(self, message: str, except_self=None):
        for conn in self.active_connections:
            # Исключаем отправителя
            if conn != except_self:
                await conn.send_text(message)


@app.websocket('/ws')
async def websocket_endpoint(websocket: WebSocket):
    await manager.connect(websocket)

    try:
        while True:
            data = await websocket.receive_text()
            # Рассылаем сообщение всем подключённым
            await manager.send_message(data, except_self=websocket)
    except WebSocketDisconnect:
        await manager.disconnect(websocket)

Здесь определён класс ConnectionManager, управляющий подключениями. Каждый новый клиент проходит через /ws эндпоинт, который добавляет его в список активных соединений и начинает принимать сообщения.


Управление соединениями: подключение, отключение и рассылка

WebSocket-соединение может быть открыто, закрыто или разорвано (например, из-за таймаута). Важно корректно обрабатывать все три состояния:

  • websocket.open — соединение активно.

  • websocket.closed — клиент отключился добровольно (close()).

  • WebSocketDisconnect — исключение при потере соединения (сетевые ошибки и т.п.).

В примере выше используется кастомный ConnectionManager, который хранит множество активных WebSocket объектов. При получении сообщения от любого клиента, оно рассылается всем остальным.

Для тестирования можно использовать простой JavaScript-клиент:

const ws = new WebSocket('ws://localhost:8000/ws');

ws.onopen = () => {
    console.log('Соединение установлено');
};

ws.onmessage = (event) => {
    document.getElementById('messages').appendChild(
        createElementWithMessage(event.data)
    );
};

function sendMessage() {
    const input = document.getElementById('input');
    ws.send(input.value);
    input.value = '';
}

Групповая рассылка и исключения из списка получателей

Часто требуется не отправлять сообщение всем, а только определённой группе. Для этого можно расширить ConnectionManager:

from typing import List

class ConnectionManager:
    def __init__(self):
        self.active_connections: dict[str, set[WebSocket]] = {}  # room_name -> connections
        self.global_connected: set[WebSocket] = set()

    async def connect_room(self, room: str, connection: WebSocket):
        if room not in self.active_connections:
            self.active_connections[room] = set()
        self.active_connections[room].add(connection)
        self.global_connected.add(connection)

    async def disconnect_from_room(self, room: str, connection: WebSocket):
        if room in self.active_connections:
            self.active_connections[room].discard(connection)  # безопасно
        self.global_connected.discard(connection)

    async def send_to_room(self, room: str, message: str, except_self=None):
        connections = self.active_connections.get(room, set())
        for conn in connections:
            if conn != except_self and not conn.client_disconnected:
                try:
                    await conn.send_text(message)
                except Exception as e:
                    print(f'Ошибка отправки: {e}')


@app.websocket('/ws')
async def websocket_endpoint(websocket: WebSocket):
    await manager.connect_room('general', websocket)  # или другое имя комнаты

    try:
        while True:
            data = await websocket.receive_text()
            room, msg = parse_data(data)
            if room == 'broadcast':
                await broadcast(msg)
            else:
                await manager.send_to_room(room, msg, except_self=websocket)
    except WebSocketDisconnect:
        for room in list(manager.active_connections.keys()):
            if websocket in manager.active_connections[room]:
                await manager.disconnect_from_room(room, websocket)

Теперь сообщения могут отправляться в конкретные комнаты (send_to_room) или рассылаться глобально (broadcast). Это полезно для чатов с разделением по темам.


Обработка ошибок и отказоустойчивость

WebSocket соединения подвержены сбоям: медленная сеть, закрытие браузера, таймауты. Важно не допускать падения сервера из-за одного плохого клиента.

В FastAPI при возникновении WebSocketDisconnect или исключений внутри обработчика (await conn.send_text() может выбросить ошибку), приложение должно продолжать работать и обрабатывать другие подключения.

Пример защиты:

async def send_message(self, message: str, except_self=None):
    for conn in self.active_connections:
        try:
            await asyncio.wait_for(conn.send_text(message), timeout=5.0)
        except Exception as e:
            print(f'Не удалось отправить сообщение клиенту {id(conn)}')  # логируем, но не прерываем цикл

Использование asyncio.wait_for() ограничивает время ожидания ответа от клиента и предотвращает зависание.

Также важно обрабатывать случаи, когда клиент отправляет данные без чтения (например, если соединение разорвано). В таких случаях receive_text() выбросит исключение — оно должно быть перехвачено в блоке try/except внутри цикла.


Безопасность WebSocket-соединений

Хотя WebSocket работает поверх HTTPS и защищён на уровне TLS, важно учитывать следующие аспекты:

  • Аутентификация: клиент должен пройти проверку перед подключением. Это можно реализовать через токен (JWT) или капчу.

  • Валидация сообщений: все входящие данные должны проходить очистку от HTML/JS-инъекций.

  • Ограничение частоты запросов (rate limiting): предотвратить DoS-атаки.

Пример проверки JWT при подключении:

from fastapi import Depends, HTTPException
import jwt


async def get_current_user(websocket: WebSocket) -> dict:
    token = websocket.query_params.get('token')
    if not token:
        raise HTTPException(status_code=401, detail='Токен отсутствует')

    try:
        payload = jwt.decode(token, 'secret', algorithms=['HS256'])
        return {'user_id': payload['sub']}
    except Exception:
        raise HTTPException(status_code=403, detail='Недействительный токен')


@app.websocket('/ws/auth')
async def websocket_endpoint_with_auth(
    websocket: WebSocket,
    user: dict = Depends(get_current_user)
):
    await manager.connect(user['user_id'], websocket)

Теперь доступ к /ws/auth возможен только после успешной проверки токена.


Производительность и масштабируемость

FastAPI + Uvicorn — один из самых быстрых Python-фреймворков для ASGI-приложений. При правильной архитектуре он способен обрабатывать тысячи одновременных WebSocket-соединений на одной машине (в зависимости от нагрузки).

Для повышения производительности:

  • Используйте Gunicorn с Workers (--worker-class uvicorn.workers.UvicornWorker) — позволяет запускать несколько воркеров.

  • Для высокой нагрузке рассмотрите Redis + Pub/Sub или RabbitMQ для рассылки сообщений, а WebSocket использовать только как канал доставки (push).


Заключение

WebSocket в Python с использованием FastAPI предоставляет мощный и гибкий инструмент для реализации real-time приложений. Основные принципы:

  • Управление соединениями через ConnectionManager.

  • Обработка ошибок и исключений — ключ к отказоустойчивости.

  • Безопасность: аутентификация, валидация данных, rate limiting.

  • Масштабируемость возможна за счёт правильной архитектуры (Redis, воркеры).

Реализация чатов, уведомлений и других real-time сервисов становится простой задачей при использовании современных подходов. Важно помнить о надёжности соединения, защите от ошибок и возможной нагрузке.


Дополнительные ресурсы