Pull to refresh

Реализация серверной части в многопользовательских онлайн играх

Reading time 3 min
Views 16K
За свою недолгую жинь я ни разу не встречал программиста, который бы не любил игры. И уж тем более, программиста, который никогда их не писал.
Кто-то начинает с тетриса, кто-то со змейки. У кого-то это увлечение проходит, а кто-то этим «заболевает» и превращает свою болезнь в любимую работу или занятное хобби.

В эру интернета и социальных сетей играть одному неинтересно, хочется общаться и играть вместе с друзьями.
И не просто общаться, а ходить группой в подземелье или показать кто на арене хозяин.
В данной статье я бы хотел рассказать о своем подходе к серверной реализации такого взаимодействия.



Задача


Рассмотрим пример простого игрового приложения, в котором игрок управляет персонажем, который может покупать\продавать предметы, сражаться на арене, ходить в подземелья один или с друзьями.

Концепция


Например, игрок создает группу для похода в подземелье. Игрок посылает запрос нашему серверу, который добавляет новую группу в список доступных групп для походов, после чего остальные игроки имеют возможность получить этот список и присоединиться. Группа заполняется, и, нажатием кнопки, создатель отправляет ее в подземелье на встречу опасным монстрам.

Казалось бы, все просто. Создаем таблицу в БД, записываем туда желающих и отправляем в подземелье.
Так думают многие начинающие программисты, но забывают про самую главную проблему многозадачных систем — race condition.

Логично предположить, что правильной реализацией такого взаимодействия был бы серверный демон, который отвечал за состояние игрового мира, принимал запросы от игроков (в порядке очереди), обрабатывал их и отдавал результат.
В нашем случае сервер должен хранить текущее состояние игроков, чтобы иметь возможность корректно обрабатывать результаты.

Схематично это выглядит так:

Базовая схема нашего демона

Пожалуй, самое интересное — это обработчик запросов.

Он содержит в себе основную логику нашего сервера:
— хранение состояния игроков (свободен, находится в подземелье, в группе и т.д.)
— хранение и обработка информации об игровом мире (битвы на арене, походы в подземелья, группы для похода)

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

Кроме того, на сервере должна производиться обработка игрового мира.
Допустим, мы пошли в подземелье на 5 минут. За 5 минут сервер должен произвести обработку битвы с монстрами, посчитать опыт, дроп, а также изменить состояние игрока.

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

Реализация



Для реализации такого демона был выбран язык PHP 5.3 с расширением libevent, как наиболее знакомый автору.
Про libevent существует много статей, создан небезызвестный phpDaemon, поэтому углубляться в его работу не имеет смысла.
Стоит только отметить возможность создания отложенных событий (флаг EV_TIMEOUT), что в нашем случае решает очень много проблем.

Однако, наш сервер должен, кроме всего прочего, достаточно активно работать с БД для записи результатов, покупки вещей и т.д.
Как известно, БД — узкое место любого серьезного приложения, а наш сервер может «лечь» от большого количества запросов.

Поэтому для обработки «тяжелых» запросов можно предусмотреть дополнительный серверный демон с необходимым нам числом нитей (thread/workers), которые будут с радостью выполнять всю кропотливую работу.

Дополнительный демон

Стоит отметить, что запросы к work daemon так же становятся в очередь и не обрабатываются, пока мы не получим ответ.
Принцип работы work daemon такой же, как и основного демона, за исключением наличия нескольких нитей (thread/workers) для обработки запросов.

Выводы



Достоинства такого подхода заключаются в следующем:
— всю тяжелую работу мы сбрасываем на work daemon, а обработку состояний игроков оставляем на нашем основном демоне, что делает его очень легковесным и быстрым
— work daemon можно вынести на отдельный сервер, в случаем необходимости, а количество workers варьировать в зависимости от железа
— возможность масштабирования

А теперь о недостатках.
Самый главный недостаток реализации: PHP течет. Даже новый сборщик мусора в 5.3 не решает всех проблем. И мы имеем:

Состояние памяти на каждом worker

Решение: периодически перегружать worker'а, когда объем используемой памяти достигает определенного предела.

Наиболее правильным решением я вижу использование языка программирования Erlang\OTP.
Поставленная задача отлично укладывается в его концепцию FSM/gen_server, чем автор и планирует заняться в ближайшее время.

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

P.S. Если такого рода тематика интересна жителям Хабра, то готов более детально рассказать о каждом из компонентов такой системы.
Tags:
Hubs:
+153
Comments 76
Comments Comments 76

Articles