Files
hse-2026/homework/02-grpc-messenger/readme.md
T
2026-09-17 16:34:16 +03:00

21 KiB
Raw Blame History

Мессенджер на gRPC

Опишите gRPC-интерфейс и реализуйте сервер и клиент мессенджера с одним общим чатом. Сервер и клиент общаются по gRPC, пользователь обращается к клиенту по HTTP.

У сервера два RPC-метода: SendMessage отправляет сообщение в чат, ReadMessages открывает подписку на новые сообщения. Сервер должен обрабатывать несколько запросов одновременно, в том числе принимать сообщения при открытых подписках.

Клиент при запуске открывает подписку и сохраняет сообщения от сервера в буфере в порядке получения. Через HTTP пользователь отправляет сообщения и забирает содержимое буфера.

На схеме показаны сервер и два клиента. Тесты обращаются к клиентам от имени двух пользователей:

flowchart RL
   subgraph Tests
      U1{User 1}
      U2{User 2}
   end

   subgraph Clients with HTTP interface
      C1(fa:fa-comments Client 1)
      C2(fa:fa-comments Client 2)
   end

   subgraph gRPC server
      S(fa:fa-server Server)
   end


   C1 -- SendMessage --> S
   S -. Stream ReadMessages .-> C1

   U1 -- POST /sendMessage --> C1
   C1 -- Forward messages in /getAndFlushMessages --> U1

   C2 -- SendMessage --> S
   S -. Stream ReadMessages .-> C2

   U2 -- POST /sendMessage --> C2
   C2 -- Forward messages in /getAndFlushMessages --> U2

Требования

Доставка сообщений

  • Подписка начинается, когда сервер регистрирует вызов ReadMessages, и действует до отмены RPC или закрытия соединения. Сообщения, принятые до регистрации, в подписку не попадают.
  • Сервер передаёт каждое сообщение ровно один раз во все подписки, активные в момент его принятия, включая подписку отправителя. Восстанавливать историю после разрывов и перезапусков не нужно.
  • Сообщения, общие для нескольких подписчиков, должны идти в одинаковом порядке во всех их потоках. Клиенты сохраняют этот порядок. Для одновременных вызовов SendMessage сервер может выбрать любой порядок.
  • sendTime — серверное время принятия сообщения. В течение одного запуска эти значения должны быть уникальны и строго возрастать в порядке рассылки. Ответ SendMessage и сообщение во всех подписках содержат одинаковый sendTime.
  • Успешный ответ SendMessage означает, что сервер принял сообщение. Это не гарантирует, что все клиенты уже его получили.

HTTP-интерфейс клиента

Пользователи и тесты обращаются к клиенту через два HTTP-метода.

При успехе оба метода возвращают HTTP 200 и JSON. Поле sendTime — строка в JSON-формате google.protobuf.Timestamp, например 2025-09-20T10:58:42.665193557Z.

POST /sendMessage
Отправляет одно сообщение в общий чат.

Тело запроса:
{
    "author": "Ivan Ivanov",
    "text": "Hey guys"
}

Тело ответа:
{
    "sendTime": "..."
}
POST /getAndFlushMessages
Возвращает накопленные сообщения в порядке получения и очищает буфер.

Тело запроса: нет

Тело ответа:
[{
    "author": "Ivan Ivanov",
    "text": "Hey guys",
    "sendTime": "..."
},{
    "author": "Petr Petrov",
    "text": "Hey Ivan",
    "sendTime": "..."
}]

Если буфер пуст, getAndFlushMessages сразу возвращает []. Чтение и очистка буфера должны быть атомарными: сообщение, пришедшее во время этой операции, попадает в текущий или следующий ответ. Клиент не должен терять сообщения, выдавать их повторно или менять их порядок.

gRPC-интерфейс сервера

Тесты проверяют сервер отдельно от клиента. Соблюдайте требования к интерфейсу:

  • синтаксис — proto3, пакет — mes_grpc;
  • gRPC-сервис MessengerServer содержит два метода: SendMessage и ReadMessages;
  • SendMessage — унарный вызов. Запрос содержит одиночные строковые поля author и text, ответ — одиночное поле sendTime типа google.protobuf.Timestamp;
  • ReadMessages принимает один пустой запрос и возвращает поток сообщений. Можно описать свой тип пустого сообщения или взять готовый из библиотеки. Каждое сообщение в потоке содержит одиночные поля author и text типа string и sendTime типа google.protobuf.Timestamp.

Все перечисленные поля одного сообщения должны допускать одновременное заполнение.

Имена типов сообщений и номера полей выберите самостоятельно — тесты их не фиксируют.

Оценивание

За задание можно получить 10 баллов:

  • 2 балла — протокол messenger.proto, проверяется в test_proto.py.
  • 4 балла — сервер, проверяется в test_server.py.
  • 4 балла — клиент, проверяется в test_client.py.

В отчёте solution/readme.md опишите структуру решения, какие компоненты вы реализовали и как работают методы сервера и клиента. Без отчёта тесты запускаются, но защита не проводится и решение не засчитывается — см. общие правила сдачи.

Баллы за компонент начисляются, только если прошли все его тесты: 2 или 0 за протокол, 4 или 0 за сервер, 4 или 0 за клиент. Значение SCORE в выводе тестов — предварительная оценка. Итоговую оценку преподаватель выставляет после защиты с учётом штрафов ниже.

Система собирает и проверяет сервер и клиент независимо. Если сервер не собирается или не запускается, он получает 0 баллов, но клиент всё равно проверяется, и наоборот. Протокол проверяется отдельно.

При независимом оценивании клиенты работают со служебным gRPC-сервером, построенным по вашему messenger.proto. Поэтому для проверки клиента нужен корректный протокол, но ошибки вашего сервера не влияют на баллы за клиент.

На защите можно потерять баллы за следующие ошибки:

  • Сервер не может обрабатывать несколько запросов одновременно — 2 балла.
  • При конкурентном доступе сервер может потерять или продублировать сообщения, выдать их в разном порядке в потоках ReadMessages либо нарушить требования к sendTime — 2 балла.
  • Клиент теряет, повторно выдаёт или меняет порядок сообщений из потока ReadMessages — 2 балла.

За ошибки сервера снимаются только баллы за сервер, за ошибки клиента — только баллы за клиент, не больше 4 баллов в каждом случае. Баллы за протокол сохраняются. На защите нужно разобрать предложенный преподавателем сценарий конкурентного выполнения и объяснить по своему коду, почему решение работает правильно.

Заготовки для клиента

В templates есть заготовки клиента на Python, Python с asyncio и Go. Официальная заготовка — messenger-py/client; она проверена для текущего задания. Остальные заготовки относятся к прошлым версиям задания: их можно использовать, но расхождения с условием нужно исправить самостоятельно. Можно выбрать и другой язык — тесты обращаются к решению через HTTP и gRPC.

Порядок выполнения задания

Выполняйте команды из папки homework/02-grpc-messenger. Примеры с переменными окружения написаны для Bash. В Windows используйте WSL или задавайте переменные через PowerShell.

Подготовка окружения

Установите Python 3.12 или новее и Docker по общей инструкции. Для Python-заготовки и локального запуска тестов установите зависимости:

python3 -m pip install -r templates/messenger-py/client/requirements.txt -r tests/requirements.txt
grpcurl -version

Если grpcurl не найден, установите его по инструкции в разделе «Полезные материалы». На Windows используйте python вместо python3.

Структура проекта

Разместите решение в папке solution. Сохраните пути к трём файлам, которые используют тесты и docker-compose.yml:

  • client.dockerfile — сборка и запуск клиента;
  • server.dockerfile — сборка и запуск сервера;
  • proto/messenger.proto — описание gRPC-интерфейса; дополните начальный файл.

При сдаче отправляется только папка solution. Изменения за её пределами не учитываются.

Для официальной Python-заготовки скопируйте templates/messenger-py/client в solution/client, а образец client.dockerfile — в solution/client.dockerfile. Сервер разместите в solution/server/server.py. Заготовка использует пакет solution и импорты from solution.proto import messenger_pb2, messenger_pb2_grpc. В своей реализации можно выбрать другую структуру, сохранив три обязательных пути выше.

Описание и компиляция gRPC-интерфейса

Опишите сообщения и сервис в solution/proto/messenger.proto.

Сгенерируйте код для выбранного языка с помощью protoc:

# Python
python3 -m grpc_tools.protoc -I. --python_out=. --pyi_out=. --grpc_python_out=. solution/proto/messenger.proto

# Go (после установки protoc и плагинов protoc-gen-go и protoc-gen-go-grpc)
protoc -I solution/proto --go_out=solution/proto --go_opt=paths=source_relative --go-grpc_out=solution/proto --go-grpc_opt=paths=source_relative messenger.proto

Для Go укажите в option go_package путь пакета в вашем модуле; пример есть в Go-заготовке. Закрепите версии генераторов, совместимые с вашей версией Go. Сгенерированные файлы включите в решение или генерируйте при сборке образа с закреплёнными версиями инструментов.

Реализация сервера и клиента

Сервер реализуйте с нуля; заготовки для него нет. Можно использовать потоки или асинхронный код. Открытые подписки не должны мешать обработке других запросов.

Если используете Python-заготовку клиента, заполните места с пометкой TODO. HTTP-сервер в ней уже реализован. При запуске клиент должен дождаться сервера, открыть ReadMessages и принимать сообщения независимо от обработки HTTP-запросов.

Сервер и клиент должны брать настройки из переменных окружения:

Переменная Назначение
MESSENGER_SERVER_PORT Порт gRPC-сервера, по умолчанию 51075
MESSENGER_SERVER_ADDR Адрес gRPC-сервера для подключения клиента
MESSENGER_HTTP_PORT Порт HTTP-интерфейса клиента

Сервер и HTTP-интерфейс клиента должны слушать на 0.0.0.0.

В solution/server.dockerfile и solution/client.dockerfile опишите сборку и запуск сервера и клиента. Контекст сборки — папка solution. Включите в образы все нужные файлы и зависимости. За образец можно взять Dockerfile заготовки.

Тестирование решения

Публичные тесты проверяют протокол, сервер и клиент. Сложные конкурентные сценарии вы разберёте на защите.

Полная проверка

Рекомендуемый запуск в окружении тестирующей системы:

docker run --privileged --pull always --rm -v ./solution:/hw/solution distsys.ru/course/grpc-messenger:latest

Сервер и клиент собираются и проверяются независимо: ошибка сборки одного не мешает проверить другой.

Отдельные компоненты через Docker Compose

docker compose build tests

# Протокол
docker compose run --rm --no-deps tests --component proto

# Сервер
docker compose build server-tests
docker compose run --rm server-test-runner

# Клиент со служебным сервером
docker compose build client-test1
docker compose run --rm client-tests

После изменения кода пересоберите соответствующий образ и повторите проверку. После изменения протокола заново сгенерируйте код и выполните:

docker compose down
docker compose build tests server-tests client-test1
docker compose run --rm tests

Смотрите логи через docker compose logs, останавливайте контейнеры командой docker compose down. После обновления задания скачайте свежий служебный образ: docker compose pull client-test-server.

Ручная отладка своей связки (необязательно)

Запустите свой сервер и два клиента через Compose:

docker compose build server client1
docker compose up -d server client1 client2

Сервер доступен на localhost:51075, клиенты — на localhost:8080 и localhost:8081. Compose запускает клиентов раньше сервера. После правок пересоберите соответствующий образ и повторите up; для логов и остановки используйте команды выше.

Без Docker запустите компоненты и тестер в отдельных терминалах из папки задания. Для официальной Python-заготовки и сервера в solution/server/server.py:

# Терминал 1
python3 -m solution.server.server
# Терминал 2
python3 -m solution.client.client
# Терминал 3
MESSENGER_HTTP_PORT=8081 python3 -m solution.client.client
# Терминал 4
python3 tests/main.py

Если вы добавили зависимости, установите и их. Чтобы проверить один компонент, передайте тестеру --component proto, --component server или --component client. По умолчанию проверяются все компоненты (--component all).

Здесь клиенты работают с вашим сервером, поэтому его ошибки могут повлиять на клиентские тесты. Для независимой проверки клиента используйте Compose со служебным сервером.

Примеры HTTP-запросов к запущенному клиенту:

curl -X POST localhost:8080/sendMessage -d '{"author": "alice", "text": "hey"}'
curl -X POST localhost:8080/getAndFlushMessages

Сдача решения

Подготовьте solution/readme.md и отправьте решение по общей инструкции. В журнале проверки будут вывод сборки, результаты тестов (после строки === RUN TESTS) и логи контейнеров.

Полезные материалы

grpcurl

grpcurl позволяет вызывать gRPC-методы из терминала. Для Linux и Windows скачайте архив для своей ОС и архитектуры со страницы релизов, распакуйте его и добавьте каталог с исполняемым файлом в PATH. В macOS: brew install grpcurl.

Для проверки сервера откройте подписку в одном терминале, а в другом отправьте сообщение:

# Терминал 1: поток остаётся открытым; Ctrl+C отменяет вызов
grpcurl -proto solution/proto/messenger.proto -plaintext localhost:51075 mes_grpc.MessengerServer/ReadMessages

# Терминал 2
grpcurl -proto solution/proto/messenger.proto -d '{"author": "alice", "text": "hello"}' -plaintext localhost:51075 mes_grpc.MessengerServer/SendMessage

Конкурентная обработка в Python

При конкурентном доступе к общим изменяемым данным учитывайте возможные гонки. Структуры данных и способы синхронизации выберите самостоятельно.