Это Простой и простой в использовании Сетевая библиотека.
Это универсальное решение для сетевых модулей. Разработано для предоставления унифицированного интерфейса HighLevel для модулей прикладных сети.
Вся библиотека классов разделена на несколько DLL. Проще говоря: netRemotestandard.dll - это стандарт, только с определениями интерфейса; Megumin.remote.dll - это реализация. Аналогирует взаимосвязь между DotnetStandard и DotnetCore.
Зачем разделить на несколько DLL?
Конкретная реализация может потребовать зависимости от многих других DLL, и определение интерфейса не требует этих зависимостей. Для пользователей, которые хотят использовать интерфейсы и пользовательские реализации, внедрение дополнительных зависимостей не нужно. Например, Messagestandard, пользователи могут ссылаться только на выбранную ими библиотеку сериализации, без необходимости ссылаться на множественные библиотеки сериализации.

Да, используйте Nuget, чтобы получить Megumin.remote. Тем не менее, обратите внимание, что вам необходимо соответствовать библиотеке сериализации, и различные библиотеки сериализации могут иметь дополнительные требования. Из -за использования синтаксиса C# 7.3 требуется не менее 2018.3, если исходный код используется в Unity.
Целевой фреймворк NetStandard2.1 рекомендуется быть в Unity Version 2021.2 или выше. Исходный код может использоваться для слишком маленьких версий, но зависимости должны быть разрешены сами.

или добавить "com.megumin.net": "https://github.com/KumoKyaku/Megumin.Net.git?path=UnityPackage/Packages/Net" в Packages/manifest.json .
Если вы хотите установить целевую версию, используйте тег
*.*.*Выпустите, чтобы вы могли указать версию, такую как#2.1.0. Например,https://github.com/KumoKyaku/Megumin.Net.git?path=UnityPackage/Packages/Net#2.1.0.
Span<T> . Используйте System.IO.Pipelines в качестве высокопроизводительного буфера ввода-вывода.MIT许可证 Принцип дизайна: наиболее часто используемый код является наиболее упрощенным, и все сложные части инкапсулируются.发送一个消息,并等待一个消息返回- это весь содержимое библиотеки классов.
Имеет смысл вернуть исключение из значения результата:
///实际使用中的例子
IRemote remote = new TCPRemote ( ) ; ///省略连接代码……
public async void TestSend ( )
{
Login login = new Login ( ) { Account = "LiLei" , Password = "HanMeiMei" } ;
/// 泛型类型为期待返回的类型
var ( result , exception ) = await remote . SendAsync < LoginResult > ( login ) ;
///如果没有遇到异常,那么我们可以得到远端发回的返回值
if ( exception == null )
{
Console . WriteLine ( result . IsSuccess ) ;
}
} Подпись метода:
ValueTask < Result > SendAsyncSafeAwait < Result > ( object message , object options = null , Action < Exception > onException = null ) ; Значение результата гарантированно будет иметь значение. Если значение результата является пустым или другими исключениями, функция обратного вызовов исключения не будет брошена, поэтому нет необходимости пытаться вылов.异步方法的后续部分不会触发, поэтому последующая часть может быть исключена из пустых проверок.
(注意:这不是语言特性,也不是异步编程特性,这依赖于具体Remote的实现,这是类库的特性。如果你使用了这个接口的其他实现,要确认实现遵守了这个约定。 )
IRemote remote = new TCPRemote ( ) ; ///省略连接代码……
public async void TestSend ( )
{
Login login = new Login ( ) { Account = "LiLei" , Password = "HanMeiMei" } ;
/// 泛型类型为期待返回的类型
LoginResult result = await remote . SendAsyncSafeAwait < LoginResult > ( login , ( ex ) => { } ) ;
///后续代码 不用任何判断,也不用担心异常。
Console . WriteLine ( result . IsSuccess ) ;
}Хотя не рекомендуется, чтобы один запрос соответствовал нескольким типам ответов, некоторые бизнес -проекты по -прежнему имеют это требование. Например, если на все коды ошибок отвечаются как независимый тип, запрос может иметь два типа ответов: соответствующие ответы и код ошибки.
IMessage接口может использоваться в качестве типа, чтобы ждать возврата в протоколе Protobuf.
class ErrorCode { }
class Resp { }
class Req { }
async void Test ( IRemote remote ) {
Req req = new Req ( ) ;
///泛型中填写所有期待返回类型的基类,然后根据类型分别处理。
///如果泛型处仅使用一种类型,那么服务器回复另一种类型时,底层会转换为 InvalidCastException 进如异常处理逻辑。
var ret = await remote . SendAsyncSafeAwait < object > ( req ) ;
if ( ret is ErrorCode ec )
{
}
else if ( ret is Resp resp )
{
}
} ValueTask<object> OnReceive(short cmd, int messageID, object message);Функция обратного вызова получателя
protected virtual async ValueTask < object > OnReceive ( short cmd , int messageID , object message )
{
switch ( message )
{
case TestPacket1 packet1 :
Console . WriteLine ( $ "接收消息{ nameof ( TestPacket1 ) } -- { packet1 . Value } " ) ;
return null ;
case Login login :
Console . WriteLine ( $ "接收消息{ nameof ( Login ) } -- { login . Account } " ) ;
return new LoginResult { IsSuccess = true } ;
case TestPacket2 packet2 :
return new TestPacket1 { Value = packet2 . Value } ;
default :
break ;
}
return null ;
} Для конкретных методов ответа см. В исходном коде функции PreReceive , см.
Heartbeat, RTT, синхронизация TimeStamp и другие функции реализованы этим механизмом.
PreReceive и вызовы GetResponse для возврата результата отправителю. public interface IAutoResponseable : IPreReceiveable
{
ValueTask < object > GetResponse ( object request ) ;
} Планирование потоков
Remote использует функцию bool UseThreadSchedule(int rpcID, short cmd, int messageID, object message) , чтобы определить, какой поток выполняется функция обратного вызова сообщения. Когда это правда, все сообщения суммируются в megumin.threadscheduler.update.
Вам необходимо опросить эту функцию для обработки полученного обратного вызова, который гарантирует, что обратный вызов запускается в порядке полученных сообщений (если вне заказа, отправьте ошибку). FixedUpdate обычно следует использовать в Unity.如果你的消息在分布式服务器之间传递,你可能希望消息在中转进程中尽快传递,那么, когда ложно, полученный обратный вызов выполняется с использованием задачи, и вам не нужно ждать в опросе, но нельзя гарантировать, что он не может быть упорядоченным и не может иметь как рыбу, так и лапы.
///建立主线程 或指定的任何线程 轮询。(确保在unity中使用主线程轮询)
///ThreadScheduler保证网络底层的各种回调函数切换到主线程执行以保证执行顺序。
ThreadPool . QueueUserWorkItem ( ( A ) =>
{
while ( true )
{
ThreadScheduler . Update ( 0 ) ;
Thread . Yield ( ) ;
}
} ) ; Message.dll
(AOT/IL2CPP) Когда сериализованный класс импортируется в Unity в форме DLL (поскольку библиотека классов сообщений иногда разработана как общий проект за пределами Unity), необходимо добавлять файл ссылки, чтобы предотвратить получение и установить методы сериализованных атрибутов класса, которые были вырезаны IL2CPP.重中之重,因为缺失get,set函数不会报错,错误通常会被定位到序列化库的多个不同位置(我在这里花费了16个小时)。
<linker>
<assembly fullname="Message" preserve="all"/>
</linker>
| Totallength (значение, включая общую длину 4 байта) | RPCID | CMD | MSGID | Тело |
|---|---|---|---|---|
| Общая длина (значение содержит 4 байта самой общей длины) | Идентификатор сообщения | Текст сообщения | ||
| Int32 (int) | Int32 (int) | Int16 (короткий) | Int32 (int) | байт[] |
| 4byte | 4byte | 2byte | 4byte | байт []. Ленгт |
当服务器不使用本库,或者不是C#语言时。满足报头格式,即可支持本库所有特性。 MessagePipeline является частью функций, разделенных Megumin.remote.
Это также можно понять как стек протоколов.
Он определяет, какие конкретные шаги используются для отправки и получения сообщений. Вы можете настроить сообщение об сообщении и ввести ее в удаленное для удовлетворения некоторых особых потребностей.
Например:
你可以为每个Remote指定一个MessagePipeline实例,如果没有指定,默认使用MessagePipeline.Default。Версия 2.0 удалила MessagePipeline и изменила ее на перезагруженную функцию в нескольких удаленных реализациях. В инженерной практике было обнаружено, что бессмысленно отделить трубопровод сообщений от удаленного и был переоборудован. Если вам нужно одновременно настроить конвейер из трех протоколов, удаленных, пользователь может разделить его сам, и структура не будет обработана.
人生就是反反复复。
Версия 3.0 решила изменить оригинальный дизайн, а идея дизайна первой версии лучше.
После инженерной практики было обнаружено, что дизайн 2.0 не удобен для переписывания. Пользователям необходимо переписать несколько копий одного и того же кода переписать для различных протоколов, наследуя от TCPREMOTE, UDPREMOTE и KCPREMOTE. Каждый раз, когда они изменяются, несколько копий должны быть изменены одновременно, что очень громоздко.
Пользователь в основном переписывает часть принимающего сообщения и отключенную часть, и отключенная часть переподключения также обрабатывается по -разному для различных протоколов.
Так что разделите транспорт и Idisconnecnecthandler от удаленного.
По сути, отдаленный 3,0 равен сообщению 1,0. Транспорт 3.0 равен отдаленному 1,0.
Messagelut (сообщение Serialize Deserialize Callback Table) является основным классом Messagestandard. MessagePipeline сериализована путем поиска функций, зарегистрированных в Messagelut.因此在程序最开始你需要进行函数注册.
Общая регистрационная функция:
void RegistIMeguminFormatter < T > ( KeyAlreadyHave key = KeyAlreadyHave . Skip ) where T : class , IMeguminFormatter , new ( ) Промежуточное программное обеспечение библиотеки классов сериализации предоставляет несколько простых и простых в использовании API на основе Messagelut, автоматически генерируя сериализацию и функции десериализации. Вам необходимо добавить MSGIDATTribute в класс протокола, чтобы обеспечить идентификатор, используемый таблицей поиска. Поскольку идентификатор может соответствовать только одному набору функций сериализации, каждый класс протокола может использовать только одну библиотеку сериализации одновременно.
namespace Message
{
[ MSGID ( 1001 ) ] //MSGID 是框架定义的一个特性,注册函数通过反射它取得ID
[ ProtoContract ] //ProtoContract 是protobuf-net 序列化库的标志
[ MessagePackObject ] //MessagePackObject 是MessagePack 序列化库的标志
public class Login //同时使用多个序列化类库的特性标记,但程序中每个消息同时只能使用一个序列化库
{
[ ProtoMember ( 1 ) ] //protobuf-net 从 1 开始
[ Key ( 0 ) ] //MessagePack 从 0 开始
public string Account { get ; set ; }
[ ProtoMember ( 2 ) ]
[ Key ( 1 ) ]
public string Password { get ; set ; }
}
[ MSGID ( 1002 ) ]
[ ProtoContract ]
[ MessagePackObject ]
public class LoginResult
{
[ ProtoMember ( 1 ) ]
[ Key ( 0 ) ]
public bool IsSuccess { get ; set ; }
}
}Ассамблея может быть зарегистрирована непосредственно в среде JIT
private static async void InitServer ( )
{
//MessagePackLUT.Regist(typeof(Login).Assembly);
Protobuf_netLUT . Regist ( typeof ( Login ) . Assembly ) ;
ThreadPool . QueueUserWorkItem ( ( A ) =>
{
while ( true )
{
ThreadScheduler . Update ( 0 ) ;
Thread . Yield ( ) ;
}
} ) ;
} В среде AOT/IL2CPP необходимо显示регистрацию каждого класса протокола посредством общих функций, чтобы гарантировать, что соответствующие общие функции генерируются во время статического анализа AOT/IL2CPP编译器.
public void TestDefine ( )
{
Protobuf_netLUT . Regist < Login > ( ) ;
Protobuf_netLUT . Regist < LoginResult > ( ) ;
} Уведомление:序列化库используется代码生成器生成代码, который представляет собой функцию сериализации, которая генерирует фактический тип.
Это для создания общих функций для общего API сериализованной библиотеки классов во время статического анализа.
Например:
ProtoBuf.Serializer.Serialize<T>()генерируется какProtoBuf.Serializer.Serialize<Login>()
Два разные.
Каждая библиотека имеет свои собственные ограничения, и поддержка IL2CPP также отличается. Фреймворк напишет новый Messagelut, унаследованный от Messagestandard/Messagelut для каждой поддерживаемой библиотеки.
Поскольку каждая библиотека сериализации поддерживает Span<byte> по -разному, в среднем слое может произойти небольшая потеря производительности.
Есть три формы для функций сериализации:
RPC功能: обеспечивает сопоставление запросов и возвращающихся сообщений один на один. RPCID является отрицательным при отправке, RPCID*-1 является положительным при возвращении, а положительные и отрицательные используются для различения линий вверх и вниз.内存分配: уменьшить выделение с помощью内存池.内存池: стандартный пул памяти библиотеки, ArrayPool<byte>.Shared .序列化: используйте тип для выполнения функции поиска ключей.反序列化: используйте MSGID (int), чтобы выполнить функцию поиска ключей.MessageLUT.Regist<T> Функция вручную добавляет другие типы.消息类型: Старайтесь не использовать большие пользовательские структуры, так как весь процесс сериализации有可能привести к множественной упаковке и распаковке. В процессе передачи параметров он будет скопирован много раз, а производительность ниже, чем в классе.<TargetFrameworks>netstandard2.0;netstandard2.1;net5;net6</TargetFrameworks> .时间和空间上的折衷Размер сообщения не может быть определен до сериализации, поэтому необходимо передать буфер, достаточно большой, необходимо передать в слой сериализации. Если вы не копируете, весь большой буфер будет передаваться непосредственно к отправному слою. Из -за асинхронной природы невозможно точно знать жизненный цикл процесса отправки. Большое количество больших буферов может быть накоплено в отправном уровне, который потребляет сильную память. Следовательно, библиотека создает копию между уровнем сериализации и отправляющим слоем.
Версия 2.0 решает эту проблему с помощью IBufferWriter<byte> и ReadOnlySequence<byte> , что более эффективно.
Это знание или предположение, обобщенное во время библиотеки письма:
Megumin.remote достигается с целью Mmorpg. Это может быть не лучший выбор для игр, не являющихся MMORPG. В отдаленном будущем различные реализации NetRemotestesdard могут быть написаны для разных типов игр.
Where the data stores before we invoke 'socket.read(buffer, offset, count)'?Doubt regarding Winsock Kernel Buffer and Nagle algorithm