هذا هو بسيطة وسهلة الاستخدام مكتبة الشبكة.
هذا حل عالمي لوحدات الشبكة. مصمم لتوفير واجهة موحدة عالية المستوى لوحدات شبكة التطبيق.
يتم تقسيم مكتبة الفصل بأكملها إلى DLLs متعددة. ببساطة: NetRemotestandard.dll هو المعيار ، مع تعريفات الواجهة فقط ؛ megumin.remote.dll هو التنفيذ. ينسق العلاقة بين dotnetstandard و dotnetcore.
لماذا تنقسم إلى DLLs متعددة؟
قد يتطلب التنفيذ المحدد الاعتماد على العديد من DLLs الأخرى ، ولا يتطلب تعريف الواجهة هذه التبعيات. بالنسبة للمستخدمين الذين يرغبون فقط في استخدام الواجهات والتطبيقات المخصصة ، فإن تقديم تبعيات إضافية أمر غير ضروري. على سبيل المثال ، Messagestandard ، يمكن للمستخدمين فقط الرجوع إلى مكتبة التسلسل التي يختارونها ، دون الحاجة إلى الرجوع إلى مكتبات التسلسل المتعددة.

نعم ، استخدم nuget للحصول على megumin.remote. ومع ذلك ، لاحظ أنك تحتاج إلى مطابقة مكتبة التسلسل ، وقد يكون لمكتبات التسلسل المختلفة متطلبات إضافية. نظرًا لاستخدام بناء الجملة C# 7.3 ، يلزم 2018.3 على الأقل إذا تم استخدام رمز المصدر في الوحدة.
ينصح الإطار الهدف NetStandard2.1 ليكون في الإصدار 2021.2 Unity أو أعلى. يمكن استخدام رمز المصدر للإصدارات الصغيرة جدًا ، ولكن يجب حل التبعيات بنفسك.

أو أضف "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 كمخزن مؤقت IO عالي الأداء.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 ) ;
}على الرغم من أنه لا ينصح بأن يتوافق طلب واحد مع أنواع الرد المتعددة ، إلا أن بعض تصميمات الأعمال لا تزال لديها هذا المطلب. على سبيل المثال ، إذا تم الرد على جميع رموز الأخطاء كنوع مستقل ، فقد يكون للطلب نوعان من الإجابات: الردود المقابلة و ErrorCode.
يمكن استخدام 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 ، راجع iPrereceivable ، icmdoption ، sendoption.echo ، إلخ.
يتم تنفيذ نبضات القلب ، RTT ، مزامنة الطابع الزمني وغيرها من الوظائف بواسطة هذه الآلية.
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.
تحتاج إلى استطلاع هذه الوظيفة للتعامل مع رد الاتصال المستلم ، مما يضمن تشغيل رد الاتصال بترتيب الرسائل المستلمة (إذا كان خارج الترتيب ، إرسال خطأ). يجب استخدام ثابت في الوحدة.如果你的消息在分布式服务器之间传递,你可能希望消息在中转进程中尽快传递,那么عندما يتم تنفيذ رد الاتصال المستلم باستخدام المهمة ، وليس عليك الانتظار في الاستطلاع ، ولكن لا يمكن ضمان أن تكون منظمًا ولا يمكن أن يكون لديك مخلب الأسماك والدب.
///建立主线程 或指定的任何线程 轮询。(确保在unity中使用主线程轮询)
///ThreadScheduler保证网络底层的各种回调函数切换到主线程执行以保证执行顺序。
ThreadPool . QueueUserWorkItem ( ( A ) =>
{
while ( true )
{
ThreadScheduler . Update ( 0 ) ;
Thread . Yield ( ) ;
}
} ) ; Message.dll
(AOT/IL2CPP) عندما يتم استيراد الفئة المسلسل إلى الوحدة في شكل DLL (لأن مكتبة فئة الرسائل يتم تصميمها أحيانًا كمشروع مشترك خارج الوحدة) ، يجب إضافة ملف الارتباط لمنع طرق GET وتحديدها من سمات الفئة التسلسلية من قطعها بواسطة 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.
يمكن أيضًا فهمه على أنه كومة بروتوكول.
يحدد الخطوات المحددة المستخدمة لإرسال واستلام الرسائل. يمكنك تخصيص MessagePipeline وحقنها في عن بُعد لتلبية بعض الاحتياجات الخاصة.
على سبيل المثال:
你可以为每个Remote指定一个MessagePipeline实例,如果没有指定,默认使用MessagePipeline.Default。الإصدار 2.0 تم حذف messagePipeline وقام بتغييره إلى وظيفة إعادة الكتابة في تطبيقات عن بُعد متعددة. في الممارسة الهندسية ، وجد أنه كان من غير المعقول فصل خط أنابيب الرسائل عن جهاز التحكم عن بُعد وتم تصميمه بشكل مفرط. إذا كنت بحاجة إلى تخصيص خط أنابيب ثلاثة بروتوكولات عن بُعد في نفس الوقت ، يمكن للمستخدم تقسيمه بنفسه ولن تتم معالجة الإطار.
人生就是反反复复。
قرر الإصدار 3.0 التغيير مرة أخرى إلى التصميم الأصلي ، وكانت فكرة التصميم للإصدار الأول أفضل.
بعد الممارسة الهندسية ، وجد أن تصميم 2.0 ليس مناسبًا لإعادة كتابة. يحتاج المستخدمون إلى إعادة كتابة نسخ متعددة من نفس رمز إعادة كتابة لبروتوكولات مختلفة ، ويرثون من TCPremote و UDPremote و KCPREMOTE. في كل مرة يتم تعديلها ، يجب تعديل نسخ متعددة في نفس الوقت ، وهو ضخم للغاية.
يعيد المستخدم بشكل أساسي كتابة جزء رسالة الاستقبال والجزء غير المتصل ، ويتم التعامل مع جزء إعادة الاتصال غير المتصل أيضًا بشكل مختلف لبروتوكولات مختلفة.
حتى تقسيم النقل و edisconnecthandler من عن بعد.
في الأساس ، فإن جهاز التحكم عن بعد 3.0 يساوي MessagePipeline 1.0. نقل 3.0 يساوي عن بعد من 1.0.
Messagelut (رسالة تسلسل جدول البحث عن رد الاتصال) هي الفئة الأساسية لـ Messagestandard. يتم تسلسل MessagePipeline من خلال البحث عن وظائف مسجلة في Messagelut.因此在程序最开始你需要进行函数注册.
وظيفة التسجيل العامة:
void RegistIMeguminFormatter < T > ( KeyAlreadyHave key = KeyAlreadyHave . Skip ) where T : class , IMeguminFormatter , new ( ) توفر البرامج الوسيطة لمكتبة فئة التسلسل واجهات برمجة تطبيقات بسيطة وسهلة الاستخدام استنادًا إلى 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. في المستقبل البعيد ، قد تتم كتابة تطبيقات مختلفة لـ NetRemotEstandard لأنواع الألعاب المختلفة.
Where the data stores before we invoke 'socket.read(buffer, offset, count)'?Doubt regarding Winsock Kernel Buffer and Nagle algorithm