Добавить в корзинуПозвонить
Найти в Дзене
ZDG

Рогалик: Клиент и сервер

Вся подборка по рогаликам Предыдущая часть: Требуется разработать модули клиента и сервера. Пока что я не использую никакой язык, так как проектирование будет чисто на архитектурном уровне. Программирование начнётся не ранее, чем будет готова архитектура. Итак, чем занимаются клиент и сервер? Клиент Сервер Тут надо отметить, что раз они называются клиент и сервер и что-то друг другу отправляют, то взаимодействие между ними происходит по идее через сеть. Это одна из реализаций, но она не обязана быть именно такой. Взаимодействовать могут и две части одной программы на одном и том же компьютере без всякой сети. Главное здесь то, что они концептуально отделены друг от друга. Клиент ничего не знает об игровой логике, а сервер ничего не знает о том, как отображается состояние игры и как обслуживаются устройства ввода игрока. Состояние, Запрос, Ответ Из вышеперечисленных списков можно выделить сущности – Состояние игры, Запрос клиента, Ответ сервера. Состояние игры – назовём его классом Game
Оглавление

Вся подборка по рогаликам

Предыдущая часть:

Требуется разработать модули клиента и сервера. Пока что я не использую никакой язык, так как проектирование будет чисто на архитектурном уровне. Программирование начнётся не ранее, чем будет готова архитектура.

Итак, чем занимаются клиент и сервер?

Клиент

  • Получает состояние игры
  • Отображает на экране состояние игры
  • Обслуживает ввод с устройства игрока (клавиатура, мышь и т.д.)
  • Транслирует ввод игрока в запросы к серверу

Сервер

  • Владеет состоянием игры и игровой логикой
  • Отправляет состояние игры на клиент
  • Получает запросы от клиента
  • Меняет состояние игры в соответствии с запросами и игровой логикой
  • Отправляет клиенту частичные обновления состояния игры

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

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

Состояние, Запрос, Ответ

Из вышеперечисленных списков можно выделить сущности – Состояние игры, Запрос клиента, Ответ сервера.

Состояние игры – назовём его классом GameState – это совокупность всех игровых параметров на данный момент. Текущая карта уровня, открытые клетки, параметры игрока, местонахождение монстров и т.д.

Клиент в начале игры должен получить GameState, чтобы что-то нарисовать.

  • В игре, сделанной традиционным способом в виде единой неделимой программы, вопросов бы не было. Состояние игры доступно из любого места программы. Одна часть кода его меняет, и все изменения сразу же становятся доступны для другой части кода.
  • Если клиент и сервер логически разделены, но находятся в одной программе, проблем по-прежнему нет. Они оба имеют доступ к GameState, но теперь клиенту можно его только читать. Чтобы изменить состояние, он должен вызвать метод сервера, сервер внесёт изменения, и потом клиент может считать эти изменения прямо из GameState.
  • Если же клиент и сервер реально удалены друг от друга, начинаются проблемы. Теперь актуальный объект GameState находится на отдельном сервере, и клиент не может его даже читать. Он должен открыть канал связи с сервером, послать какой-то запрос и получить ответ, в котором будут содержаться данные о GameState.

Далее, при каких-то действиях в клиенте он может каждый раз получать GameState целиком для обновления. Но по факту придётся гонять по сети слишком много данных (если, конечно, планировать с размахом) каждый ход, что выглядит неправильным. Поэтому нужен какой-то протокол, который пересылал бы не состояние целиком, а только изменённые данные. И клиент должен уметь применять эти частичные изменения к своей текущей копии GameState.

Это радикально другой способ обмена, поэтому версии клиента с локальным и удалённым сервером не будут совместимы как по методам вызова, так и по представлению данных. Но нас это не устраивает.

Прокси

С самого начала мы хотели, чтобы клиент вообще не знал, с каким именно сервером он работает, и с сервером ли.

Поэтому между клиентом и сервером я внедряю посредника – Прокси. Его задача – транслировать запросы клиента на сервер и ответы сервера обратно на клиент. При этом ни клиент, ни сервер не должны знать, с каким каналом связи они работают.

Таким образом, меняя прокси, можно менять каналы связи. А все необходимые преобразования структур данных будут происходить внутри конкретного прокси. Он будет приводить их к общему формату, а клиент и сервер будут работать так, как будто они находятся в одной программе.

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

Прокси здесь – общий объект для клиента и сервера. И также у них общее GameState. Клиент знает о прокси и GameState, сервер знает о прокси и GameState, а прокси знает о сервере и клиенте.

Клиент отправляет запрос на прокси, чтобы что-то сделать. Прокси перекидывает запрос на сервер. Сервер что-то делает, меняет GameState и отправляет прокси ответ. Прокси перекидывает ответ клиенту. Клиент берёт изменённое GameState и рисует что-то.

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

Теперь посмотрим схему сетевого взаимодействия, когда клиент и сервер находятся на разных машинах:

-2

Здесь два экземпляра прокси, по одному на каждой машине. Они связаны друг с другом через некое соединение (connection), по которому идёт обмен данными.

Оригинал GameState находится на сервере, и к нему имеют доступ сервер и серверная прокси.

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

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

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

При этом клиент может не иметь (и более того, не должен иметь) полной копии GameState. Например, на сервере есть уже полностью заполненная карта подземелья, но в клиентской копии есть только её часть.

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

Читайте дальше: