Top.Mail.Ru
artace.ru - творческий сектор Портал Обучение Java для L2J: Основы серверной разработки

Глава 1: Обучение L2JMobius-исходный код - сетевая передача данных

2024-8-31 01:38

В этой главе рассказывается о сетевой передаче данных между клиентом и сервером. Используемая технология - Jav ...

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

Пожалуйста, обратите внимание, что в отличие от продвинутых протоколов, таких как HTTP, формат данных для связи с сокетом часто является байтовым.Когда мы получаем байтовые данные, отправленные клиентом, нам необходимо преобразовать эти байтовые данные в данные соответствующего типа данных.Например, если реальные данные имеют тип int, нам нужно преобразовать 4 байта данных в данные типа int.Аналогично, данные, отправляемые сервером клиенту, также равномерно преобразуются в байтовые данные.

Итак, как эта куча байтовых данных может быть преобразована в реальные данные?Как мы узнаем, какие байты данных необходимо преобразовать в какие типы данных?Это требует от нас “согласования формата” данных, передаваемых двумя сторонами.Например, когда мы получаем фрагмент данных, мы “фиксированно считываем” первые два байта и преобразуем их в числовые данные короткого типа. Числовые данные представляют длину текущего пакета данных. Далее нам нужно получить последующие данные на основе этой длины.

После получения полного пакета данных мы продолжаем считывать числовые данные типа int, которые представляют “класс бизнес-модели”.Далее мы можем преобразовать данные в пакете данных в соответствии с атрибутами (переменными), определенными в “Классе бизнес-модели”.Порядок следования этих атрибутов класса (переменных) имеет однозначную связь с байтовыми данными в пакете данных.Например, в настоящее время в “Классе бизнес-модели” есть переменная a типа int и переменная b типа short. Затем мы поговорим о первых 4 байтах, преобразованных в тип int, присвоенный переменной a, и следующих 2 байтах, преобразованных в тип short, присвоенный переменной B.Для строкового типа вы также должны указать его длину, а затем преобразовать байтовые данные этой длины в данные строкового типа в целом.

После успешного получения "класса бизнес-модели“ мы можем перейти к следующему шагу в соответствии с ”бизнес-логикой игры".Например, если этот “класс бизнес-модели” является запросом на вход в систему, то он содержит номер учетной записи и данные пароля.Затем нашим следующим шагом должно стать подтверждение правильности номера учетной записи и пароля.Если это неверно, клиенту должен быть отправлен пакет сбоя; если это правильно, клиенту должен быть отправлен пакет успеха.Конечно, для пакетов данных, возвращаемых клиенту здесь, вам нужно поместить данные различных типов данных в массив байтов один за другим по порядку и, наконец, отправить их клиенту через сокет.Это простой процесс взаимодействия между сервером и клиентом.

Здесь следует отметить, что поскольку передача байтовых данных при сокетной связи не является “упорядоченной”, они не будут отправляться один за другим, но будет отправлен один или несколько, или даже половина пакетов данных.Следовательно, когда мы получаем байтовые данные, мы должны прочитать полный пакет данных в соответствии с “соглашением о формате”.Если это не полный пакет данных, нам нужно дождаться, пока последующие данные будут прочитаны и объединены в полный пакет.

Далее мы возвращаемся к проекту ”L2J_Mobius".
Давайте сосредоточимся на нескольких каталогах.
config - это каталог файлов конфигурации. В нем много файлов конфигурации, включая конфигурацию подключения к данным (мы изменили ее ранее).
Data - это каталог игровых данных, который содержит множество игровых данных, таких как диалоги NPC и так далее.
libs - это файл драйвера связи с базой данных, который мы также представляли ранее.
Log - это каталог журналов. после запуска службы сюда записывается множество журналов.
src - это каталог исходного кода, на котором мы хотим сосредоточиться.
Далее мы перейдем в каталог src/org/l2jmobius


Commons — это общий пакет, который предоставляет некоторые классы с уже реализованными специфическими функциями для использования другими модулями.
Gameserver — пакет игрового сервера. Запуск GameServer.java внутри этого пакета позволяет обрабатывать пакеты данных, поступающие от клиента.
Log — пакет для ведения логов, который отвечает за выполнение функций записи журналов.
Loginserver — пакет службы авторизации. Запуск LoginServer.java внутри этого пакета позволяет обрабатывать операции авторизации клиента.
Tools — пакет инструментов, содержащий функции управления игровыми аккаунтами и инициализации базы данных, который нам пока не нужен.
Config.java — класс конфигурации, соответствующий каталогу конфигурационных файлов, который мы описали выше.

Теперь давайте кратко разберём разницу между gameserver и loginserver. Loginserver используется для обработки авторизации игрока. После того как игрок выбрал игровой регион, сервер возвращает IP-адрес данного региона, и игрок может войти в игровой мир этого региона. Это соответствует gameserver. Очевидно, что loginserver существует только один, тогда как игровых регионов может быть много, и каждый из них соответствует своему gameserver. То есть, между ними существует отношение "один ко многим". Конечно, для локального тестирования нам нужен только один loginserver и один gameserver, и они могут находиться на одном компьютере. В реальном развертывании игры loginserver и gameserver будут находиться на отдельных серверах, каждый из которых будет иметь свой уникальный IP-адрес. Конечно, это не относится к содержанию данной главы.

В этой гаве рассматривается часть, касающаяся сетевой передачи данных, код которой находится в каталоге commons\network.

  • ReadablePacket.java: родительский класс пакетов данных, которые отправляются клиентом на сервер.
  • WritablePacket.java: родительский класс пакетов данных, которые отправляются сервером клиенту.
  • ReadThread.java: поток для чтения, используется для чтения пакетов данных, отправленных клиентом.
  • ExecuteThread.java: поток выполнения, в основном используется для расшифровки пакетов данных и обработки игровой логики.
  • EncryptionInterface.java: интерфейс для шифрования и расшифровки, который должен быть реализован подклассами.
  • PacketHandlerInterface.java: интерфейс для обработки игровой логики пакетов данных, который должен быть реализован подклассами.
  • NetConfig.java: параметры конфигурации для передачи сетевых данных, такие как настройка размера пула потоков.
  • NetClient.java: родительский класс клиента, содержит объект канала сокетов (SocketChannel).
  • NetServer.java: класс сервера, представляет собой класс ServerSocketChannel.

  • Прежде всего, давайте рассмотрим два родительских класса пакетов данных: ReadablePacket.java и WritablePacket.java. Они выполняют только базовые функции обработки данных и не содержат бизнес-данных, связанных с игрой. В каждом из этих классов присутствует массив байтов (byte), который используется для передачи данных между клиентом и сервером на уровне байтов.

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

    Далее, подробно рассмотрим поток чтения — ReadThread.java. Внутри этого потока имеется коллекция типа Set, в которой хранятся объекты клиента NetClient. В классе NetClient.java есть три важных переменных атрибута.

    // Полная очередь пакетов данных, нуждающаяся в последующей расшифровке

    private Queue<byte[]> _pendingPacketData;

    // Неполный пакет данных, требующий дальнейшего чтения оставшихся данных от клиента

    private ByteBuffer _pendingByteBuffer;

    // Длина неполного пакета данных, используется для чтения оставшихся данных

    private int _pendingPacketSize;

    Поняв эти три свойства, мы сможем легко понять работу потока чтения ReadThread.java. Во-первых, мы должны циклически проходить через коллекцию типа Set и получать каждый объект клиента NetClient, затем получать соответствующий объект канала SocketChannel. После этого мы можем использовать метод read для чтения данных, отправленных клиентом. Здесь возможны два сценария: первый — это ситуация с "неполным пакетом" (или "половинным пакетом"), а второй — ситуация с "полным пакетом".

    Если мы имеем дело с "неполным пакетом", нам нужно поместить этот неполный пакет данных в pendingByteBuffer внутри NetClient, а также установить полную длину этого пакета в переменной pendingPacketSize. Таким образом, при следующем чтении данных, отправленных клиентом, необходимо учитывать, есть ли данные в pendingByteBuffer. Если данные существуют, то сначала нужно получить данные из pendingByteBuffer, а затем получить оставшиеся данные в соответствии с pendingPacketSize. Это можно сделать, вычитая длину pendingByteBuffer из pendingPacketSize.

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

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

    Если его можно прочитать, это полный пакет данных, и мы можем поместить его в очередь pendingPacketData в NetClient. Если фактические прочитанные данные неполны, то есть имеет место ситуация “неполного пакета”, мы можем поместить прочитанные данные только в pendingByteBuffer в NetClient, и мы также должны установить полную длину пакета данных pendingPacketSize .

    Таким образом, все возвращается к тому, с чего только началось. Что мы должны помнить, так это то, что считывания полного пакета данных достаточно только для того, чтобы его поместили в очередь pendingPacketData в NetClient.

    Далее мы представляем поток выполнения ExecuteThread . У него также есть коллекция Set, в которой также хранятся клиентские объекты NETCLENT. В то же время, в потоке также есть подкласс PacketHandlerInterface, который используется для обработки игровой логики для пакетов данных. Однако, прежде чем выполнять логическую обработку игры, пакет данных все равно необходимо расшифровать. Для этого требуется реализация подкласса EncryptionInterface . Давайте вернемся к потоку ExecuteThread. Первый - это перебирать заданную коллекцию, а затем получать каждый клиентский объект NETCLENT. Затем получите полный пакет данных, расшифруйте его и, наконец, передайте его подклассам PacketHandlerInterface для обработки.

    Наконец, давайте представим серверный класс NetServer, который содержит объект ServerSocketChannel и может прослушивать указанный порт.В этом классе есть два важных объекта List, как показано ниже

    Вы можете определить, взглянув на название. один из них предназначен для чтения списка клиентов, а другой - для выполнения списка клиентов.То, что хранится в обоих списках, является установленной коллекцией.То, что помещено в эту коллекцию Set, является объектом клиента NETCLENT.И каждая коллекция Set будет соответствовать потоку чтения ReadThread или потоку выполнения ExecuteThread.Мы можем понять это следующим образом. Есть два списка, в которых хранится множество потоков чтения ReadThread или потоков выполнения ExecuteThread. Каждому потоку соответствует установленная коллекция. Эта установленная коллекция содержит определенное количество клиентских объектов NETCLENT.Почему он спроектирован именно так?На самом деле, это очень легко понять.Нам определенно нужно использовать многопоточность для обработки клиентских запросов.Следовательно, нам нужно создать множество экземпляров потоков. Эти потоки можно разделить на два типа: потоки чтения и потоки выполнения.Эти потоки должны быть помещены в список, или также возможно использовать пул потоков.Каждый поток не может обрабатывать только один клиентский объект NETCLENT, что было бы слишком расточительно для ресурсов на стороне сервера, поэтому каждый поток будет обрабатывать определенное количество клиентских объектов NETCLENT.Эти клиентские объекты NETLIENT необходимо поместить в коллекцию Set.Это облегчает понимание.

    Основной код серверного класса NetServer используется для получения новой клиентской ссылки и последующего создания экземпляра клиентского объекта NETCLENT.Затем поместите клиентский объект NETLIENT в коллекцию Set.Если коллекции Set нет, создается экземпляр новой коллекции Set, и одновременно создается экземпляр потока чтения или выполнения, и коллекция Set передается этому потоку.Наконец, просто внесите нашу коллекцию наборов в список.Это код для серверного класса NetServer.

    Давайте резюмируем, что код для сетевой передачи данных между клиентом и сервером находится в каталоге commons\network и представляет собой инкапсулированный общедоступный модуль.И наш сервер входа в систему, и игровой сервер должны полагаться на него для обеспечения передачи данных.Способ использования сетевого пакета заключается в наследовании родительского класса внутри.Например, класс пакета данных, который считывает клиент, наследует ReadablePacket.java; и пакеты данных, отправляемые клиенту, наследуют WritablePacket.java; Шифрование и дешифрование данных должно наследовать EncryptionInterface.java; для обработки пакетов игровых данных наследуйте PacketHandlerInterface.java; клиентская страница наследует NETCLENT.java; серверная часть также наследует NetServer.java (вы также можете использовать этот класс напрямую).Здесь мы не рассказываем, как отправлять пакеты данных клиенту. Это очень просто. Вам нужно только вызвать метод записи объекта канала SocketChannel клиентского объекта NETCLENT.Его выполнение фактически выполняется потоком после создания экземпляра пакета игровых данных.Процесс создания экземпляра заключается в преобразовании байтовых данных в переменные атрибута класса.Мы подробно познакомимся с этой частью в последующих главах.

    Продолжение следует


    Ужасно

    Печально

    Восхитительно

    Насмешили

    Оцтой
    В статье уже есть0 Участие в комментариях

    Пожалуйста, оставьте комментарий

    Все комментарии

    Понравилось Прочитано403 Коментариев0
    Предыдущий:
    Следуюий:
    Шаг 1: Введение в классы и методы на примере L2j_MobiusДата публикации:2024-10-15
    Сектор творческих людей
    Горячая линия

    638638758

    С понедельника по воскресенье с 9:00 до 23:00

    Обратной связь

    admin@artace.ru Онлайн

    QR-код

    Powered by Discuz! X3.5© 2001-2021 Comsenz Inc.