序文
Unity フレームワーク JEngine の作成者によって開発された C# サーバー側 RPC フレームワークは、設計が非常に斬新に見え、サーバーは ECS モードを使用します。
このフレームワークは作者自身が書いたNinoシリアライゼーションスキームを使っています.protobufに置き換える場合はTcpServerやメッセージ処理の方法を変更する必要があると思います.将来必要になったら変更しても構いません.ただし,個人的な開発として使用する場合、Nino を使用しても影響はほとんどありません。(ピットを踏むのが怖い)
公式リンクを添付:DaServer
建築
github のホームページに UML 図が描かれていますが、UML 図を理解するにはコードを理解する必要があります
。
共有は、クライアントとサーバー間の共有コードです。
サーバーはサーバーコードです。
クライアントはクライアントです。
下図に示すように、フレームワークの核となる概念は DaServer.Core にあります。
実在物
Entity エンティティは、実際には Timer を含むサービスであり、10 ミリ秒ごとに Update() を呼び出します。
成分
ここでのコンポーネントは Entity のコンポーネントであり、サーバー側には Actor-System-Component モードの別のセットがあります。
セッション
1接続用パッケージ
削除通話
rpc リクエストのカプセル化
通信網
TcpServer と TcpClient は、tcp の最下層のカプセル化であり、通信プロトコルは長さ 4 バイト + メッセージ本文として定義され、メッセージ本文は Nino でシリアル化されます。
メッセージ
ネットワーク通信メッセージ、要求と応答は IMessage から継承する必要があり、属性を通じてメッセージ ID をマークします。詳細については例を参照してください
ダサーバー
DaShare は、使用される基本的な機能をセットアップしただけです. サーバー プログラムのスタートアップ エントリは、
DaServer.Program.cs エントリで非常に単純です. エンティティを開始し、3 つのコンポーネントを追加します. 異なるコンポーネントは異なる機能を提供します.
コンポーネントを追加すると、コンポーネントの create メソッドが呼び出されます。その後、エンティティの更新サイクルは、各コンポーネントの更新を順番に呼び出します。
TcpComponent & RemoteCallComponent
このコンポーネントで TCP リッスンを開始します。次のコードに注意してください。データが受信されると、RemoteCall 構造に解析され、受信メッセージ キューに追加されます。Update で RemoteCallComponent コンポーネントを呼び出します。このコンポーネントも最初にキューに入れられ、次に update で処理されます。
OnClientDataReceived += (id, data) =>
{
//get session
if (_sessions.TryGetValue(id, out _))
{
//process data
_queue.Enqueue((id, MessageFactory.GetRemoteCall(data)));
}
};
ActorSystemComponent
このコンポーネントは、システム コンポーネント メカニズムを実装します。
俳優はプレーヤーとして理解することができます。
このコンポーネントは、すべてのプレーヤーの管理を担当します。
プレイヤーが rpc リクエストを送信したとき。コンポーネントにプレーヤー用のアクターがない場合は、作成されます。
コンポーネントの作成中に、アクターの更新を呼び出すために無限スレッド ループが作成されます。
このコンポーネントの更新でシグナルを送信して、アクター スレッドの更新を開始します。
アクターの更新は、実際にはすべてのアクター システムを呼び出します。システムを更新できる場合は、対応する更新を呼び出します。
システムの更新は、実際にシステムが保持する各コンポーネントの更新を呼び出します。
コンポーネントには更新間隔があり、10ms を超えた場合にのみ更新されます。
ここで ASC 関係を要約すると、C はどの S とどの A に属しているかを認識し、S はこのタイプのアクターのすべてのコンポーネントを保持し、アクターは ActorSystemComponent コンポーネントを介して独自の S と C を操作します。
更新時、ActorSystemComponent の更新はすべてのシステムの更新を呼び出し、コンポーネントがどのアクタに属しているかに関係なく、システムは保持しているコンポーネントの更新を順番に呼び出します。
現在、SessionComponent と RequestComponent の 2 つの Actor コンポーネントが実装されています. SessionComponent は、接続が切断された後の Actor の削除を実装し、RequestComponent は RPC リクエストを処理するコンポーネントです。
RPC を処理する
上記の RemoteCallComponent コンポーネントの更新では、セッションに従ってアクターが検索され、対応するアクターの RequestComponent コンポーネントに rpc リクエストが配置されます。
応答結果の戻りをブロックせずに、要求コンポーネントの更新で応答メソッドを呼び出します
RPC の例
DaServer.UnitTest を参照して、対応するメッセージ処理メソッドを直接記述します。
ダクライアント
以下はクライアントの使用方法のデモンストレーションです. 次の例では、接続後、100 個の同一のリクエストが並行して送信され、await は非同期で応答を待ちます。
client.OnConnected += () =>
{
Logger.Info("客户端连上了服务端");
Parallel.For(0, 100, async (i, _) =>
{
await Task.Delay(i * 10);
var response = await Request<MTestRequest, MTestResponse>(client, new MTestRequest()
{
Txt = "hello"
});
Logger.Info("客户端收到了服务端的回应: {@c}", response);
Logger.Info("多线程任务{i} 收到回应后的线程ID: {c}", i, Thread.CurrentThread.ManagedThreadId);
});
};
TaskCompletionSource を使用すると、応答が同じスレッドにあることが保証されることに注意してください。
Request は GetResponse を呼び出して、新しい TaskCompletionSource オブジェクトを作成し、応答タイムアウトを設定します。
応答を受信する過程で、AddResponse が呼び出され、この tcs.SetResult が設定されるため、await Request が正常に返され、要求と同じスレッドになります。