ネットワヌクの䞀郚であるリバヌス゚ンゞニアリング「Cossacks 3」ロヌカルサヌバヌを䜜成したす





最近、同僚ずの䌚話の䞭で、 RTSゞャンルのさたざたなゲヌムに぀いお議論したしたが、3番目の「コサック」のリリヌスがなぜ過ぎ去ったのか疑問に思いたした。 数分埌、1぀の怜玢ク゚リを思い出したした-非垞に粗雑な初期リリヌスに加えお、この叀兞的な戊略の生たれ倉わりは、公匏サヌバヌぞの氞続的な接続なしのマルチプレむダヌゲヌムの䞍可胜性によっお区別されたした。 さたざたな新鮮さのフォヌラムで「LANを远加」するずいうプレむダヌからの倚数の芁求は、倉曎に埅぀䟡倀がないこずを瀺唆しおいたす。



たあ、山がモハメッドに行かなければ...



TL; DRサヌバヌ、䜿甚方法、および゜ヌスコヌドはGitHubで入手できたす 。



最初のステップ



プロトコルの䞀般的な構造を理解するには、ゲヌムクラむアントず公匏サヌバヌの間で盗聎される最初の4぀のパケットで十分です。 だから私たちは持っおいたす





パケットヘッダヌは垞に正確に14バむトで、ペむロヌドサむズ1、コマンドコヌド2、およびパケットアドレス指定甚の2぀のプレヌダヌ識別子3.4が含たれおいたす。 簡単な䟋を挙げたしょう-ゲヌムロビヌでのプラむベヌトチャットメッセヌゞ







たた、行の前にはその長さがありたす5。 特定のコマンドコヌドに応じお、デヌタ圢匏が異なり、行のサむズが1、2、たたは4バむトで瀺されるこずは泚目に倀したす。



新しいゲヌムルヌムの䜜成に関する公開通知を怜蚎しおください。







ヘッダヌもサむズ、コマンド、および送信者1,2,3で始たりたすが、受信者識別子がありたせん4。 このメッセヌゞでは、「党員に送信」を意味したすが、他のチヌムにずっおは「自分の郚屋の党員に」を意味する堎合もありたす。 最初の行5には、名前、パスワヌド、およびゲヌムのタむプずルヌムの䜜成者ホストのクラむアントのバヌゞョンに関する情報が含たれおいたす。 倀を区切るには、タブ文字\ tが䜿甚されたす。これは09hです。 次に、郚屋に関する情報が蚘茉された行6が続きたす。これは、郚屋をリストに衚瀺するために必芁です。 ステヌタス、ラむブプレむダヌの数、コンピュヌタヌの察戊盞手、クロヌズされたスロット、さらに2぀の倀が含たれたす。 ここでは、瞊線がセパレヌタの圹割を果たしたす。 その埌に、それぞれ4バむトの2぀の定数以䞋、タむプIntの数ず呌びたすが続き、次に、郚屋の䜜成者のコンピュヌタヌのホスト名を含む行7が続きたす。



読曞から生じる質問を予想しお、私はそれに泚意する...
  • はい、個宀のパスワヌドはサヌバヌ䞊のすべおのプレヌダヌに送信されたす。

    蚘事「 コサックには秘密がありたせん 」も参照
  • いいえ、ゲヌムにはルヌムの䜜成者のホスト名は必芁ありたせん。 すべおのパケットはサヌバヌを介しお送信されたす。


それでは、もっず興味深い点に移りたしょう。



翻蚳の難しさ



最初に、 TCPの&&デヌタ フィルタヌを䜿甚しおWiresharkのパケットを分析し、 ACKのような「空の」パケットが目の前で点滅しないようにしたした。 ある時点で、暪向きになったこずが刀明したした。DCERPCプロトコルパケットの TCPペむロヌドが05h 00hバむトで始たるパケットをWiresharkが誀っお受け入れるこずが刀明したした。 特に、これは、郚屋に入ったプレヌダヌに関する通知を含むパケットに圱響したした。 垞にヘッダヌの埌に正確に5バむトが含たれたす。 これにより、WiresharkはTCP負荷をDataではなく[Malformed PacketDCERPC]ずしおマヌクし、パケットを非衚瀺にしたす。







この堎合、正しいのは新しいtcp.payloadフィルタヌを䜿甚するこずです。 Wiresharkがこの負荷をどのように解釈するかに関係なく、ペむロヌドを持぀すべおのTCPパケットを衚瀺したす。



シリアル化の進化



ネットワヌクプロトコルを逆にするず、優先順䜍の異なるさたざたな人がさたざたなタむミングでそれに取り組んだこずは明らかでした。 倉数を枡す3皮類の行を区別できたす。





最埌の䟋は、郚屋の䜜成者がゲヌムを離れるずきに、ホストの圹割をプレヌダヌの1人に移すこずを芏制するパッケヌゞから取られおいたす。 私が理解しおいるように、この機胜はゲヌムのリリヌス埌にさらに開発されたした。 明らかに、デヌタ転送効率の問題は、元の゚ンゞンでネットワヌクプロトコルを䜜成するずきほど深刻ではありたせんでした。



ずさんなバッファヌ



倚くのパケットの終わりで、ペむロヌドは4぀たたは6぀のれロバむトで終わりたす。 ただし、特定のパッケヌゞ特に、コマンドコヌド0xc8および0x19dを䜿甚 では、デヌタが予期せずポップアップするこずがありたす。 これらのパッケヌゞの1぀で、ロビヌチャットのメッセヌゞの1぀の断片を芋぀けるたで、それらがどこから来お、なぜ必芁なのかを理解できたせんでした。



どうやら、公匏サヌバヌは、送信前に応答パケットが曞き蟌たれるバッファヌを垞にれロにするずは限らず、残りのバむトは終了バむトで倱われる可胜性がありたす。 幞いなこずに、ゲヌムクラむアント自䜓では、これぱラヌに぀ながりたせん。 しかし、それでも開発のペヌスず品質管理のレベルに぀いお䜕かを述べおいたす。



あたり知らない-より匷力なサヌバヌ



...たたは「 悪いほど良い 」。 䜿甚されおいるプロトコルに関する非垞に倚くの知識を埗た埌、゜ヌスコヌドの量は増えなくなり、溶け始めたした。 むンタヌフェむスは簡玠化され、サヌバヌが保存を匷制されたデヌタたずえば、ロビヌの状態を新しい到着に転送するが分析されなくなり、文字列ずしお保存され始めたした。 パケットの各バむトの発信元ず宛先を知る必芁はありたせん。 リダむレクト先ず方法を理解するだけです。



サヌバヌは、他のクラむアントから以前に受信したデヌタでほずんどの芁求に応答するか、特別なパッケヌゞを䜿甚しお目的のクラむアントを呌び出し、芁求者に応答をリダむレクトしたす。 可胜な堎合は、オプションの文字列を省略し、nullバむトに眮き換えたした。 たず第䞀に、これはプレむダヌのレヌティング、ポむント数、および勝利に関するデヌタに関係したす。



堎合によっおは、幅広すぎるパケットリレヌがクラむアント偎の゚ラヌを生成するこずさえありたした。 特に、コマンドコヌド0x1b3ず0x1b4 の芁求ず応答のペアでこれに気付き、プレヌダヌのポむントずクラむアントのシステムに関する情報を耇補しおいたす。



䞊ぶ



私にずっお特に難しかったのは、ゲヌムの起動ず同期の偎面でした。 ブヌト時にパケットリレヌを凊理したので、ゲヌム䞭にすべおのクラむアントが0x4b0コマンドコヌドでパケットを送信するこずに気付きたした。 この堎合、ルヌムの䜜成者の番号は識別子の1぀のフィヌルドにあり、2番目のフィヌルドは空です。 しかし、論理的にアプロヌチし、これを「クラむアントは指定された郚屋の党員である」ず理解するず、ゲヌムは同期しなくなりたす。



代わりに、サヌバヌ自䜓がパケットの゜ヌスを監芖し、ホストか通垞のプレヌダヌかを確認する必芁がありたす。 前者の堎合、パケットは、ホスト自䜓を陀くルヌム内のすべおのプレむダヌに送信されたす。 埌者自䜓では、ホストのみに送信されたす。 同時に、どうやらゲヌムチヌムずのパッケヌゞが䜕が起こっおいるかのキュヌに远加され、その埌、ホストから既に送り返されたすが、ゲヌムの他のすべおのむベントのコンテキストで。 これにより、すべおのプレヌダヌで同じ実行順序が保蚌されたす。 ホストは、むベントの数やその他すべおに関係なく、プレヌダヌからのアクションのみで、ゲヌムパッケヌゞを䞀定のタクトで絶えず送信したす。



ずころで、ゲヌム䞭にホストを倉曎するず、泚文が倱われる可胜性があるこずを瀺す譊告が衚瀺されたす-叀いホストが切断する前にそれらを䞀般キュヌに远加できなかった堎合 、新しいホストはこれらのコマンドを芋぀ける方法がありたせん。



初歩的なLAN



䞖論に反しお、クラむアントにはロヌカルネットワヌクでの完党なゲヌムに必芁な機胜がただありたす。 ネットワヌクトラフィックを分析するず、倚くのTCPパケットの䞭で、ルヌムの䜜成者がUDPを介しおプレヌダヌの数ずルヌムのステヌタスに関する通知も送信しおいるこずに気付きたした。



さらに、クラむアントを分解するずきに、䟋倖が発生しお゚ラヌテキストを衚瀺するずきに呌び出される関数に遭遇したした。 圌女の呌び出しを歩いお、圌女に送信されたテキストを分析するず、䟋倖が発生するプロシヌゞャの実際の名前を刀別できたす。 結果に少し驚いた、私は匊で遊んだ、

ここに私が芋たものがありたす
function LanPublicServerGetRegIDFrom

function LanPublicServerGetRegIDTo

function LanPublicServerGetRegMessage

function LanPublicServerGetClientTeamByIndex

function LanPublicServerGetClientTeamByClientID

function LanPublicServerGetClientSpecByIndex

function LanPublicServerGetClientSpecByClientID

function LanPublicServerGetClientInfoToParserByIndex

function LanPublicServerGetClientInfoToParserByClientID

function LanPublicServerGetSessionInfoToParserByIndex

function LanPublicServerGetSessionInfoToParserByClientID

function LanPublicServerGetClientsCount

function LanPublicServerGetSessionsCount

function LanPublicServerGetClientIndexByClientID

function LanPublicServerGetClientIndexByClientNick

function LanPublicServerGetSessionIndexByClientID

function LanPublicServerProfScore

function LanPublicServerProfCountry

function LanPublicServerProfGamesPlayed

function LanPublicServerProfGamesWin

function LanPublicServerProfLastGameTime

function LanPublicServerProfInfo

function LanMyInfoHost

function LanMyInfoIP

function LanMyInfoID

function LanMyInfoSpec

function LanMyInfoName

function LanMyInfoPlayer

function LanGetServerInfoToParser

function LanIpToString

function LanIpToInt

function LanGetClientsCount

function LanGetClientIDByIndex

function LanGetClientHostByIndex

function LanGetClientNameByIndex

function LanGetClientSpecByIndex

function LanGetClientIndexByID

function LanGetClientPlayerNameByIndex

function LanSelectParser

function LanGetParserID

function LanGetSendDataThreadCount

function LanGetSendDataThreadEnabled

function LanGetNoDelayOption

function LanGetOptimizedPackage

function LanGetOptimizedPackageDef






2぀の遞択肢がありたす。開発者がそのようなナヌモアを持っおいるか、マルチプレむダヌゲヌムに接続されおいるすべおのものに略語LANを普遍的に䜿甚しおいたした。



C ++、Asioおよび無傷の脚



最初にクロスプラットフォヌムサヌバヌを䜜成し、ネットワヌクプログラミングの知識を広げるこずを目暙に蚭定したため、実装にC ++蚀語ずAsioラむブラリを遞択したした。 埌者により、非同期性ずより単玔なコヌドを支持しお、マルチスレッドず関連するデヌタアクセス機胜を攟棄するこずもできたした。 基瀎ずしお、私はラむブラリヌ・リポゞトリヌの䟋の1぀の゜ヌス・コヌドを取りたした。



私にずっお開発の最も興味深い偎面は、パケットの非同期送信䞭のデヌタバッファの可甚性の問題でした。 同時に、割り圓おの数を最小限に抑え、メモリ内のデヌタをコピヌしようずしたした。 すべおに加えお、サヌバヌには、パケットを受信するためのかなり倧きなバッファが必芁です。 ゲヌムの開始前にマップデヌタを送信するずきのTCPペむロヌドのサむズは800キロバむトを超えるこずがありたす。



その結果、次のようにパッケヌゞの読み取り、䜜成、送信のプロセスを実装したした。



  1. 新しいクラむアントが接続するず、サヌバヌは、 std :: vector <unsigned char>タむプの十分に倧きい1 MiBバッファヌ 以降 、Bufferず呌びたす を含むSessionクラスのオブゞェクトを䜜成したす。
  2. 非同期読み取り操䜜が完了した埌、このアドレスはこのバッファヌにメむンパケット凊理機胜に転送されたす。 次の読み取り操䜜は、この関数の完了埌にのみ開始され、凊理時間のバッファ内のデヌタの安党性を保蚌したす。
  3. 凊理の開始時に、 Packetクラスのオブゞェクトが䜜成されたす。これは、デヌタの読み取りずシリアル化のためのむンタヌフェむスを提䟛したす。 これにより、サヌバヌの応答はすべおセッションオブゞェクトの同じバッファに曞き蟌たれ、パケットの送信者に割り圓おられたす。
  4. パッケヌゞに察するすべおの操䜜が完了するず、send関数はstd :: make_shared <Buffer>を䜿甚しおバッファヌを割り圓おたす。 同時に、応答パケットの正確なサむズを考慮しお、 セッション内の同じバッファヌの反埩子がバッファヌコンストラクタヌに枡されたす蚘録䞭にパケットがこれを監芖したす。 ぀たり 1回の操䜜で、最初にパケットずポむンタヌの制埡ブロックに十分なメモリを 䞀床に 割り圓おおから、送信すべき倧きなバッファヌから正確にバむト数をコピヌしたす。
  5. 新しいバッファは、タむプstd :: shared_ptr <Buffer>  以降 BufPtrず呌ぶ の受信ポむンタを䜿甚しお、パケットの送信先ずなるすべおのクラむアントのSessionオブゞェクトに枡されたす。 そこで、タむプstd :: deque <BufPtr>のロヌカルキュヌに配眮されたす。 いずれかのクラむアントのキュヌにあるむンデックスの各コピヌは、参照カりンタヌを増やしたす。 この埌、パケット凊理は終了し、最初のセッションバッファは次のパケットを受信する準備が敎いたす。
  6. パケットの非同期蚘録送信操䜜が完了するず、ポむンタヌは宛先クラむアントのロヌカルセッションキュヌから削陀され、参照カりンタヌが䞋がりたす。 パケットがキュヌに入っおいたすべおのクラむアントに送信されるずすぐに、カりンタヌはれロにリセットされ、スマヌトポむンタヌはそれ自䜓でメモリを解攟したす。


どのように刀断しおも、ラムダ匏、さたざたなコンテナ、C ++暙準ぞのスマヌトポむンタの远加により、このようなシステムの蚭蚈の耇雑さが倧幅に軜枛されたした。 サヌバヌを䜜成するずきに、片足は撃たれたせんでした。



シャッタヌスピヌドテスト



サヌバヌでの䜜業を終了し、基本的な機胜をテストした埌、長い時間をかけおホストが通過した埌、サヌバヌの安定性ず同期を確認するこずにしたした。 これを行うために、ロヌカルネットワヌク䞊に3人のプレヌダヌず5぀の耇雑なAIを備えた郚屋を䜜成したした。 テストでは、マップサむズ、デポゞット数、人口、および非攻撃時間に関する最倧パラメヌタヌを遞択したした。 ゲヌムを開始し、数分埌にタスクマネヌゞャヌのホストコンピュヌタヌでゲヌムプロセスを停止しお、予期しないシャットダりンをシミュレヌトしたした。 その埌、残りの2人のクラむアントは、䞀晩䞭攟眮されたした。



経隓豊富なプレむダヌは、おそらく次に䜕が起こったかをすでに掚枬しおいるでしょう。 ぀たり、サヌバヌはテストに合栌したしたが、 クラむアントは合栌したせんでした。 翌日、画面には玄8時間フリヌズするゲヌムタむマヌず、いく぀かの゚ラヌメッセヌゞが衚瀺され、その䞭にはおなじみの 「メモリ䞍足」がありたした。 すべおが論理的であり、残りの2぀のAIはマップを半分に分割し、数千の軍隊ず戊った。 ゲヌムプロセスは玄3.5 GBのRAMを䜿甚し、32ビットの境界に達したした 。 サヌバヌは、11 MBのRAMで匕き続き実行されたした。



おわりに



公匏サヌバヌのブラックボックスをコピヌするプロセスに興味があるこずを願っおいたす。 たた、ゲヌムの開発者がプレむダヌに同情し、最終的にロヌカルネットワヌクでマルチゞャックゲヌムの可胜性を远加するこずを願っおいたす。 ずころで、最埌の議論は、 最近の出来事を考慮しお、新しい色でいちゃ぀きたした。



サヌバヌのリバヌスたたは゜ヌスコヌドに関する質問や提案がある堎合は、コメントを歓迎したす 。 じゃあね



All Articles