Skip to content

Network

Bronuh edited this page Feb 27, 2024 · 3 revisions

Синглтон Network предоставляет пользователям возможность отправлять и принимать пакеты по сети.

Подоготовка. PacketRegistry

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

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

PacketRegistry.ScanPackets();

Начало работы

После сканирования и регистрации типов пакетов можно переходить к сети.

Для начала работы класс Network предоставляет 2 метода:

  • CreateServer(int port) - создает локальный сервер и слушает входящие подключения на указанном порту. Помимо сервера будет инициализирован локальный клиент.
  • ConnectToRemoteServer(string host, int port) - подключается к серверу, инициализируя локальный клиент и удаленный сервер.

Синхронизация PacketRegistry

Первое, что необходимо сделать после подключения к серверу - синхронизировать PacketRegistry клиента и сервера, так как ID на обеих сторонах могут различаться.

Это делается автоматически при подключении, поэтому первым отправленным по сети пакетом всегда будет RegistrySynchronizationPacket, который ВСЕГДА должен иметь ID 0 в PacketRegistry. После того, как типы пакетов будут синхронизированы, на стороне клиента, будет вызвано статическое событие SynchronizationCompleted в классе PacketRegistry, сигнализирующее о том, что теперь можно слать пакеты на сервер.

Note

Событие SynchronizationCompleted не имеет непосредственного отношения к шине событий. Это обычный делегат, объявленный прямо в классе PacketRegistry.

Note

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

MultiplayerApi Godot

Чтобы получить доступ к сетевому функционалу Godot, используемому классом Network, можно обратиться к свойствам Network.Api и Network.Peer

Свойства Network

Класс Network имеет несколько свойств, позволяющих определять его текущее состояние.

  • Network.IsClient - будет равен true, если инициализирован локальный клиент
  • Network.IsServer - будет равен true, если инициализирован локальный сервер
  • Network.MyPeerId - вернет ID пира, используемый MultiplayerApi. Если был запущен сервер, то MyPeerId ВСЕГДА будет равен 1, иначе - случайному числу.
  • Network.MyPlayerId - вернет ID игрока. Если был запущен сервер, то MyPlayerId ВСЕГДА будет равен 0, иначе - MyPeerId.

При использовании CreateServer(int port) оба свойства станут true.

При использовании ConnectToRemoteServer(string host, int port) IsClient будет true, а IsServer - false

Network.MyPlayerId особенно важен при работе с пакетами на стороне локального клиента, так как отправка пакетов локальному клиенту происходит прямым вызовом метода вместо RPC, в результате чего Api.GetRemoteSenderId() вернет 0 (что значит, что метод был вызван не по RPC).

Отправка пакетов

Для отправки пакетов предоставляется 3 основных метода:

  • SendPacketToClients(AbstractPacket packet, bool reliable) - Отправит указанный пакет всем подключенным клиентам. Может быть вызван ТОЛЬКО если Network.IsServer == true.
  • SendPacketToServer(AbstractPacket packet, bool reliable) - Отправит указанный пакет на сервер. Может быть вызван ТОЛЬКО если Network.IsClient == true.
  • SendPacketToPeer(long id, AbstractPacket packet, bool reliable) - Отправит указанный пакет клиенту с указанным id. Может быть вызван ТОЛЬКО если Network.IsServer == true.

Во всех трех методах имеется параметр reliable. Он отвечает за гарантию доставки пакета клиенту.

  • true - пакет будет гарантированно доставлен.
  • false - никаких гарантий.

Буферизация и пакетная отправка пакетов. PacketBuffer

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

Тип PacketBuffer может записывать очередь пакетов, которые затем будут отправлены все разом в удобное время (напрример, пакеты перемещения персонажей. Они отправляются несколько раз в секунду и могут быть потеряны по пути. Идеально чтоб накопить их и отправить все вместе).

Для отправки буферизованых пакетов можно сипользовать 2 метода:

  • SendPacketsToServer(PacketBuffer buffer) - отправит все пакеты из буфера на сервер. Под капотом вызывает SendPacketToServer, так что ограничения SendPacketToServer здесь так же актуальны.
  • SendPacketsToClients(PacketBuffer buffer) - отправит все пакеты из буфера клиентам. Под капотом вызывает SendPackSendPacketToClientstToServer, так что ограничения SendPacketToClients здесь так же актуальны.

Получение пакетов

Класс Network имеет 2 события, вызываемые при получении пакета.

  • Network.ReceivedRawPacket - содержит в себе ID отправителя и не десериализованный текст пакета.
  • Network.ReceivedPacket - содержит в себе десериализованный пакет.

Note

Десериализованные пакеты имеют свойство Sender, содержащее информацию об отправителе.

Note

По умолчанию Network не имеет прямой связи с шиной событий, но так как все пакеты по умолчанию являются подтипами AbstractPacket, который в свою очередь является собтием, ничто не мешает вам сделать так:

  Network.ReceivedPacket += (packet) => EventBus.Publish(packet);

где-нибудь в процессе инициализации игры.

После этого можно можно обрабатывать пакеты как события (см. EventBus)

Создание пакетов

Пакетами будут считаться классы, которые:

  • являются подтипами AbstractPacket
  • имеют конструктор по умолчанию
  • помечены атрибутом [GamePacket]
    • последнее не касается пакета RegistrySynchronizationPacket

Note

При выборе имени пакета рекомендуется добавлять в конце слово Packet

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

  • bool IsBufferable { get; } - если true, то пакет можно (рекомендуется) буферизовать перед отправкой, иначе рекомендуется отправлять немедленно.
  • bool IsReliable { get; } - если true, то пакет рекомендуется гарантированно доставлять получателю.

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

Стандартные настройки

В статическом классе KludgeBox.Net.DefaultNetworkSettings определены настройки по умолчанию, которые на самом деле нигде не используются (пока что), но вот вам его содержимое:

public static class DefaultNetworkSettings
{
    public const int DefaultMaxPlayers = 4;
    public const int ServerPeerId = 1;
    public const int LocalPeerId = 0;
    
    public const string Host = "localhost";
    public const int Port = 25564;
}