/nltt

Primary LanguageRust

Readme

Преамбула

Я не выкладываю в открытый доступ текст задания и использую репозиторий с обфуцированным названием. Могу удалить этот код после просмостра, чтобы не оставлять решенное тестовое в сети.

Краткое описание

Краткое описание того, что получилось.

  • Клиент-серверное приложение на TCP. В своем составе имеет: сервер состоящий из двух серверных процессов, один обслуживает игру (поддерживает авторизацию по ТЗ), другой статистику (для простоты, он не поддерживает авторизацию). Игрового клиента. Два скрипта для запроса статистики с сервера статистики.
  • Все выборы были сделаны на мое усмотрение. Tcp vs Udp vs Grpc/ tokio vs async / HashMap vs VecDeque. Если появлялся вопрос по внутренней логике приложения, то я делал выбор и старался комментировать его в коде, рядом с реализацией.
  • Фреймовый протокол поверх TCP. Можно было разработать разные протоколы для игры и для апи-сервера, но в целях простоты и экономии времени проект реализует единный протокол.
  • Все реализовано с использованием экосистемы Tokio (tokio-core, tokio-streams, tokio-codec, etc)
  • Приложение не использует никакие внешние хранилища, все реализовано in-memory (можно было бы статистику вынести в отдельное хранилище со своим API, но насколько я понимаю не в этом суть задания)
  • Минимально используются внешние пакеты. Только в одном месте не хотелось писать linked-hash-map, поэтому был взят готовый пакет.
  • В первую очередь выполнялись функциональные требования, во вторую учитывалась производительность.
  • Приложение не доработано до боевого состояния. Например список всех пользователей хранится в памяти и если приложение будет работать бесконечно долго, то оно рано или поздно упадет по OOM. В клиентской части имеются необязательные unwrap(), поэтому клиент может запаниковать в случае, если сервер упал. На сервере я постарался правильно обработать ошибки, в случае плохих клиентов, но некоторые send/await все равно игнорируют result и потребуют доп. полировки
  • Местами имеется избыточное копирование данных, для основных структур данных можно рассмотреть альтернативные варианты. Например использовать dashmap, где-то можно запараллелить обработку. Но это потребует каких-то бенчмарков и замеров, а соответственно времени, поэтому все структуры выбраны на глаз и в основном это std коллекцию обернутые мьютексом и аркой.
  • Токен авторизации используется как id клиента. В данной интерпретации это было допустимо, но если придумать дополнительные ограничения, то такой вариант может не подойти.
  • Весь код снабжен подробными комментариями

Приятного просмотра!

Как запустить

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

GAME_SERVER_PORT=8000 API_SERVER_PORT=8010 RUST_LOG="debug" cargo run --bin server

Клиент.

GAME_SERVER_PORT=8000 RUST_LOG="debug" cargo run --bin client

Я не принял решение относительно того куда положить токены(ключи/подписи), поэтому добавил опциональную ENV-переменную SIGNATURE для этой цели, для удобства - примеры команд с ключами ниже. При подключении клиент напечатает под какой подписью он подключился.

SIGNATURE=96a9354f-a8bc-4895-8317-61bf73f127c8 GAME_SERVER_PORT=8000 RUST_LOG="debug" cargo run --bin client
SIGNATURE=fc5d5532-d8c1-4a7d-b035-c2edc8ba66c6 GAME_SERVER_PORT=8000 RUST_LOG="debug" cargo run --bin client

Статистика c логом всех побед. Скрипт идет на апи сервер и печатает статиситку в stdout.

API_SERVER_PORT=8010 RUST_LOG="debug" cargo run --bin get_wins_log

Статистика по пользователям. Скрипт идет на апи сервер и печатает статиситку в stdout.

API_SERVER_PORT=8010 RUST_LOG="debug" cargo run --bin get_sorted_winners

Как запустить множество клиентов это выбор пользователя. Я просто открываю в разных табах консоли.