Это было в то время. Со времени моего последнего поста я работал и создавал много вещей. Сейчас, когда наступил 2019 год, я хотел бы снова начать периодически писать о своей работе.
Одно большое изменение для меня - это то, что я основал новую компанию Cysharp вместе с Cygames (The Idolmaster Cinderella Girls: Starlight Stage, Shadowverse, Dragalia Lost и т. Д.). Cysharp специализируется на C #, Unity и .NET Core.
Сегодня я объявляю, что Cysharp выпустила интегрированную библиотеку с открытым исходным кодом для связи в реальном времени и API-интерфейса для Unity.
Https://github.com/Cysharp/MagicOnion
Впервые он был выпущен два года назад и использовался в мобильной игре, появившейся на рынке, и в этом официальном выпуске мы дополнительно улучшили его функции для общения в реальном времени.
Его основная функция - обеспечение потоковой передачи RPC между сервером и клиентом. И серверная, и клиентская стороны реализованы на C #, формат сообщения - MessagePack со сжатием LZ4, а обмен данными осуществляется по протоколу HTTP / 2 с использованием gRPC. Он также функционирует как сервер API, поэтому действует как обычная веб-платформа.
MagicOnion был разработан для обеспечения максимальной производительности и интерфейса, который кажется естественным разработчикам на C #.

MagicOnion предназначен для микросервисов (связь между серверами .NET Core, такими как Orleans, ServiceFabric, AMBROSIA), службы API (для WinForms / WPF, например WCF, ASP.NET Core MVC), API собственного клиента (для Xamarin, Unity) и сервера реального времени, который заменяет например Socket.io, SignalR, Photon, UNet и т. д.
Интерфейс, строго типизированный C #
Благодаря использованию общего интерфейса C # между сервером и клиентом, вызов методов как клиент-сервер, так и сервер-клиент строго типизирован. Например, предположим, что следующий интерфейс и класс будут общими.
Если и сервер, и клиент совместно используют их, безошибочная связь может быть установлена между ними, просто реализовав этот интерфейс на обеих сторонах.

Таким образом, нет необходимости генерировать код из промежуточного языка, и методы могут вызываться по сети, просто вызывая их (даже с несколькими входами или переменными примитивного типа) в манере, согласованной с синтаксисом C #. Конечно, он поддерживает автозаполнение.

Фактическая реализация описана ниже. Сервер реализует интерфейс, определенный как IGamingHub.
- все это делается асинхронно. (задачи, передающие возвращаемые значения, являются асинхронными.)
- значения могут быть возвращены. (Если исключение обнаружено, оно будет передано клиенту как таковое.)
- Группировка по группе позволяет отправлять клиентам в группе с помощью широковещательной рассылки (группы).
Клиент может принимать широковещательные данные с сервера, реализуя интерфейс, определенный как IGamingHubReceiver. Кроме того, сам IGamingHub действует как сетевой клиент, который автоматически реализуется на сервере.
Клиентская сторона может принимать широковещательные данные с сервера, реализуя интерфейс, определенный как IGamingHubReceiver. Кроме того, сам IGamingHub служит сетевым клиентом, автоматически реализуемым на сервере.
Поскольку все строго типизировано как переменные C #,
- Рефакторинг IDF отслеживается по изменениям имени метода и его входных данных как на стороне сервера, так и на стороне клиента.
- Неполная реализация приводит к ошибке компиляции, что позволяет обнаружить и исправить их.
- общение без строк повышает эффективность. (Имена методов автоматически преобразуются в идентификационные номера, поэтому строка не отправляется.)
- переменные примитивного типа могут быть отправлены естественным образом. (Нет необходимости заключать их в назначенный класс запроса.)
При использовании протокольных буферов вам необходимо управлять .proto (IDL: Interface Definition Language), беспокоиться о том, как их сгенерировать и т. Д., Но пока он написан на C #, ничего из этого не происходит.
Отображение нулевой десериализации
В RPC, особенно при обмене данными в реальном времени, включающем частую передачу данных, часто именно процесс сериализации, когда данные преобразуются перед отправкой, ограничивает производительность. В MagicOnion сериализация выполняется моим MessagePack for C #, который является самым быстрым двоичным сериализатором для C #, поэтому он не может быть ограничивающим фактором. Кроме того, помимо производительности, он также обеспечивает гибкость в отношении данных в том, что переменные любого типа могут быть отправлены, если они могут быть сериализованы с помощью MessagePack для C #.
Кроме того, пользуясь преимуществом того факта, что и клиент, и сервер работают на C # и данные, хранящиеся во внутренней памяти, должны иметь один и тот же макет, я добавил возможность выполнять сопоставление через копию памяти без сериализации / десериализации в случае значения -тип переменной.
Здесь ничего обрабатывать не нужно, поэтому он обещает наилучшую теоретически возможную производительность с точки зрения скорости передачи. Однако, поскольку эти переменные типа структуры необходимо скопировать, я рекомендую обрабатывать все как ref, как правило, когда вам нужно определить большой тип структуры, или это может замедлить процесс.
Я считаю, что это можно легко и эффективно применить для отправки большого количества преобразований, таких как массив переменных Vector3.
Почему двунаправленной потоковой передачи gRPC недостаточно
gRPC стандартно поставляется с двунаправленной потоковой передачей, которая реализует двунаправленную связь. Фактически, потоковый RPC MagicOnion построен на двунаправленной потоковой передаче.
// Bidirectional Streaming definition by proto rpc BidiHello(stream HelloRequest) returns (stream HelloResponse);
Однако использовать двунаправленную потоковую передачу в качестве RPC для связи в реальном времени сложно по многим причинам. Самая большая причина заключается в том, что, поскольку на данный момент это не RPC, после установления соединения запрос / ответ, определенный с помощью oneof (один тип, содержащий несколько типов), должен быть вручную разветвлен на метод, который необходимо вызвать. Это возможно, но есть еще много препятствий. Например,
- клиент не может дождаться завершения операции сервером (после отправки запроса выполняется следующая строка кода).
- невозможность дождаться ответа означает, что клиент не может получать возвращаемые значения или исключения.
- в настоящее время нет возможности связать несколько подключений.
Даже если вы создадите систему для решения этих проблем, вы никогда не сможете избежать использования шаблона двунаправленной потоковой передачи, созданного протоколом proto, поэтому он испортит код. Хотя MagicOnion StreamingHub использует двунаправленную потоковую передачу для установления соединения, он обменивается данными с помощью уникального облегченного протокола в рамках этого кадра связи, реализуя RPC для связи в реальном времени, что кажется естественным для разработчиков C #.
Почему я выбираю распределенную модель и gRPC
В отличие от других механизмов связи в реальном времени для Unity, MagicOnion не имеет собственного балансировщика нагрузки. Существует несколько стратегий реализации распределенной обработки, и я рекомендую использовать облачные платформы или другое промежуточное программное обеспечение. Например, при полностью независимом размещении во внутренней памяти один из способов - сделать так, чтобы внешняя служба обнаружения / сопоставления служб решила, какой сервер использовать.

Другой заключается в том, что полное распределение нагрузки с помощью балансировщика нагрузки TCP при делегировании процесса широковещательной рассылки группой в Redis позволяет отправлять данные клиентам, подключенным к разным серверам. Эта функция входит в стандартную комплектацию MagicOnion как MagicOnion.Redis. Это подходит для реализации функций чата, уведомлений и т. Д.

Кроме того, как и сам gRPC, MagicOnion подходит для реализации так называемых микросервисов, поэтому вы можете построить соединение сервер-сервер и построить структуру RPC-сервер-сервер.
Теперь MagicOnion построен на gRPC, но полностью игнорирует необходимость предоставления независимого от языка RPC с использованием .proto, что является его наиболее заметной характеристикой. Более того, тот факт, что сетевое взаимодействие ограничено протоколом HTTP / 2 (TCP), не обязательно делает его идеальным для создания игр. Однако есть веские причины, по которым я выбрал gRPC.
Одна из причин - зрелость библиотеки. Нет доступных для связи библиотек, поддерживающих реализацию сервер / клиент, включая Unity, а основная часть (gRPC C, которая используется на всех языках) используется почти всеми разработчиками, включая Google, что означает высокую стабильность. Возможно, удастся реализовать оригинальную коммуникационную библиотеку, состоящую из частей, специфичных для общения в играх, но обеспечение стабильности с нуля - непростая задача. Не изобретайте велосипед, правда?
Однако меня не устраивает привязка C # в gRPC с точки зрения производительности. Вот почему я думаю, что было бы неплохо продолжать использовать gRPC C Core, полностью заменяя привязку C #. По крайней мере, если это ограничивается стороной Unity (общение с клиентом), я считаю, что это осуществимо и эффективно.
Другая причина - экосистема. gRPC зарекомендовал себя как стандарт де-факто как современный RPC, поэтому он поддерживается многими серверами и промежуточным программным обеспечением. HTTP / 2 и gRPC являются отраслевыми стандартными протоколами, поэтому их использование дает множество преимуществ, таких как использование с Nginx или балансировка нагрузки на основе запросов с помощью Envoy. Кроме того, существует множество блогов и слайд-шоу с информацией о gRPC, что упрощает разработчикам создание более совершенной системы.
MagicOnion имеет встроенный уровень исходного приложения, но его инфраструктура - это gRPC, поэтому любой компонент промежуточного программного обеспечения или любые общие знания почти всегда можно применить напрямую.
Я считаю, что современный сервер должен иметь архитектуру, готовую к работе с облаком, и что система, полностью использующая инфраструктуру и промежуточное ПО, предоставленное поставщиком облачных услуг, имеет больше шансов на хорошую производительность, чем система, которая пытается делать все сама. Следовательно, структура, которая имеет дело с инфраструктурой, должна быть легкой и состоять только из основных функций.
Поддержка связи через API
Цель MagicOnion - стать единым сетевым движком. Под «унифицированным» я подразумеваю не то, что и сервер, и клиент используют C #, а то, что система связи в реальном времени и система связи API едины. Система связи API использует тот же интерфейс и предназначена для автоматической генерации клиентского кода, если метод определен с использованием синтаксиса C #.
Кроме того, благодаря обмену данными через API все, что связано с фреймворком, делается асинхронным и неблокирующим. Это выглядит почти естественно благодаря функции async / await, предоставляемой самим языком C #. Он также поставляется с функцией фильтрации, которая перехватывает выполнение до и после запроса, что также способствует естественной асинхронной обработке.
Фильтр также можно использовать с StreamingHub таким же образом.
Чванство
Трудно проверить, правильно ли работают API, и непросто постоянно отлаживать из Unity, а gRPC нельзя отлаживать с помощью такого инструмента, как Postman. Поэтому я спроектировал MagicOnion так, чтобы он автоматически генерировал документы API, которые может выполнять Swagger. Поскольку MagicOnion действует как хостинг-сервер HTTP / 1, нет необходимости настраивать внешний прокси-сервер, и все, что вам нужно сделать, это добавить несколько строк кода в часть, которая обрабатывает запуск.

Это все, что вам нужно сделать, чтобы иметь возможность проверить, работают ли API-интерфейсы должным образом, и, просто определив команды отладки как API-интерфейсы, они отображаются в Swagger, поэтому можно легко подготовить команды, управляющие базой данных, для отладки.
StreamingHub в настоящее время не поддерживает его, но я планирую создать WebSocketGateway, который соединит WebSocket и MagicOnion.
Развертывание и хостинг
В прошлом самая большая проблема на стороне сервера C # заключалась в том, как развернуть и как разместить. В конце концов, он работал на Windows Server. Тот факт, что gRPC не основан на IIS, еще больше усложнил задачу. Однако теперь это легко. Если вы создаете контейнер с помощью Docker, в работе с C # нет ничего особенного. Нет ничего сложного в том, чтобы превратить приложение MagicOnion, созданное .NET Core, в контейнер. На самом деле это довольно просто (поскольку это просто консольное приложение .NET Core). Как только это будет сделано, все, что вам нужно сделать, это развернуть его внутри контейнера Linux. Неважно где, будь то ECS, Fargate, GKE или AKS. Об этом есть много онлайн-статей, и вы можете напрямую применить их практику.
Когда дело доходит до C #, создание контейнера - это не совсем то, что нужно для создания локальной среды. Он предназначен для того, чтобы легко переносить вещи в среды разработки / развертывания и позволять людям, не особенно знакомым с C # / Windows, опираться на обширные знания об инфраструктуре, не изучая ничего особенного. Я считаю, что это самое большое преимущество.
Заключение
Вы можете начать использовать MagicOnion только для общения в реальном времени, а также можете использовать его для связи через API, что будет очень хорошо работать. Поскольку он поддерживает быструю передачу данных и сериализацию данных, разработанную с учетом сжатия, он избавит вас от всех ваших проблем, связанных с коммуникацией. Кроме того, поскольку async / await используется в Unity, он может служить шлюзом для включения последней версии C #.
В качестве среды связи в реальном времени он предоставляет только клиент-серверный RPC. Однако это единственное, что вам нужно, и вы можете сами создать все остальные функции. (Это зависит от обстоятельств, но, вообще говоря, для этого не потребуется много работы.) Я считаю, что без всех ненужных функций он обеспечивает лучший опыт кодирования, когда дело касается RPC. (Хотел бы я также сказать, что это лучшая производительность, но есть несколько вещей, которые можно улучшить в том, как он обрабатывает привязку gRPC C #, поэтому я надеюсь, что смогу сказать это, когда я выпущу следующую версию.)
Кроме того, поскольку это независимая закрытая система, вы можете, например, использовать ее для демонстрации контента VR / AR, просто поддерживая работу сервера в той же сети LAN, даже если сеть LAN имеет ограничения ……!
Надеюсь, вы попробуете.
Я надеюсь, что смогу продолжить писать в этом блоге о MagicOnion, а также о том, как обстоят дела с UniRx, UniRx.Async, MessagePack для C # и т. Д.