Что такое веб-сокеты, как они работают и где использовать этот протокол для постоянного двунаправленного клиент-серверного взаимодействия с демонстрацией практического примера на Python.
Как устроена интеграция через веб-сокеты
Синхронное взаимодействие в стиле запрос-ответ или эпизодическое через веб-хуки подходит не для всех сценариев. Например, когда нужна интеграция двух систем в реальном времени с поддержкой постоянного соединения между клиентом и сервером так, чтобы каждый из них мог инициировать передачу данных, как в онлайн-играх, чатах и мессенджерах. Для этого можно использовать протокол WebSocket вместо HTTP. Протокол WebSocket работает поверх транспортного протокола TCP, открывая постоянного двунаправленное взаимодействие клиента, инициирующего запрос, с сервером, отвечающим на него.
Благодаря тому, что TCP обеспечивает двустороннюю (дуплексную) связь, основанный на этом протоколе WebSocket позволяет клиенту и серверу отправлять сообщения одновременно. Причем передаваемые данные могут быть любого формата и даже большого размера, т.к. они разбиваются на фреймы. Заголовок такого фрейма указывает его длину и тип полезной нагрузки, а также информацию, является ли этот кадр последним.
Принцип клиент-серверного взаимодействия через веб-сокеты следующий:
- Клиент начинает связь с сервером, устанавливая открывающее рукопожатие по HTTP;
- После ответа на открывающее рукопожатие сервер меняет протокол с HTTP на WebSocket, открывая постоянное двунаправленное соединение;
- Любой из участников может безопасно инициировать закрывающее рукопожатие, которое закрывает соединение.
На UML-диаграмме последовательности это выглядит так.
Скрипт PlantUML для этой диаграммы:
@startuml title Интеграция через веб-сокеты participant Клиент as C participant Сервер as S C -> S : открыть рукопожатие() S --> C : рукопожатие подтверждено S -> S : переключить HTTP на WS C <-- S : соединение установлено loop пока соединение установлено C -\ S : отправить данные(датафрейм) S -\ C : отправить данные(датафрейм) end loop alt клиент разрывает соединение C -\ S : закрыть соединение() S -> S : закрыть соединение() else сервер разрывает соединение S -\ C : закрыть соединение() C -> C : закрыть соединение() end alt @enduml
Чтобы посмотреть, как это работает на практике, далее реализуем простой клиент и сервер на Python.
UML для бизнес-аналитика: основы ООП и разработка моделей
Код курса
BUML
Ближайшая дата курса
19 сентября, 2024
Продолжительность
8 ак.часов
Стоимость обучения
18 000 руб.
Практический пример на Python
В качестве примера возьмем сервис генерации адрес электронной почты для новых пользователей. Предположим, клиентское приложение отправляет на сервер ФИО нового пользователя, чтобы серверное приложение сгенерировало email для него. Иногда серверное приложение вместо генерации и отправки email будет отвечать, что оно перегружено.
Чтобы реализовать это по протоколу WebSocket на Python, необходимо использовать соответствующие библиотеки: websockets и asyncio для асинхронного обмена. Для случайной генерации ФИО пользователя и адреса электронной почты я, как обычно буду использовать библиотеку генерации фейковых данных Faker. Все эти пакеты надо установить в среде интерпретатора Python, в качестве которой я сейчас использую IDE PyCharm.
По сравнению с VS Code процесс создания виртуальных сред в ней намного проще. Виртуальная среда нужна, чтобы избежать конфликтов между разными Python-библиотеками, т.н. dependency hell. А привычный Colab использовать для этой демонстрации не хочется из-за сложности открытия портов сервера на виртуальной машине Google, даже с помощью утилиты тунелирования ngrok. Поэтому буду использовать локальный хост, выделив порт 1000 под серверное приложение, которое будет слушать входящие соединения этого TCP-сокета. О том, что такое сокет и зачем это для интеграции систем, я ранее писала здесь.
Код серверного приложения выглядит так:
#импорт модулей import asyncio import websockets from faker import Faker from faker.providers.person.ru_RU import Provider import random #назначаем порт на локалхосте port = 1000 # создание объекта Faker с локализацией для России fake = Faker('ru_RU') fake.add_provider(Provider) # асинхронная функция async def handler(websocket, path): try: async for message in websocket: op = random.randint(0, 100) print(f"Получено сообщение от клиента - новому пользователю {message} нужен email") if op>10 : message_ser = fake.free_email() print(f"Отправлено клиенту для пользователя {message} сгенерирован email {message_ser}") else: message_ser = "сервер перегружен" print(f"Для пользователя {message} не удалось сгенерировать email, т.к. {message_ser}") await websocket.send(f"{message_ser}") except websockets.ConnectionClosed: print("Соединение закрыто") # объявляем сервер start_server = websockets.serve(handler, "localhost", port) # запускаем сервер asyncio.get_event_loop().run_until_complete(start_server) asyncio.get_event_loop().run_forever()
Код клиентского приложения:
#импорт модулей import random import websockets import asyncio from faker import Faker from faker.providers.person.ru_RU import Provider # создание объекта Faker с локализацией для России fake = Faker('ru_RU') fake.add_provider(Provider) # асинхронная функция получения сообщения от сервера async def receive_message(websocket): try: async for message in websocket: print(f"Получено сообщение от сервера: {message}") except websockets.ConnectionClosed: print("Соединение закрыто") # асинхронная функция отправки сообщения на сервер async def send_message(): uri = "ws://localhost:1000" async with websockets.connect(uri) as websocket: # Запуск корутины для получения сообщений от сервера receive_task = asyncio.create_task(receive_message(websocket)) try: while True: sec = random.randint(1, 10) message = fake.name() await websocket.send(message) print(f"Отправлено сообщение серверу от клиента: У нас новый пользователь {message}, сгенери ему емейл") await asyncio.sleep(sec) # Ожидание перед отправкой следующего сообщения except websockets.ConnectionClosed: print("Соединение закрыто") finally: receive_task.cancel() # Запуск корутины send_message() с учетом уже запущенного цикла событий asyncio.get_event_loop().run_until_complete(send_message())
Запустив в PyCharm каждый из скриптов, в их терминалах можно увидеть постоянный обмен данными по протоколу WebSocket.
Таким образом, интеграция через веб-сокеты работает очень быстро, обеспечивая постоянную двустороннюю связь между клиентом и сервером в реальном времени. Это довольно универсальный способ взаимодействия, который подходит для веб-, мобильных и десктопных приложений. Безопасность соединения можно обеспечить с помощью шифрования, используя протокол wss:// вместо ws://. А благодаря возможности одновременно поддерживать множество соединений, пропускная способность такой интеграции довольно высока: можно передавать много данных в любом формате и объеме.
Однако, при большом количестве одновременных постоянных подключений может возникнуть гонка за ресурсы, обращающиеся к одному и тому же сокету сервера. Кроме того, при разработке stateful-систем придется дополнительно управлять состоянием и синхронизацией между клиентом и сервером в рамках длительного постоянного соединения.
Основы архитектуры и интеграции информационных систем
Код курса
OAIS
Ближайшая дата курса
5 ноября, 2024
Продолжительность
16 ак.часов
Стоимость обучения
36 000 руб.
Узнайте больше про другие технологии интеграции систем на моих курсах в Школе прикладного бизнес-анализа в нашем лицензированном учебном центре обучения и повышения квалификации системных и бизнес-аналитиков в Москве: