นี่คือ ใช้งานง่ายและใช้งานง่าย ห้องสมุดเครือข่าย
นี่เป็นโซลูชันสากลสำหรับโมดูลเครือข่าย ออกแบบมาเพื่อให้อินเทอร์เฟซระดับสูงแบบครบวงจรสำหรับโมดูลแอปพลิเคชันเครือข่าย
ไลบรารีคลาสทั้งหมดถูกแบ่งออกเป็น DLL หลายตัว ใส่ง่ายๆ: netremotestandard.dll เป็นมาตรฐานโดยมีคำจำกัดความอินเตอร์เฟสเท่านั้น megumin.remote.dll เป็นการดำเนินการ อะนาล็อกความสัมพันธ์ระหว่าง Dotnetstandard และ Dotnetcore
ทำไมถึงแยกออกเป็น DLL หลายตัว?
การใช้งานที่เฉพาะเจาะจงอาจต้องพึ่งพา DLL อื่น ๆ อีกมากมายและคำจำกัดความของอินเทอร์เฟซไม่ต้องการการพึ่งพาเหล่านี้ สำหรับผู้ใช้ที่ต้องการใช้อินเทอร์เฟซและการใช้งานที่กำหนดเองเท่านั้นการแนะนำการอ้างอิงเพิ่มเติมนั้นไม่จำเป็น ตัวอย่างเช่น MessageStandard ผู้ใช้สามารถอ้างถึงไลบรารีการทำให้เป็นอนุกรมที่พวกเขาเลือกโดยไม่ต้องอ้างถึงไลบรารีการทำให้เป็นอนุกรมหลายรายการ

ใช่ใช้ NuGet เพื่อรับ megumin.remote อย่างไรก็ตามโปรดทราบว่าคุณต้องจับคู่ไลบรารีอนุกรมและไลบรารีการทำให้เป็นอนุกรมที่แตกต่างกันอาจมีข้อกำหนดเพิ่มเติม เนื่องจากการใช้ไวยากรณ์ C# 7.3 จึงจำเป็นต้องใช้อย่างน้อย 2018.3 หากใช้ซอร์สโค้ดใน Unity
Framework 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 เป็นบัฟเฟอร์ 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 ) ;
}แม้ว่าจะไม่แนะนำว่าคำขอหนึ่งคำขอสอดคล้องกับประเภทการตอบกลับหลายประเภท แต่การออกแบบทางธุรกิจบางอย่างยังคงมีข้อกำหนดนี้ ตัวอย่างเช่นหากรหัสข้อผิดพลาดทั้งหมดถูกตอบเป็นประเภทอิสระคำขออาจมีสองประเภทตอบกลับ: การตอบกลับที่สอดคล้องกันและรหัสข้อผิดพลาด
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 อ้างถึง iPrereceiveBle, ICMDOPTION, SendOption.echo ฯลฯ
Heartbeat, RTT, การซิงโครไนซ์การประทับเวลาและฟังก์ชั่นอื่น ๆ ทั้งหมดถูกนำมาใช้โดยกลไกนี้
PreReceive และการเรียก getResponse เพื่อส่งคืนผลลัพธ์ไปยังผู้ส่ง public interface IAutoResponseable : IPreReceiveable
{
ValueTask < object > GetResponse ( object request ) ;
} การตั้งเวลาเธรด
ระยะไกลใช้ 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 (เนื่องจากไลบรารีคลาสข้อความบางครั้งได้รับการออกแบบเป็นโครงการที่ใช้ร่วมกันนอก Unity) ต้องเพิ่มไฟล์ลิงค์เพื่อป้องกันวิธีการรับและชุดของแอตทริบิวต์คลาสที่เป็นอนุกรม重中之重,因为缺失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 ทุกครั้งที่พวกเขาแก้ไขต้องมีการแก้ไขหลายสำเนาในเวลาเดียวกันซึ่งมีขนาดใหญ่มาก
ผู้ใช้ส่วนใหญ่เขียนชิ้นส่วนข้อความที่ได้รับและส่วนที่ไม่เชื่อมต่อและส่วนที่เชื่อมต่อการเชื่อมต่อใหม่ยังได้รับการจัดการที่แตกต่างกันสำหรับโปรโตคอลที่แตกต่างกัน
ดังนั้นแยกการขนส่งและ idisconnecthandler ออกจากระยะไกล
โดยพื้นฐานแล้วระยะไกลของ 3.0 เท่ากับ MessagePipeline ที่ 1.0 การขนส่ง 3.0 เท่ากับระยะไกลที่ 1.0
MessageLut (ข้อความเป็น serialize deserialize ตารางค้นหาการโทรกลับ) เป็นคลาสหลักของ Messagestandard MessagePipeline ถูกทำให้เป็นอนุกรมโดยค้นหาฟังก์ชั่นที่ลงทะเบียนใน Messagelut因此在程序最开始你需要进行函数注册
ฟังก์ชั่นการลงทะเบียนทั่วไป:
void RegistIMeguminFormatter < T > ( KeyAlreadyHave key = KeyAlreadyHave . Skip ) where T : class , IMeguminFormatter , new ( ) มิดเดิลแวร์ของไลบรารีคลาส Serialization ให้ APIs ที่ง่ายและใช้งานง่ายหลายตัวบนพื้นฐานของ Messagelut โดยอัตโนมัติสร้างฟังก์ชั่นการทำให้เป็นอนุกรมและ deserialization โดยอัตโนมัติ คุณต้องเพิ่ม msgidattribute ในคลาสโปรโตคอลเพื่อให้ ID ที่ใช้โดยตารางการค้นหา เนื่องจาก ID สามารถสอดคล้องกับฟังก์ชั่นการทำให้เป็นอนุกรมหนึ่งชุดเท่านั้นคลาสโปรโตคอลแต่ละคลาสจึงสามารถใช้ไลบรารีอนุกรมเดียวในเวลาเดียวกันเท่านั้น
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 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