Хендлер для браузерных онлайн игр и групповых чатов

14 minute read

NB! Данный хендлер предназначен для работы только с вебсокетами golang.org/x/net/websocket. Хотя, если ваше приложение не использует вебсокеты или использует вебсокеты из другого пакета, но есть необходимость объединять гороутины обработчиков в группы с общими данными, то, наверное, вам может быть полезен данный велосипед в качестве примера, если вы еще не написали подобный и если не знаете способа по-лучше конечно.

Введение

Когда функциональная нагрузка распределена по модулям программы не правильно, то переписывание или дописывание программы превращается в настоящий ад. Так вот, при написании сервера для браузерной онлайн игры или группового чата (или чего-нибудь подобного) не следует смешивать:

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

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

Интерфейсы

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

1) Интерфейс для общих данных группы ("окружающая среда участника группы"). Общими данными может быть что угодно.

type Environment interface{}

2) Интерфейс менеджера групп. Этот объект должен хранить группы и управлять ими. Именно он должен решать, когда группа должна быть добавлена, а когда удалена, и именно он распрелеляет юзеров по группам.

type PoolManager interface {
    // AddConn должен найти подходящую группу для соединения и 
    // добавить туда информацию о нем, и вернуть общие данные
    // окружения группы. Также AddConn добавляет группы если надо
    AddConn(ws *websocket.Conn) (Environment, error)
    // DelConn удаляет информацию о соединении из группы. Может, в
    // случае если группа стала пустой удалить группу
    DelConn(ws *websocket.Conn) error
}

3) Интерфейс обработчика. Этот объект занимается работой с соединениями. Он получает соединение и общие данные группы и делает все что нужно, как обычный хендлер.

type ConnManager interface {
    // Handle - это обработчик. data - это общие данные группы
    Handle(ws *websocket.Conn, data Environment) error
    // HandleError для информирования пользователей об ошибках
    // Можно использовать для того, чтобы записать инфу об ошибке в
    // нужный лог, и написать что-то пользователю. Но не для закрытия
    // соединения (следуем принципу: не я открыл, не мне закрывать)
    HandleError(ws *websocket.Conn, err error)
}

4) Интерфейс для проверяльщика. Проверяльщик вызывается первым и может пообщавшись с клиентом по вебсокету либо авторизовать пользователя, вернув nil, либо не авторизовать, вернув ошибку. Эта ошибка будет передана в ConnManager.HandleError(), который передаст нужную информацию пользователю, запишит что-либо в лог, и, после того как HandleError завершится, соединение будет закрыто.

type RequestVerifier interface {
    // Verify проверяет соединение и возвращает ошибку в случае, если
    // что-то не так.
    Verify(ws *websocket.Conn) error
}

А так выглядит конструктор хендлера:

// PoolHandler создает хендлер с указанными менеджером групп,
// менеджером соединений и проверяльщиком. Проверяльщик может быть
// nil, и тагда соединения не будут проверяться
func PoolHandler(poolMgr PoolManager, connMgr ConnManager,
    verifier RequestVerifier) http.Handler {
}

Использование

Итак, мы имеем:

  • RequestVerifier - займется логикой авторизации
  • PoolManager - займется логиками управления группами и инициализации общих данных группы. Здесь следует использовать фабрики (или одну фабрику) для генерации групп и генерации общих данных. Фабрики указываем при инициализации самого PoolManager. То есть тут лучше разделить: добавление и удаление групп - это задачи для PoolManager, а деталями инициализации занимаются фабрики.
  • ConnManager займется работой с соединением. В нем же инициализируем данные участника, тоже обратившись к фабрике, которая сделает все, что нужно. Фабрику указываем при инициализации ConnManager.

Ссылки