Яндекс.Метрика

    Реклама

    Веб-Преферанс на Python

    Не первое десятилетие продолжается спор между архитекторами настольных приложений на предмет, какой же подход, statefull или stateless, более предпочтителен, и в каких случаях.
    Сейчас мы наблюдаем определенный тренд сдачи позиций апологетами statefull. Не в последнюю очередь в этом виноват даже не столько web, сколько банальный HTTP, вполне заслуженно занявший нишу «универсального протокола для всего». Причем возможность передачи Cookie весьма слабо влияет на его насквозь stateless природу.

    Но что HTTP может нам предложить, в случае необходимости создания интерактивного приложения на вебе? Для того чтобы достичь «реактивности» оффлайнового statefull приложения, классического варианта с постоянным опросом состояния сервера короткими AJAX запросами по таймеру явно недостаточно. HTML5 несет нам технологию WebSockets, призванную решить эту проблему. Но ведь нужно чтобы работало «здесь и сейчас» на всех браузерах выпущенных в текущем тысячелетии.

    На помощь приходит технология Comet, при помощи которой мы предприняли попытку реализовать браузерный русский преферанс с мгновенной реакцией на события.

    Итак, знакомьтесь. Клуб интеллектуальных игр «Трельяж»
    trellis-club.com

    Трельяж

    Выбор фреймворка


    На роль низкоуровневой асинхронной библиотеки для проекта проходили кастинг два кандидата.
    Twisted и Tornado. У обоих есть свои плюсы и минусы, по совокупности которых победил Tornado. Он значитаельно уступает своему оппоненту в фичасточти, и что самое неприятное, не умеет «из коробки» делать асинхронные запросы к базе, но… вменяемые html-шаблоны, диспетчеры ресурсов, встроенные средства авторизации на openid серверах, впечатляющая производительность, очень удобный и простой интерфейс для прикладного программиста перевесили чашу на другую сторону.

    Проблема с количеством одновременно открытых соединений


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

    Иными словами, если мы хотим сделать что-то более сложное чем банальный чат, нам придется придумать и реализовать свой высокоуровневый протокол доставки сообщений, работающий поверх JSON.

    Авторизация и Аутентификация


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

    В текущей версии сервера авторизация упразднена за ненадобностью. Все игроки равны и не делятся на какие-либо классы.

    Аутентификация полностью отдана на откуп OpenID протоколу. В состав tornado входят классы аутентификации через Facebook, Gmail, и другими популярными сервисами. Нет никаких проблем дописать доступ к аутентификационным сервисам того же LiveJournal.

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

    Игровой цикл


    Общая схема такова. Интерактивная страница делает «долгий» AJAX запрос. Сервер для каждого игрового стола держит пул этих запросов и ждет событий. Событием может быть что угодно. Игрок нажал на карту, написал сообщение в чат, произошел таймаут, изменился счет пули, и т. д. При поступлении события в очередь, сервер закрывает соединения всех слушателей с ответом что именно произошло. Клиентская страница принимает сообщение и решает что делать дальше. Она может взять данные из сообщения и перерисовать какие-то свои части, может сделать «короткий» AJAX запрос дополнительных данных, может сделать редирект на другую страницу, но в конце-концов она опять посылает «долгий» AJAX запрос который ждет следующего события, и цикл возобновляется.

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

    Веб-сайт


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

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

    База данных


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

    Обычно это обходят использованием асинхронных запросов к базе. Из популярных драйверов такой режим работы поддерживает, например, psycopg2. Можно пойти дальше и воспользоваться услугами «взрослых» ОРМ вроде SQL Alchemy, которые могут добавить слой асинхронности для практически любого драйвера.

    Альтернативой является использование базы данных по минимуму. Игровые процессы целиком происходят в памяти сервера. Вся статистика для просмотра кешируется везде где только можно. В базу информация о сыгранной партии сбрасывается после расчета только один раз. Сессии в общепринятом понимании веб-приложений в базе вообще не нужны при данной модели построения сервера. Главная страница веб-сайта не создает ни единого sql запроса. Все берется из оперативных кешей.

    Таким образом мы сохраняем простоту синхронного программирования, уменьшаем список зависимостей, и дополнительным бонусом идет защита от DDOS. Запросы от незалогиненных игроков в принципе не имеют доступа к ресурсам, способным генерировать SQL запросы.

    Общие данные


    Помимо оперативных данных каждого стола, на сервере хранятся данные, которые должны быть доступны из любого модуля. Это, например, общий список игроков в онлайне, кеши последних просмотренных игр, пул базы данных, и прочее. Данные такого рода собраны в один модуль. Казалось бы вот оно. Наконец-то паттерн Singleton найдет хоть какое-то применение. Но не все так просто. Во-первых — внутри сервера активно используются таймауты из системного модуля threading. А это значит что все наши кеши обязаны быть потокобезопасными. То есть вызовы меняющие их содержимое придется оборачивать в безопасные секции. С другой стороны, нам нужен простой в использовании режим тестов, в котором модуль общих данных должен работать совсем по другому. Поэтому Singleton уходит туда где ему самое место, и на его позицию приходит Facade.

    Таймауты


    Система таймаутов существенно упрощена по сравнению с существующими нативными клиентами. Игрок нажимает кнопку «накрыть стол» При этом стол сразу переходит в состояние «конвенции». Всем игрокам, находящимся в этой комнате посылается соответствующее сообщение, и они сразу видят что статус стола изменился. У накрывающего, средствами jquery-ui, появляется диалоговое окно, в котором он может выбрать игровые конвенции и сверху тикает таймер. По умолчанию 2 минуты. Если за это накрывающий так и не решил во что именно он хочет сыграть, окно закрывается и стол возвращается в статус «свободен» без каких-либо последствий для игрока.

    После того как стол создан, он переходит в режим бесконечного ожидания игроков. Игроки могут подходить к столу, смотреть конвенции, садиться за стол, вставать из-за стола (при этом место освобождается). Если из-за стола встал последний игрок то стол полностью освобождается, и на нем можно накрыть на другую игру.

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

    Любители нечестной игры


    Единственное ограничение звучит так: Один и тот-же игрок не может сесть за один стол дважды. Больше ограничений нет. Если он успевает играть за двумя, и более столами одновременно, то это его право.

    Широко известная в узких кругах «проблема налапа» техническими средствами не решается (кому надо, знают и про анонимные прокси, и поднять несколько виртуальных машин для них не проблема). В этом случае очень важна деанонимность, аудит, система отзывов и рейтинга игроков. Когда вы играете не с обезличенными никами, а с реальными людьми, у которых есть имя и фамилия (предоставляемые open-id consumer, и поменять их на сервере невозможно), то в этом плане все становится намного проще.

    Что дальше?


    На данный момент сервер находится в статусе BETA-тестирования.
    Расположен по адресу trellis-club.com/

    Приятной и доcтавляющей игры.