Подробнее о сетевом протоколе передачи данных в Unity3D

Примечание от автора:
Вольный перевод статьи с блога

О том, что такое UNET можно прочесть здесь. В этой статье рассмотрим протокол передачи данных в UNET.

Когда мы начали проектировать новую сетевую библиотеку для Unity, мы хотели понять, какой она должна быть в идеале. Мы разделили наших пользователей на две группы:

1. Пользователи, которые хотят получить инструмент для настройки сети, требующий минимум усилий и затрат для настройки (в идеале, без усилий вообще).

2. Пользователи, которые работают с сетевыми играми и желающие иметь мощный и гибкий инструмент.

Основываясь на этих группах, мы разделили нашу сетевую библиотеку на две различные части: HLAPI (high-level(высокоуровневый) API) и LLAPI (low-level(низкоуровневый) API).

Наша библиотека основана на следующих принципах:

Производительность, производительность, производительность...

LLAPI является тонким слоем над UDP сокетами, большая часть работы которых выполняется в отдельном потоке (хотя можно настроить выполнение работы исключительно в главном потоке). Не используется никаких динамических выделений памяти, тяжелых синхронизаций.

Если что-то можно сделать при помощи C#, то оно сделается

(примечание: автор имел ввиду, что всю обвязку можно допилить используя C# и LLAPI)

Мы решили предоставить только то, в чем будут нуждаться пользователи. Подобно BSD сокетам, LLAPI осуществляет только одну задачу — обмен необработанными бинарными сообщениями (прим. raw binary message). Никаких tcp-подобных потоков, сериализации или RPC в LLAPI; только низкоуровневые сообщения.

Гибкость и настраиваемость? Пожалуйста!

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

Просто и красиво

Мы пытались сделать LLAPI подобно API BSD-сокетов насколько это возможно.

Сетевой и транспортный уровни

Низкоуровневая библиотека UNET является стэком сетевого протокола поверх UDP, содержащим «сетевой» слой и «транспортный». Сетевой слой используется для создания соединения между пирами, доставки пакетов и обработки различных состояний соединения и доставки. Транспортный слой работает с сообщениями, доставляемых через разные каналы:



Каналы выполняют две цели: логически отделяют сообщения друг от друга и обеспечивают различные способы доставки.

Процесс настройки каналов является частью общей настройки сети, и более подробно об этом напишем в следующих статьях. Пока, давайте рассмотрим часть настроек, к примеру «Моя система будет содержать 10 соединений, каждое соединение будет иметь 5 каналов, из которых 0 канал имеет этот тип, 1 — другой и т.д.». Последную часть предложения можно описать так:



Второй параметр — номер канала, последний — тип канала(от него зависит способ доставки сообщений).

UNET поддерживает следующие способы доставки (прим. в оригинале используется термин Quality of Service, QOS):

-Unreliable(Негарантированный): негарантированное сообщение может быть потеряно по разным причинам, связанных с UDP.
Пример использования: короткие лог сообщения

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

-UnreliableSequenced(НегарантированныйУпорядоченный): канал гарантирует порядок сообщений, хотя сообщения могут быть потеряны.
Пример использования: голос, видео

-Reliable(Гарантированный): канал гарантирует доставку сообщения(или отключение), но не гарантирует порядок.
Пример использования: наносимый урон

-ReliableFragmented(ГарантированныйФрагментный): такой же, как и UnreliableFragmented, но гарантирует доставку.
Пример использования: групповой урон

-ReliableSequenced: такой же, как и UnreliableSequenced, но гарантирует доставку сообщения(аналогично TCP потоку).
Пример использования: передача файлов/патчинг

-StateUpdate(ОбновлениеСостояния): то же, что и unreliable, только откидывает старые сообщения при отправке/получении. Если отправляются сообщения из канала, то только последнее будет отправлено, остальные отбросятся. При получении сообщений из канала, только последнее будет прочитано, остальные отбросятся.
Пример использования: отправка позиции

-AllCostDelivery(ДоставкаЛюбойЦеной): очень похож на Reliable способ, однако есть отличие. reliable канал перешлет недоставленное сообщение основываясь на значении RTT, которое является динамический, в то время как AllCostDelivery автоматически перешлет сообщение после определенного промежутка времени, который можно настроить. Это может быть полезно для коротких важных сообщений.
Пример использования: игровое событие выстрела

Если у вас есть специфические случаи которых нет выше, мы будем рады рассмотреть их, пишите в комментах!

Давайте рассмотрим стандартные функции LLAPI:

1. Инициализация библиотеки:



2. Настройка сети: топология, каналы, размеры буферов и другое (мы обсудим это в следующих статьях)

3. Создание сокета:



Эта функция откроет сокет, который будет слушать на всех интерфейсах на 5000 порту. Функция вернет int-значение указателя на этот сокет.

4. Соединяемся с другим пиром:



Функция отправит запрос другому пиру по адресу 127.0.0.1/6000. Она вернет int-значение как указатель на соединения для текущего хоста. Вы получите событие в случае успешного соединения или отказа.

5. Отправка сообщения:



Последняя функция отправит данные в буфере через сокет, указатель которого равен hostId для соединения connectionId используя канал #1 (в нашем примере это гарантированный, поэтому доставка сообщения гарантирована).

6. Получение сетевых событий:

Для получения сетевых событий мы используем пул. Пользователь должен вызывать метод UTransport.Receive() для получения событий. Этот метод возвращает 4 события:

UNETEventType.kConnectEvent — кто-то хочет присоединиться к вам, или соединение после запроса с помощью UTransport.Connect() было успешно установлено

UNETEventType.kDisconnectEvent — кто-то отсоединился от вас, или запрос на соединение был отклонен по какой-то причине (код ошибки должен сообщить в чем дело)

UNETEventType.kDatatEvent – есть новые данные

UNETEventType.kNothing — ничего нет

7. Отправка запроса на отключение:



Метод отправит запрос на отключение. Соединение закроется сразу после вызова метода, и может быть использовано в будущем.

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

0 комментариев

Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.