За свою недолгую жинь я ни разу не встречал программиста, который бы не любил игры. И уж тем более, программиста, который никогда их не писал.
Кто-то начинает с тетриса, кто-то со змейки. У кого-то это увлечение проходит, а кто-то этим «заболевает» и превращает свою болезнь в любимую работу или занятное хобби.
В эру интернета и социальных сетей играть одному неинтересно, хочется общаться и играть вместе с друзьями.
И не просто общаться, а ходить группой в подземелье или показать кто на арене хозяин.
В данной статье я бы хотел рассказать о своем подходе к серверной реализации такого взаимодействия.
Рассмотрим пример простого игрового приложения, в котором игрок управляет персонажем, который может покупать\продавать предметы, сражаться на арене, ходить в подземелья один или с друзьями.
Например, игрок создает группу для похода в подземелье. Игрок посылает запрос нашему серверу, который добавляет новую группу в список доступных групп для походов, после чего остальные игроки имеют возможность получить этот список и присоединиться. Группа заполняется, и, нажатием кнопки, создатель отправляет ее в подземелье на встречу опасным монстрам.
Казалось бы, все просто. Создаем таблицу в БД, записываем туда желающих и отправляем в подземелье.
Так думают многие начинающие программисты, но забывают про самую главную проблему многозадачных систем — 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'а, когда объем используемой памяти достигает определенного предела.
Наиболее правильным решением я вижу использование языка программирования Erlang\OTP.
Поставленная задача отлично укладывается в его концепцию FSM/gen_server, чем автор и планирует заняться в ближайшее время.
Надеюсь, что статья поможет начинающим разразботчикам игр не наступать на грабли и правильно проектировать свое приложение.
P.S. Если такого рода тематика интересна жителям Хабра, то готов более детально рассказать о каждом из компонентов такой системы.
Кто-то начинает с тетриса, кто-то со змейки. У кого-то это увлечение проходит, а кто-то этим «заболевает» и превращает свою болезнь в любимую работу или занятное хобби.
В эру интернета и социальных сетей играть одному неинтересно, хочется общаться и играть вместе с друзьями.
И не просто общаться, а ходить группой в подземелье или показать кто на арене хозяин.
В данной статье я бы хотел рассказать о своем подходе к серверной реализации такого взаимодействия.
Задача
Рассмотрим пример простого игрового приложения, в котором игрок управляет персонажем, который может покупать\продавать предметы, сражаться на арене, ходить в подземелья один или с друзьями.
Концепция
Например, игрок создает группу для похода в подземелье. Игрок посылает запрос нашему серверу, который добавляет новую группу в список доступных групп для походов, после чего остальные игроки имеют возможность получить этот список и присоединиться. Группа заполняется, и, нажатием кнопки, создатель отправляет ее в подземелье на встречу опасным монстрам.
Казалось бы, все просто. Создаем таблицу в БД, записываем туда желающих и отправляем в подземелье.
Так думают многие начинающие программисты, но забывают про самую главную проблему многозадачных систем — 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'а, когда объем используемой памяти достигает определенного предела.
Наиболее правильным решением я вижу использование языка программирования Erlang\OTP.
Поставленная задача отлично укладывается в его концепцию FSM/gen_server, чем автор и планирует заняться в ближайшее время.
Надеюсь, что статья поможет начинающим разразботчикам игр не наступать на грабли и правильно проектировать свое приложение.
P.S. Если такого рода тематика интересна жителям Хабра, то готов более детально рассказать о каждом из компонентов такой системы.