Дипломная работа: Разработка многопользовательской ролевой онлайн игры World of Magic

Внимание! Если размещение файла нарушает Ваши авторские права, то обязательно сообщите нам

3.1.2 Описание серверной части приложения

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

Серверное приложение работает с двумя потоками и с двумя объектами - game и network. Объект game хранит в себе состояние игры и обрабатывает игру. Объект network отвечает за то, чтобы у игроков в клиентском приложении было точно такое же состояние игры, как и на сервере. Вся информация о состоянии игрового мира хранится в базе данных и периодически обновляется. Первый поток обрабатывает функцию network_handler(), которая отвечает за сетевую обработку, а второй поток обрабатывает функцию game_handler(), которая отвечает за игровую обработку. В функции main() создаются и настраиваются эти самые потоки. Также настраивается объект network - ему задаётся номер порта и добавляется слушатель на этот порт. Код функции main() представлен на рисунке 3.4.

Рисунок 3.4 Главная функция

Сетевой обработчик всего лишь запускает функцию обработки сервера server_accept() объекта network. Реализацию сетевого обработчика можно посмотреть на рисунке 3.5.

Рисунок 3.5 Сетевой обработчик

Игровой обработчик включает в себя три переменные и игровой цикл. Сначала создаётся игровое окно - window, затем переменная для хранения времени между обработками приложения - time, и clock - счётчик времени, класс предоставленный библиотекой SFML для подсчёта времени. Игровой цикл работает до тех пор, пока игровое окно не будет закрыто. В начале цикла в переменную time записывается время, которое отсчитал счётчик после последнего обнуления счётчика, после чего счётчик обнуляется, для засекания очередного промежутка времени. Когда промежуток времени стал известен, ссылка на окно и время передаются в игру для дальнейшей обработки с помощью функции behavior() объекта game. Реализация игрового обработчика представлена на рисунке 3.6.

Рисунок 3.6 Игровой обработчик

Объект network включает в себя два объекта - listener и selector, предоставленные библиотекой SFML, которые необходимы для организации сетевого взаимодействия. Также объект network содержит массив всех текущих подключений - sockets, и массив для накопления пакетов для отправки - listOfPackets. Вся информация о клиентах и функционал для работы с ними хранится в базе клиентов - base. Функционал для обработки пакетов хранится в объекте lib. Структура Network представлена на рисунке 3.7.

Рисунок 3.7 Структура Network (Network.h)

В структуре Network также присутствуют четыре важных функции, для организации сетевого взаимодействия:

- server_accept

- handler_packets

- send

- sendAll

Handler_packets() - отвечает за обработку принятых пакетов в зависимости от кода пакета. В зависимости от кода пакета вызывается соответствующая функция, которая была привязана к этому коду. Все функции вызываются из объекта lib. В функции в качестве параметров передаются подключение(сокет и клиент) и сам пакет. Функция handler_packets() представлена на рисунке 3.8.

Рисунок 3.8 Функция handler_packets

Функция server_accept для понятности и наглядности представлена в виде блок-схемы в приложение 1. Объявление функции изображено на рисунке 3.9.

Рисунок 3.9 Объявление функции server_accept

Функция send() рассылает накопленные пакеты (Рисунок 3.10).

Рисунок 3.10 Функция send рассылающая накопившиеся пакеты LibPackets

В структуре Network присутствует структура LibPackets, которая содержит функционал для обработки пакетов. Код структуры представлен на рисунке 3.7. Почти каждая функция принимает входным параметром Connect (сокет и клиент) и packet (пакет). Также структура имеет ссылку на объект Network, чтобы иметь обратную связь с сетевым интерфейсом.

Рисунок 3.11 Структура LibPackets (Network.h)

Функции названы так, чтобы отразить их назначение:

registration - регистрация на сервере

login - авторизация

logout - выход с сервера

set_nickname - установить имя персонажа

connect_client - оповещение уже подключенных клиентов о подключении другого клиента

disconnect_client - оповещение подключенных клиентов об отключении другого клиента

clients_pos_all - послать координаты всех игроков игроку, который только что подключился

nextpos - оповестить всех клиентов о том, что игрок начал перемещение

message - оповестить всех клиентов о том, что игрок что-то написал в чат.

3.2 Клиентское приложение

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

Сначала мы подключаемся к серверу с помощью функции connection. После того как успешно подключились, мы запускаем поток обработки пакетов и продолжаем выполнять функцию main (Рисунок 3.12)

Рисунок 3.12 Начало функции main

После того как клиент подключился к серверу, можно регистрироваться или авторизоваться на сервере. Это делается с помощью ввода логина и пароля в консоль. Сначала идёт регистрация, потом авторизация (Рисунок 3.13).

Рисунок 3.13 Код отвечающий за регистрацию и авторизацию на сервере

Игровой цикл сводится к тому, что игра обрабатывается и рисуется с течением времени. Игровой цикл можно увидеть на рисунке 3.14. В переменной time накапливается время, между итерациями цикла, и, ориентируясь на это время, игра обеспечивает плавность и равномерность движения.

Рисунок 3.14 Игровой цикл

4. Тестирование

Любое разрабатываемое приложение нуждается в качественном тестировании, как на стадии разработки, так и на этапе финальных тестирований. Поэтому разработка функционала для тестирования одна из обязанностей разработчика.

4.1 Функциональное тестирование

Универсальное средство для получения информации о состоянии приложения является консоль [11]. В консоль можно вывести любые данные или узнать, в каком направлении начал выполняться алгоритм.

4.1.1 Тестирование сервер приложения

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

Для тестирования запросов к базе данных была специально разработана функция query, которая принимает параметр тестирования flag. Если его установить в true, то в консоль будет выдаваться информация о выполнении запроса. На рисунке 4.1. изображена консоль, в которую выводится информация о клиентах, которые имеются на начало старта сервера и после окончания его работы, путём закрытия окна приложения.

Рисунок 4.1 Скриншот консоли сервер приложения

Сначала выводиться сам запрос, потом его результат (true - выполнен, false - ошибка запроса), после чего выводятся данные, если запрос предусматривал выборку.

Тестирование сетевого модуля начнём с авторизации (Рисунок 4.2.). Сначала включим сервер. Затем через клиента попробуем перебрать все варианты авторизации: ошибка ввода логина, ошибка ввода пароля, успешная авторизация и когда клиент уже онлайн и если нельзя зайти на аккаунт. Когда клиент не авторизирован, он имеет идентификатор -1. После того как клиент успешно авторизовался, ему выдаётся идентификатор клиента, к которому пытался получить доступ игрок. Всё работает, так как должно. Если игрок вводит ошибочные данные, ему выдаётся соответствующий пакет с оповещающей информацией. Если данные вводятся правильно, то игрок получает данные об игре и своём персонаже, а подключённые игроки информацию об этом персонаже.

Рисунок 4.2 Скриншоты консолей при авторизации игроков

Теперь протестируем перемещение персонажа. На рисунке 4.3. изображена схема передачи сообщений между клиентами и сервером. Зелёными линиями и стрелками обозначены пакеты, которые идут на сервер, а красными - от сервера. Хронологический порядок пакетов лучше смотреть на сервере (слева на картинке 4.3.). Обозначение c>s означает, что сообщение пришло от Client к Server, и наоборот, s>c - от Server к Client. Первым делом игрок с id=7 присылает пакет на перемещение, после чего ответный пакет рассылается всем подключённым клиентам (в нашем случае id=6 и id=7). Далее принимается пакет на перемещение от игрока с id=6, и происходит тоже самое.

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

Рисунок 4.3 Схема передачи пакетов

По скриншоту видно, что координаты точки, куда должен двигаться игрок передаются правильно.

4.1.2 Тестирование клиентского приложения

Задача клиентского приложения - правильно отображать информацию с сервера и правильно посылать сигналы от клиентского приложения на сервер [12]. Для этого надо убедиться в том, что данные на сервере и на клиенте совпадают. Для сравнения подойдут координаты игрока. На рисунке 4.4 изображён персонаж со своими координатами, и предполагаемая точка, куда персонаж будет двигаться. Предполагается, что координаты у этой точки 300:300. После этого произведём нажатие ЛКМ по этой точке, и нам с сервера должен прибыть пакет, приказывающий двигаться в эту точку (Рисунок 4.5). Он пришёл, но координаты немного отличаются.

Рисунок 4.4 Персонаж, его координаты и следующий пункт назначения

Рисунок 4.5 Скриншот консоли с пакетами на перемещение

На рисунке 4.6. видно как персонаж встал в те координаты, в которые указал игрок. Отсюда следует, что клиентское приложение правильно обрабатывает запросы на перемещение персонажа.

Рисунок 4.6 Персонаж в новых координатах

Немаловажным остаётся тот факт, что приложение тестируется на одном компьютере. Для того, чтобы любой пользователь сети интернет мог подключиться к сервер-приложению, необходимо сервер-приложение разместить либо на оплачиваемом хостинге, либо к компьютеру, на котором запущено сервер-приложение подключить кабельный интернет. После чего узнать IP адрес и настроить этот адрес в клиентском приложении. Если запустить сервер-приложение на компьютере, который подключен только к сети Wi-Fi, то он будет виден клиентским приложениям, находящимся в этой Wi-Fi сети.

4.2 Нагрузочное тестирование

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

Первым делом нам нужно автоматически авторизоваться в игре, для этого была создана функция auto_reg_login, которая посылает сигнал на сервер, сообщающий, что последний должен автоматически зарегистрировать и авторизовать в игре клиента, и выдать ему персонажа. Сервер выполняет поиск свободных клиентов, и рассылает всем информацию о том, что якобы клиент подключился. Код функции автоматической авторизации, выполняющийся на сервере, представлен на рисунке 4.7.

Рисунок 4.7 Функция автоматической авторизации

Теперь, когда клиент пошлёт на сервер пакет с номером 7, он будет автоматически подключен и оповещён об этом, как если бы он это сделал это с помощью стандартной авторизации.

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

Рисунок 4.8 Код для имитации действий игрока

Теперь, когда наш персонаж автоматически управляется, останется только запустить соответствующее количество окон для нагрузочного тестирования. Пример подключения 20 клиентов представлен на рисунке 4.9. Все они бегают, но этого на картинке, конечно же, не видно .

Рисунок 4.9 Одновременное включение 20 игроков

4.3 Тестирование самовосстановления системы после внепланового отключения

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

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

Для сохранения информации о клиентах была разработана специальная функция updateClientPos (Рисунок 4.11). Когда на сервере происходит плановое отключение - например, нажатие крестика на окне приложения, сначала закрывается окно приложения, потом обновляются координаты клиентов, а затем уже происходит отключение от базы данных (Рисунок 4.10).

Рисунок 4.10 Код предусмотренного завершения сервер-приложения

Рисунок 4.11 Функция, обновляющая координаты клиента в базе данных

Проверить факт того, что информация о клиенте сохраняется при выключении сервер приложения можно только при его выключении и включении заново. Зайдём в игру тремя клиентами, и встанем в один ряд в определённых координатах (Рисунок 4.12). После чего отключимся всеми клиентами, и выключим сервер (Рисунок 4.13). После этого мы заново включаем сервер приложение, и подключаем одного клиента, которым специально отходили в угол для демонстрации сохранения координат в базе данных (Рисунок 4.14). Персонаж находится в тех самых координатах, в которых он был при отключении сервера, а значит, сервер обеспечивает целостность данных при плановом выключении.