Ini adalah Sederhana dan mudah digunakan Perpustakaan Jaringan.
Ini adalah solusi universal untuk modul jaringan. Dirancang untuk menyediakan antarmuka tingkat tinggi terpadu untuk modul jaringan aplikasi.
Seluruh perpustakaan kelas dibagi menjadi beberapa DLL. Sederhananya: netremotestandard.dll adalah standar, dengan hanya definisi antarmuka; Megumin.remote.dll adalah implementasi. Analogisasi hubungan antara dotnetstandard dan dotnetcore.
Mengapa dibagi menjadi beberapa DLL?
Implementasi spesifik mungkin memerlukan ketergantungan pada banyak DLL lainnya, dan definisi antarmuka tidak memerlukan dependensi ini. Untuk pengguna yang hanya ingin menggunakan antarmuka dan implementasi khusus, memperkenalkan dependensi tambahan tidak perlu. Misalnya, MessagestAddard, pengguna hanya dapat merujuk ke perpustakaan serialisasi yang mereka pilih, tanpa harus merujuk ke beberapa perpustakaan serialisasi.

Ya, gunakan Nuget untuk mendapatkan megumin.remote. Namun, perhatikan bahwa Anda perlu mencocokkan perpustakaan serialisasi, dan perpustakaan serialisasi yang berbeda mungkin memiliki persyaratan tambahan. Karena penggunaan sintaks C# 7.3, setidaknya 2018.3 diperlukan jika kode sumber digunakan dalam Unity.
Kerangka Target NetStandard2.1 disarankan untuk berada di Unity versi 2021.2 atau lebih. Kode sumber dapat digunakan untuk versi yang terlalu kecil, tetapi dependensi perlu diselesaikan sendiri.

Atau tambahkan "com.megumin.net": "https://github.com/KumoKyaku/Megumin.Net.git?path=UnityPackage/Packages/Net" ke Packages/manifest.json .
Jika Anda
#2.1.0mengatur versi target, gunakan tag rilis*.*.*Misalnyahttps://github.com/KumoKyaku/Megumin.Net.git?path=UnityPackage/Packages/Net#2.1.0.
Span<T> . Gunakan System.IO.Pipelines sebagai buffer IO berkinerja tinggi.MIT许可证 Prinsip Desain: Kode yang paling umum digunakan adalah yang paling disederhanakan, dan semua bagian kompleks dienkapsulasi.发送一个消息,并等待一个消息返回adalah seluruh konten perpustakaan kelas.
Masuk akal untuk mengembalikan pengecualian dari nilai hasil:
///实际使用中的例子
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 ) ;
}
} Metode Signature:
ValueTask < Result > SendAsyncSafeAwait < Result > ( object message , object options = null , Action < Exception > onException = null ) ; Nilai hasil dijamin memiliki nilai. Jika nilai hasilnya kosong atau pengecualian lainnya, fungsi panggilan balik pengecualian tidak akan dilemparkan, jadi tidak perlu mencoba menangkap.异步方法的后续部分不会触发, sehingga bagian selanjutnya dapat dihilangkan dari pemeriksaan kosong.
(注意:这不是语言特性,也不是异步编程特性,这依赖于具体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 ) ;
}Meskipun tidak disarankan bahwa satu permintaan sesuai dengan beberapa jenis balasan, beberapa desain bisnis masih memiliki persyaratan ini. Misalnya, jika semua kode kesalahan dijawab sebagai jenis independen, permintaan mungkin memiliki dua jenis balasan: balasan dan kode kesalahan yang sesuai.
IMessage接口dapat digunakan sebagai jenis untuk menunggu pengembalian dalam protokol 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);Fungsi panggilan balik penerima
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 ;
} Untuk metode respons spesifik, lihat kode sumber fungsi PreReceive , lihat iprereceivable, icmdoption, sendoption.echo, dll.
Detak jantung, RTT, sinkronisasi waktu dan fungsi lainnya semuanya diimplementasikan oleh mekanisme ini.
PreReceive , dan panggilan GetResponse untuk mengembalikan hasilnya ke pengirim. public interface IAutoResponseable : IPreReceiveable
{
ValueTask < object > GetResponse ( object request ) ;
} Penjadwalan utas
Remote menggunakan bool UseThreadSchedule(int rpcID, short cmd, int messageID, object message) fungsi untuk menentukan utas mana fungsi panggilan balik pesan dijalankan. Ketika benar, semua pesan dirangkum menjadi megumin.threadscheduler.update.
Anda perlu polling fungsi ini untuk menangani panggilan balik yang diterima, yang memastikan bahwa panggilan balik dipicu dalam urutan pesan yang diterima (jika rusak, kirimkan bug). FixedUpdate biasanya harus digunakan dalam persatuan.如果你的消息在分布式服务器之间传递,你可能希望消息在中转进程中尽快传递,那么ketika salah, panggilan balik yang diterima dieksekusi menggunakan tugas, dan Anda tidak perlu menunggu dalam jajak pendapat, tetapi tidak dapat dijamin tertib dan tidak dapat memiliki ikan dan kaki beruang.
///建立主线程 或指定的任何线程 轮询。(确保在unity中使用主线程轮询)
///ThreadScheduler保证网络底层的各种回调函数切换到主线程执行以保证执行顺序。
ThreadPool . QueueUserWorkItem ( ( A ) =>
{
while ( true )
{
ThreadScheduler . Update ( 0 ) ;
Thread . Yield ( ) ;
}
} ) ; Message.dll
(AOT/IL2CPP) Ketika kelas serial diimpor ke dalam persatuan dalam bentuk DLL (karena pustaka kelas pesan kadang -kadang dirancang sebagai proyek bersama di luar Unity), file tautan harus ditambahkan untuk mencegah metode GET dan yang ditetapkan dari atribut kelas serialisasi agar tidak dipotong oleh IL2CPP.重中之重,因为缺失get,set函数不会报错,错误通常会被定位到序列化库的多个不同位置(我在这里花费了16个小时)。
<linker>
<assembly fullname="Message" preserve="all"/>
</linker>
| Totallength (nilai termasuk total panjang 4 byte) | Rpcid | Cmd | Msgid | Tubuh |
|---|---|---|---|---|
| Total panjang (nilai berisi 4 byte dari panjang total itu sendiri) | ID Pesan | Teks pesan | ||
| Int32 (int) | Int32 (int) | Int16 (pendek) | Int32 (int) | byte [] |
| 4byte | 4byte | 2byte | 4byte | byte []. Lenght |
当服务器不使用本库,或者不是C#语言时。满足报头格式,即可支持本库所有特性。 Messagepipeline adalah bagian dari fungsi yang dipisahkan oleh megumin.remote.
Itu juga dapat dipahami sebagai tumpukan protokol.
Ini menentukan langkah -langkah spesifik apa yang digunakan untuk mengirim dan menerima pesan. Anda dapat menyesuaikan MessAgePipeline dan menyuntikkannya ke Remote untuk memenuhi beberapa kebutuhan khusus.
Misalnya:
你可以为每个Remote指定一个MessagePipeline实例,如果没有指定,默认使用MessagePipeline.Default。Versi 2.0 Dihapus Messagepipeline dan mengubahnya menjadi fungsi yang dapat ditulis ulang di beberapa implementasi jarak jauh. Dalam praktik teknik, ditemukan bahwa tidak ada artinya untuk melepaskan pipa pesan dari Remote dan dirancang berlebihan. Jika Anda perlu menyesuaikan pipa tiga protokol jarak jauh pada saat yang sama, pengguna dapat membaginya sendiri dan kerangka kerja tidak akan diproses.
人生就是反反复复。
Versi 3.0 telah memutuskan untuk berubah kembali ke desain asli, dan ide desain versi pertama lebih baik.
Setelah latihan teknik, ditemukan bahwa desain 2.0 tidak nyaman untuk ditulis ulang. Pengguna perlu menulis ulang beberapa salinan kode penulisan ulang yang sama untuk protokol yang berbeda, mewarisi dari TcPremote, UDPremote, dan KCPremote. Setiap kali mereka memodifikasi, banyak salinan harus dimodifikasi secara bersamaan, yang sangat besar.
Pengguna terutama menulis ulang bagian pesan penerima dan bagian yang terputus, dan bagian koneksi ulang yang terputus juga ditangani secara berbeda untuk protokol yang berbeda.
Jadi pisahkan transportasi dan Idisconnecthandler dari Remote.
Pada dasarnya, remote 3.0 sama dengan MessagePipeline 1.0. Transportasi 3.0 sama dengan remote 1.0.
MessageLut (pesan Serialize Deserialize Tabel pencarian callback) adalah kelas inti dari Messagestandard. Messagepipeline di -serial dengan mencari fungsi yang terdaftar di MessageLut.因此在程序最开始你需要进行函数注册.
Fungsi Pendaftaran Umum:
void RegistIMeguminFormatter < T > ( KeyAlreadyHave key = KeyAlreadyHave . Skip ) where T : class , IMeguminFormatter , new ( ) Middleware dari Perpustakaan Kelas Serialisasi menyediakan beberapa API sederhana dan mudah digunakan berdasarkan MessageLut, secara otomatis menghasilkan fungsi serialisasi dan deserialisasi. Anda perlu menambahkan msGidattribute ke kelas protokol untuk memberikan ID yang digunakan oleh tabel pencarian. Karena ID hanya dapat sesuai dengan satu set fungsi serialisasi, setiap kelas protokol hanya dapat menggunakan satu pustaka serialisasi secara bersamaan.
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 ; }
}
}Majelis dapat didaftarkan langsung di bawah lingkungan 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 ( ) ;
}
} ) ;
} Dalam lingkungan AOT/IL2CPP , perlu显示pendaftaran masing -masing kelas protokol melalui fungsi generik untuk memastikan bahwa fungsi generik yang sesuai dihasilkan selama analisis statis AOT/IL2CPP编译器.
public void TestDefine ( )
{
Protobuf_netLUT . Regist < Login > ( ) ;
Protobuf_netLUT . Regist < LoginResult > ( ) ;
} Melihat:序列化库menggunakan代码生成器生成代码, yang merupakan fungsi serialisasi yang menghasilkan tipe aktual.
Ini untuk menghasilkan fungsi generik untuk API umum perpustakaan kelas serial selama analisis statis.
Misalnya:
ProtoBuf.Serializer.Serialize<T>()dihasilkan sebagaiProtoBuf.Serializer.Serialize<Login>()
Keduanya berbeda.
Setiap perpustakaan memiliki keterbatasan sendiri, dan dukungan untuk IL2CPP juga berbeda. Kerangka kerja ini akan menulis Messagelut baru yang diwarisi dari Messagestandard/Messagelut untuk setiap perpustakaan yang didukung.
Karena setiap perpustakaan serialisasi mendukung Span<byte> secara berbeda, mungkin ada sedikit kerugian kinerja di lapisan tengah.
Ada tiga bentuk untuk fungsi serialisasi:
RPC功能: Memastikan pencocokan permintaan dan pengembalian pesan satu-ke-satu. RPCID adalah negatif saat mengirim, RPCID*-1 positif saat kembali, dan positif dan negatif digunakan untuk membedakan garis naik dan turun.内存分配: Kurangi alloc dengan menggunakan内存池.内存池: Pool Memori Perpustakaan Standar, ArrayPool<byte>.Shared .序列化: Gunakan tipe untuk melakukan fungsi pencarian kunci.反序列化: Gunakan msgid (int) untuk melakukan fungsi pencarian kunci.MessageLUT.Regist<T> secara manual menambahkan jenis lain.消息类型: Cobalah untuk tidak menggunakan struct khusus yang besar, karena seluruh proses serialisasi有可能menyebabkan beberapa pengepakan dan unboxing. Selama proses transfer parameter, itu akan disalin berkali -kali, dan kinerjanya lebih rendah dari kelas.<TargetFrameworks>netstandard2.0;netstandard2.1;net5;net6</TargetFrameworks> .时间和空间上的折衷Ukuran pesan tidak dapat ditentukan sebelum serialisasi, sehingga buffer yang cukup besar perlu diteruskan ke lapisan serialisasi. Jika Anda tidak menyalin, seluruh buffer besar akan langsung diteruskan ke lapisan pengirim. Karena sifat asinkron, tidak mungkin untuk secara akurat mengetahui siklus hidup dari proses pengirim. Sejumlah besar buffer besar dapat diakumulasikan di lapisan pengirim, yang mengkonsumsi memori parah. Oleh karena itu, perpustakaan membuat salinan antara lapisan serialisasi dan lapisan pengiriman.
Versi 2.0 memecahkan masalah ini menggunakan IBufferWriter<byte> dan ReadOnlySequence<byte> , yang lebih efisien.
Ini adalah pengetahuan atau tebakan yang dirangkum selama perpustakaan menulis:
Megumin.remote dicapai dengan tujuan MMORPG. Ini mungkin bukan pilihan terbaik untuk game non-MMORPG. Di masa depan yang jauh, berbagai implementasi netremotestandard dapat ditulis untuk jenis permainan yang berbeda.
Where the data stores before we invoke 'socket.read(buffer, offset, count)'?Doubt regarding Winsock Kernel Buffer and Nagle algorithm