- PVSM.RU - https://www.pvsm.ru -
Разработал и запустил на домене quickpong.com [1] онлайн версию игры Pong. В игре (by design) реализован только режим мультиплейера, то есть игра идет не против искусственного интеллекта, а против другого человека.
Игра представляет из себя клиент-серверное приложение, серверная часть написана на питоновском фреймворке Twisted [2], клиентская — на флэшовом фреймворке FlashPunk [3].
Это мой первый опыт разработки асинхронного сетевого приложения, способного обслуживать тысячи одновременных подключений. Далее я расскажу о том, как эта программа работает, с какими проблемами мне пришлось столкнуться при разработке, какие идеи я хотел реализовать и что в итоге осталось нереализованным. Возможно, мой опыт окажется для кого-нибудь полезным.
На игровом поле слева и справа находятся 2 доски, способные перемещаться по вертикали. Каждой из досок управляет человек. Между досками перемещается шарик, отскакивающий от стенок игрового поля и досок. Задача игрока — не допустить попадания шарика за стенку, возле которой находится его доска.
При освоении новых технологий и инструментов огромную роль играет наличие грамотной подробной документации. Выбирая серверный фреймворк я рассматривал питоновские Twisted, Tornado и, находящийся у всех на слуху, Node.js.
Исходя из того, что у меня вообще не было опыта разработки подобных приложений, мой выбор пал на Twisted. Для него написан очень подробный вводный курс [4], объясняющий азы разработки асинхронных приложений не зависимо от какого-либо фреймворка или языка программирования, а также, разумеется, рассказывающий об использовании самого Twisted. Настоятельно рекомендую этот курс всем желающим постичь основы разработки асинхронных приложений. По ссылке выше вы сможете найти и перевод этого курса на русский язык.
В этом проекте для меня основной интерес представляла разработка серверной части, по этому для разработки клиента я решил особо не заморачиваться и сделать его на флэше, опыт работы с которым уже имел, с использованием фреймворка FlashPunk.
К выбору алгоритма взаимодействия между клиентом и сервером я подошел не так вдумчиво как к выбору фреймворков. В итоге рабочую версию алгоритма я реализовал лишь с третьего раза, причем первые две версии алгоритма выдумал сам и после двух неудач третью версию нашел в интернете.
Основной трудностью в подобных приложениях является синхронизация клиентов, нельзя допустить, чтобы в один и тот же момент времени один клиент видел одну картинку, второй клиент — другую.
Кроме того, хотелось бы избежать мошенничества со стороны клиентов: при желании и умении клиентская swf-ка может быть модифицирована и может передавать на сервер читерские данные.
Таким образом, в первой версии алгоритма я решил действовать так:
Алгоритм получился нерабочим. Он мог бы быть рабочим только в случае, если все 3 участника обмена данными имели бы одинаковое состояние. На практике в 100% случаев был рассинхрон и играть было невозможно.
Во второй версии алгоритма я решил избавиться от одного из звеньев — от расчета состояния игрового мира на сервере:
Этот подход тоже получился нерабочим. Даже клиенты запущенные на двух идентичных машинах рассчитывали состояние игрового мира немного по разному и, несмотря на то, что теперь у меня сравнивалось 2 состояния, а не 3, как в предыдущем случае, все равно состояние одного из клиентов постоянно принудительно изменялось, выглядело это как заметные “скачки” шарика и играть было невозможно.
Стало ясно, что, во-первых, состояние игрового мира должно рассчитываться только в одном месте, на сервере, и, во-вторых, перед продолжением экспериментов, чтобы избежать подобной пустой траты времени, мне стоит изучить теорию и опыт других разработчиков.
Погуглив я нашел такие вот интересные ссылки:
Алгоритм описанный по последней ссылке и был взят мною для этого проекта. Суть его такова:
Исходники сервера и клиента я выложил на Гитхабе:
На этой схеме: quickpong.com/images/quickpong.png [11] показана логика взаимодействия клиента и сервера.
С точки зрения реализации сервера все достаточно прозрачно. На 10080 порту стартует реактор (event loop), в котором работает серверная фабрика — класс QuickpongServerFactory [12]. При инициализации фабрики создается экземпляр класса Quickpong [13], который содержит всю логику по взаимодействию сервера с клиентом.
При подключении нового клиента фабрика вызывает метод buildProtocol [14] и создает для каждого присоединившегося клиента экземпляр класса QuickpongProtocol [15], созданный объект передается в Quickpong. Таким образом, объект класса Quickpong имеет доступ ко всем присоединившимся клиентам и может выполнять с ними необходимую работу: объединять в пары, обсчитывать состояние игрового мира и т.д.
Объект класса QuickpongProtocol содержит только методы для получения и передачи данных от/к клиенту.
С реализацией клиента тоже всё просто, единственным интересным моментом было следующее. Используя FlashPunk я могу задать частоту обновления картинки (FPS), при этом FlashPunk может гарантировать, что он отрисует N кадров в секунду, но не может гарантировать того, что каждый кадр будет отрисован за 1/N секунды. То есть при FPS 50 в идеальном случае каждый кадр должен отрисовываться за 20 мс, в реальном случае один кадр может быть отрисован за 15 мс, а другой за 25 мс. Если шарик двигается с постоянной скоростью, например 10 пикселов в секунду, и отрисовка каждого дата-фрейма сопадает с отрисовкой кадра ФлэшПанком, то шарик будет двигаться неравномерно, рывками, так как в одном случае он переместится на 10 пикселов за 15 мс, а в другом за 25.
Эту особенность пришлось учесть в клиенте [16] и перед отрисовкой кадра я проверяю сколько времени прошло с момента отрисовки предыдущего кадра, на основании этого я определяю рендерить дата-фрейм полностью или частично.
Самый интересный для меня вопрос — сколько игроков онлайн сможет выдержать этот сервер? Для теста я написал на питоне небольшой клиент [17], который эмулировал действия человека.
Тест проводил на виртуальной машине, под которую выделено 1 ядро Intel® Xeon® CPU E31275 @ 3.40GHz.
Средствами того же Twisted я повесил на 10082 порт веб-сервер, который выводит через запятую число юзеров онлайн и число активных игр. На основе этой информации, а также при помощи питоновской библиотеки psutil [18] и связки rrdtool [19] + py-rrdtool [20] я написал скрипты [21], которые выводят информацию о текущем числе пользователей онлайн и потребляемых ресурсах в удобоваримом виде: quickpong.com/stats.html [22] (картинки обновляются раз в минуту).
При 5000 (5 тысяч) игроков программа отъедает около 100 Мб оперативной памяти, грузит CPU в среднем на 30-40%.
Идеи, которые остались нереализованными:
На данный момент я потерял интерес к развитию этого проекта, возможно мои наработки покажутся кому-нибудь познавательными или интересными, по этому я выложил все исходники на Гитхаб:
Автор: rrromka
Источник [23]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/python/34740
Ссылки в тексте:
[1] quickpong.com: http://quickpong.com
[2] Twisted: http://twistedmatrix.com/
[3] FlashPunk: https://github.com/ChevyRay/FlashPunk
[4] вводный курс: http://krondo.com/?page_id=1327
[5] stackoverflow.com/questions/1411745/multiplayer-game-synchronization: http://stackoverflow.com/questions/1411745/multiplayer-game-synchronization
[6] www.gamasutra.com/view/feature/3230/dead_reckoning_latency_hiding_for_.php: http://www.gamasutra.com/view/feature/3230/dead_reckoning_latency_hiding_for_.php
[7] www.kirupa.com/forum/showthread.php?297510-Synchronizing-Realtime-Multiplayer-games: http://www.kirupa.com/forum/showthread.php?297510-Synchronizing-Realtime-Multiplayer-games
[8] developer.valvesoftware.com/wiki/Source_Multiplayer_Networking: https://developer.valvesoftware.com/wiki/Source_Multiplayer_Networking
[9] github.com/romka/quickpong: https://github.com/romka/quickpong
[10] github.com/romka/quickpong_flash_client: https://github.com/romka/quickpong_flash_client
[11] quickpong.com/images/quickpong.png: http://quickpong.com/images/quickpong.png
[12] QuickpongServerFactory: https://github.com/romka/quickpong/blob/master/quickpong/serverfactory.py
[13] Quickpong: https://github.com/romka/quickpong/blob/master/quickpong/quickpong.py
[14] buildProtocol: https://github.com/romka/quickpong/blob/master/quickpong/serverfactory.py#L49
[15] QuickpongProtocol: https://github.com/romka/quickpong/blob/master/quickpong/protocol.py
[16] пришлось учесть в клиенте: https://github.com/romka/quickpong_flash_client/blob/master/src/Game.as#L186
[17] небольшой клиент: https://github.com/romka/quickpong/blob/master/quickpong_test_client.py
[18] psutil: https://code.google.com/p/psutil/
[19] rrdtool: http://oss.oetiker.ch/rrdtool/
[20] py-rrdtool: https://pypi.python.org/pypi/py-rrdtool
[21] скрипты: https://github.com/romka/quickpong/blob/master/scripts/rrdget.py
[22] quickpong.com/stats.html: http://quickpong.com/stats.html
[23] Источник: http://habrahabr.ru/post/180319/
Нажмите здесь для печати.