これはですシンプルで使いやすいネットワークライブラリ。
これは、ネットワークモジュールの普遍的なソリューションです。アプリケーションネットワークモジュールに統一されたハイレベルインターフェイスを提供するように設計されています。
クラスライブラリ全体が複数のDLLに分割されます。簡単に言えば、netRemotestandard.dllは標準であり、インターフェイス定義のみを備えています。 megumin.remote.dllは実装です。 dotnetstandardとdotnetcoreの関係を類似しています。
なぜ複数のDLLに分割されるのですか?
特定の実装では、他の多くのDLLへの依存を必要とする場合があり、インターフェイス定義ではこれらの依存関係は必要ありません。インターフェイスとカスタム実装のみを使用したいユーザーの場合、追加の依存関係を導入する必要はありません。たとえば、MessageStandardでは、ユーザーは、複数のシリアル化ライブラリを参照することなく、選択したシリアル化ライブラリのみを参照できます。

はい、nugetを使用してmegumin.remoteを取得します。ただし、シリアル化ライブラリを一致させる必要があり、さまざまなシリアル化ライブラリに追加の要件がある場合があることに注意してください。 C#7.3構文の使用により、ソースコードがUnityで使用される場合、少なくとも2018.3が必要です。
ターゲットフレームワークNetStandard2.1は、Unityバージョン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.0path=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 ) ; 結果値には値が保証されています。結果値が空または他の例外である場合、例外コールバック関数はスローされないため、CACTを試す必要はありません。异步方法的后续部分不会触发ため、後続の部分は空のチェックから排除できます。
(注意:这不是语言特性,也不是异步编程特性,这依赖于具体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 ) ;
}1つの要求が複数の返信タイプに対応することはお勧めしませんが、一部のビジネスデザインにはまだこの要件があります。たとえば、すべてのエラーコードが独立したタイプとして返信される場合、リクエストには、対応する応答とエラーコードの2つの応答タイプがあります。
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 ) ;
}スレッドスケジューリング
リモートはbool UseThreadSchedule(int rpcID, short cmd, int messageID, object message)関数を使用して、メッセージコールバック関数が実行されるスレッドを決定します。 Trueの場合、すべてのメッセージがMegumin.threadscheduler.updateに要約されます。
受信したコールバックを処理するためにこの関数を投票する必要があります。これにより、受信したメッセージの順にコールバックがトリガーされることが保証されます(故障している場合は、バグを送信します)。 sixedupdateは通常、統一で使用する必要があります。如果你的消息在分布式服务器之间传递,你可能希望消息在中转进程中尽快传递,那么、falseの場合、受信したコールバックはタスクを使用して実行され、世論調査で待つ必要はありませんが、整然とすることは保証されず、魚とクマの足の両方を持つことはできません。
///建立主线程 或指定的任何线程 轮询。(确保在unity中使用主线程轮询)
///ThreadScheduler保证网络底层的各种回调函数切换到主线程执行以保证执行顺序。
ThreadPool . QueueUserWorkItem ( ( A ) =>
{
while ( true )
{
ThreadScheduler . Update ( 0 ) ;
Thread . Yield ( ) ;
}
} ) ; Message.dll
(AOT/IL2CPP)シリアル化されたクラスがDLLの形でUnityにインポートされる場合(メッセージクラスライブラリは、Unity以外の共有プロジェクトとして設計されることがあるため)、IL2CPPによってカットされないシリアル化クラス属性の取得および設定方法を防ぐためにリンクファイルを追加する必要があります。重中之重,因为缺失get,set函数不会报错,错误通常会被定位到序列化库的多个不同位置(我在这里花费了16个小时)。
<linker>
<assembly fullname="Message" preserve="all"/>
</linker>
| totallength(合計長さ4バイトを含む値) | rpcid | CMD | msgid | 体 |
|---|---|---|---|---|
| 合計長さ(値には、合計長さの4バイトが含まれています) | メッセージID | メッセージテキスト | ||
| int32(int) | int32(int) | int16(ショート) | int32(int) | バイト[] |
| 4byte | 4byte | 2Byte | 4byte | byte []。lenght |
当服务器不使用本库,或者不是C#语言时。满足报头格式,即可支持本库所有特性。 MessagePipelineは、Megumin.Remoteで区切られた関数の一部です。
また、プロトコルスタックとして理解することもできます。
メッセージの送信と受信に使用される特定の手順を決定します。 MessagePipelineをカスタマイズしてリモートに注入して、特別なニーズを満たすことができます。
例えば:
你可以为每个Remote指定一个MessagePipeline实例,如果没有指定,默认使用MessagePipeline.Default。バージョン2.0削除されたMessagePipelineを削除し、複数のリモート実装で書き換え可能な関数に変更しました。エンジニアリングの実践では、リモートからメッセージパイプラインを取り外すことは意味がなく、過度に設計されていることがわかりました。 3つのプロトコルのパイプラインを同時にカスタマイズする必要がある場合、ユーザーは自分でそれを分割することができ、フレームワークは処理されません。
人生就是反反复复。
バージョン3.0は、元のデザインに戻ることを決定しました。最初のバージョンのデザインのアイデアの方が優れています。
エンジニアリングの練習後、2.0の設計は書き換えに便利ではないことがわかりました。ユーザーは、TCPRemote、udpremote、およびKCPRemoteから継承して、同じプロトコルのコードを書き直すのと同じコピーを書き換える必要があります。彼らが変更するたびに、複数のコピーを同時に変更する必要がありますが、これは非常にかさばります。
ユーザーは主に受信メッセージパーツと切断された部分を書き換え、切断された再接続部分も異なるプロトコルで異なる方法で処理されます。
したがって、輸送とidisconnecthandlerをリモートから分割します。
基本的に、3.0のリモートは、1.0のMessagePipelineに等しくなります。 3.0の輸送は、1.0のリモートに等しくなります。
Messagelut(メッセージSerialize Deserializeコールバックルックアップテーブル)は、MessageStandardのコアクラスです。 MessagePipelineは、Messagelutに登録されている関数を探してシリアル化されます。因此在程序最开始你需要进行函数注册。
一般的な登録機能:
void RegistIMeguminFormatter < T > ( KeyAlreadyHave key = KeyAlreadyHave . Skip ) where T : class , IMeguminFormatter , new ( ) Serializationクラスライブラリのミドルウェアは、Messagelutに基づいた複数のシンプルで使いやすいAPIを提供し、シリアル化と脱派化関数を自動的に生成します。ルックアップテーブルで使用されるIDを提供するには、MSGIDATTRIBUTEをプロトコルクラスに追加する必要があります。 IDはシリアル化関数のセットにのみ対応できるため、各プロトコルクラスは同時に1つのシリアル化ライブラリのみを使用できます。
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>()として生成されます。
2つは異なります。
各ライブラリには独自の制限があり、IL2CPPのサポートも異なります。このフレームワークは、サポートされているライブラリごとにMessageStandard/Messagelutから継承された新しいMessagelutを作成します。
各シリアル化ライブラリはSpan<byte>異なるサポートしているため、中間層にわずかなパフォーマンス損失がある可能性があります。
シリアル化関数には3つのフォームがあります。
RPC功能:リクエストと返信メッセージの1対1のマッチングを保証します。 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