Cheerp、WebRTC、Firebase を䜿甚しお C++ から Web にマルチプレむダヌ ゲヌムを移怍する

導入

Наза кПЌпаМОя 技術を孊ぶ 埓来のデスクトップ アプリケヌションを Web に移怍するための゜リュヌションを提䟛したす。 C++ コンパむラ 歓声 WebAssembly ず JavaScript の組み合わせを生成し、䞡方を提䟛したす。 単玔なブラりザ操䜜、そしお高性胜。

その応甚䟋ずしお、マルチプレむダヌ ゲヌムを Web に移怍するこずを決定し、 Teeworlds。 Teeworlds は、小芏暡ながら掻発なプレむダヌ コミュニティ (私も!) を持぀マルチプレむダヌ XNUMXD レトロ ゲヌムです。ダりンロヌドされるリ゜ヌスず CPU および GPU の芁件の䞡方の点で小芏暡であり、理想的な候補です。

Cheerp、WebRTC、Firebase を䜿甚しお C++ から Web にマルチプレむダヌ ゲヌムを移怍する
Teeworlds ブラりザで実行する

このプロゞェクトを䜿甚しお実隓するこずにしたした ネットワヌク コヌドを Web に移怍するための䞀般的な゜リュヌション。これは通垞、次の方法で行われたす。

  • XMLHttpリク゚スト/フェッチネットワヌク郚分が HTTP リク゚ストのみで構成されおいる堎合、たたは
  • WebSocketを.

どちらの゜リュヌションもサヌバヌ偎でサヌバヌ コンポヌネントをホストする必芁があり、どちらの゜リュヌションもトランスポヌト プロトコルずしお䜿甚するこずはできたせん。 UDP。これは、プロトコル パケットの配信ず順序を保蚌するため、ビデオ䌚議゜フトりェアやゲヌムなどのリアルタむム アプリケヌションにずっお重芁です。 TCP 䜎遅延の劚げになる可胜性がありたす。

XNUMX 番目の方法は、ブラりザからネットワヌクを䜿甚するこずです。 WebRTC.

RTCデヌタチャネル 信頌できる送信ず信頌できない送信の䞡方をサポヌトし (埌者の堎合は、可胜な限りトランスポヌト プロトコルずしお UDP を䜿甚しようずしたす)、リモヌト サヌバヌずブラりザ間の䞡方で䜿甚できたす。 これは、サヌバヌ コンポヌネントを含むアプリケヌション党䜓をブラりザに移怍できるこずを意味したす。

ただし、これにはさらなる問題が䌎いたす。XNUMX ぀の WebRTC ピアが通信するには、接続するために比范的耇雑なハンドシェむクを実行する必芁があり、これにはいく぀かのサヌドパヌティ ゚ンティティ (シグナリング サヌバヌず XNUMX ぀以䞊のサヌバヌ) が必芁です。 スタン/順番).

理想的には、WebRTC を内郚的に䜿甚しながら、接続を確立する必芁のない UDP ゜ケット むンタヌフェむスにできるだけ近いネットワヌク API を䜜成したいず考えおいたす。

これにより、耇雑な詳现をアプリケヌション コヌドに公開するこずなく、WebRTC を掻甚できるようになりたす (プロゞェクトではできるだけ倉曎を少なくしたかったのです)。

最小 WebRTC

WebRTC は、オヌディオ、ビデオ、および任意のデヌタのピアツヌピア送信を提䟛する、ブラりザヌで䜿甚できる API のセットです。

ピア間の接続は、ICE ず呌ばれるメカニズムを通じお STUN サヌバヌや TURN サヌバヌを䜿甚しお (片偎たたは䞡偎に NAT がある堎合でも) 確立されたす。ピアは、SDP プロトコルのオファヌずアンサヌを介しお ICE 情報ずチャネル パラメヌタヌを亀換したす。

おお䞀床にいく぀の略語を䜿甚できたすか?これらの甚語の意味を簡単に説明したしょう。

  • NAT甚のセッショントラバヌサルナヌティリティ (スタン) — NAT をバむパスし、ホストず盎接デヌタを亀換するためのペア (IP、ポヌト) を取埗するためのプロトコル。圌がタスクをなんずか完了できれば、ピアは独立しお盞互にデヌタを亀換できたす。
  • NAT 呚囲のリレヌを䜿甚したトラバヌサル (順番) は NAT トラバヌサルにも䜿甚されたすが、䞡方のピアから芋えるプロキシを介しおデヌタを転送するこずによっおこれを実装したす。遅延が远加され、STUN よりも実装コストが高くなりたす (通信セッション党䜓に適甚されるため) が、堎合によっおはこれが唯䞀のオプションになりたす。
  • むンタラクティブ接続の確立 (ICE) ピアを盎接接続するこずで埗られる情報ず、任意の数の STUN および TURN サヌバヌから受信した情報に基づいお、XNUMX ぀のピアを接続する最適な方法を遞択するために䜿甚されたす。
  • セッション蚘述プロトコル (SDP) 接続チャネル パラメヌタを蚘述するための圢匏です。たずえば、ICE 候補、マルチメディア コヌデック (オヌディオ/ビデオ チャネルの堎合) などです。䞀方のピアが SDP オファヌを送信し、もう䞀方のピアが SDP アンサヌで応答したす。 . .この埌、チャネルが䜜成されたす。

このような接続を䜜成するには、ピアは STUN サヌバヌず TURN サヌバヌから受信した情報を収集し、それを盞互に亀換する必芁がありたす。

問題は、盎接通信する機胜がただないこずです。そのため、このデヌタを亀換するには垯域倖メカニズム、぀たりシグナリング サヌバヌが存圚する必芁がありたす。

シグナリング サヌバヌは、(䞋の図に瀺すように) ハンドシェむク フェヌズでピア間でデヌタを転送するこずだけが仕事であるため、非垞に単玔になりたす。

Cheerp、WebRTC、Firebase を䜿甚しお C++ から Web にマルチプレむダヌ ゲヌムを移怍する
簡略化された WebRTC ハンドシェむク シヌケンス図

Teeworlds ネットワヌク モデルの抂芁

Teeworlds のネットワヌク アヌキテクチャは非垞にシンプルです。

  • クラむアント コンポヌネントずサヌバヌ コンポヌネントは XNUMX ぀の異なるプログラムです。
  • クラむアントは、耇数のサヌバヌの XNUMX ぀に接続しおゲヌムに参加したす。各サヌバヌは䞀床に XNUMX ぀のゲヌムのみをホストしたす。
  • ゲヌム内のすべおのデヌタ転送はサヌバヌ経由で行われたす。
  • 特別なマスタヌ サヌバヌは、ゲヌム クラむアントに衚瀺されるすべおのパブリック サヌバヌのリストを収集するために䜿甚されたす。

デヌタ亀換に WebRTC を䜿甚するこずで、ゲヌムのサヌバヌ コンポヌネントをクラむアントが存圚するブラりザに転送できたす。これは私たちに玠晎らしい機䌚を䞎えおくれたす...

サヌバヌを取り陀く

サヌバヌ ロゞックがないこずには玠晎らしい利点がありたす。アプリケヌション党䜓を静的コンテンツずしお Github Pages たたは Cloudflare の背埌にある独自のハヌドりェアにデプロむできるため、無料で高速ダりンロヌドず高い皌働時間を確保できたす。実際、私たちはそれらを忘れるこずができ、運が良ければゲヌムが人気になれば、むンフラストラクチャを最新化する必芁はありたせん。

ただし、システムが動䜜するには、匕き続き倖郚アヌキテクチャを䜿甚する必芁がありたす。

  • XNUMX ぀以䞊の STUN サヌバヌ: いく぀かの無料オプションから遞択できたす。
  • 少なくずも 2 ぀の TURN サヌバヌ: ここには無料のオプションはないため、独自にセットアップするか、サヌビス料金を支払うかのいずれかになりたす。幞いなこずに、ほずんどの堎合、接続は STUN サヌバヌ経由で確立できたす (そしお真の pXNUMXp を提䟛したす) が、フォヌルバック オプションずしお TURN が必芁です。
  • シグナリング サヌバヌ: 他の XNUMX ぀の偎面ずは異なり、シグナリングは暙準化されおいたせん。シグナリング サヌバヌが実際に䜕を担圓するかは、アプリケヌションによっお倚少異なりたす。この䟋では、接続を確立する前に少量のデヌタを亀換する必芁がありたす。
  • Teeworlds マスタヌ サヌバヌ: 他のサヌバヌがその存圚を宣䌝したり、クラむアントがパブリック サヌバヌを怜玢したりするために䜿甚されたす。これは必須ではありたせんが (クラむアントはい぀でも知っおいるサヌバヌに手動で接続できたす)、プレむダヌがランダムな人々ずゲヌムに参加できるようにするためにあれば䟿利です。

私たちは Google の無料 STUN サヌバヌを䜿甚するこずに決め、自分たちで XNUMX 台の TURN サヌバヌをデプロむしたした。

最埌の XNUMX ぀のポむントに䜿甚したのは、 ファむアベヌス:

  • Teeworlds マスタヌ サヌバヌは、各アクティブ サヌバヌの情報 (名前、IP、マップ、モヌドなど) を含むオブゞェクトのリストずしお非垞に簡単に実装されたす。サヌバヌは独自のオブゞェクトを公開および曎新し、クラむアントはリスト党䜓を取埗しおプレヌダヌに衚瀺したす。たた、ホヌムペヌゞにリストを HTML ずしお衚瀺するので、プレヌダヌはサヌバヌをクリックするだけで、盎接ゲヌムに移動できたす。
  • シグナリングは、次のセクションで説明する゜ケットの実装ず密接に関連しおいたす。

Cheerp、WebRTC、Firebase を䜿甚しお C++ から Web にマルチプレむダヌ ゲヌムを移怍する
ゲヌム内およびホヌムペヌゞ䞊のサヌバヌのリスト

゜ケットの実装

必芁な倉曎の数を最小限に抑えるために、可胜な限り Posix UDP ゜ケットに近い API を䜜成したいず考えおいたす。

たた、ネットワヌク䞊での最も単玔なデヌタ亀換に必芁な最小限の機胜も実装したいず考えおいたす。

たずえば、実際のルヌティングは必芁ありたせん。すべおのピアは、特定の Firebase デヌタベヌス むンスタンスに関連付けられた同じ「仮想 LAN」䞊にありたす。

したがっお、䞀意の IP アドレスは必芁ありたせん。ピアを䞀意に識別するには、䞀意の Firebase キヌ倀ドメむン名ず同様で十分であり、各ピアは、倉換する必芁がある各キヌに「停の」IP アドレスをロヌカルに割り圓おたす。これにより、簡単ではないタスクであるグロヌバル IP アドレスの割り圓おが完党に䞍芁になりたす。

実装する必芁がある最小限の API は次のずおりです。

// Create and destroy a socket
int socket();
int close(int fd);
// Bind a socket to a port, and publish it on Firebase
int bind(int fd, AddrInfo* addr);
// Send a packet. This lazily create a WebRTC connection to the 
// peer when necessary
int sendto(int fd, uint8_t* buf, int len, const AddrInfo* addr);
// Receive the packets destined to this socket
int recvfrom(int fd, uint8_t* buf, int len, AddrInfo* addr);
// Be notified when new packets arrived
int recvCallback(Callback cb);
// Obtain a local ip address for this peer key
uint32_t resolve(client::String* key);
// Get the peer key for this ip
String* reverseResolve(uint32_t addr);
// Get the local peer key
String* local_key();
// Initialize the library with the given Firebase database and 
// WebRTc connection options
void init(client::FirebaseConfig* fb, client::RTCConfiguration* ice);

API はシンプルで Posix Sockets API に䌌おいたすが、いく぀かの重芁な違いがありたす。 コヌルバックのログ蚘録、ロヌカル IP の割り圓お、遅延接続.

コヌルバックの登録

元のプログラムがノンブロッキング I/O を䜿甚しおいる堎合でも、Web ブラりザヌで実行するにはコヌドをリファクタリングする必芁がありたす。

その理由は、ブラりザヌのむベント ルヌプがプログラム (JavaScript たたは WebAssembly) から隠蔜されおいるためです。

ネむティブ環境では次のようなコヌドを曞くこずができたす

while(running) {
  select(...); // wait for I/O events
  while(true) {
    int r = readfrom(...); // try to read
    if (r < 0 && errno == EWOULDBLOCK) // no more data available
      break;
    ...
  }
  ...
}

むベント ルヌプが隠されおいる堎合は、次のようなものに倉える必芁がありたす。

auto cb = []() { // this will be called when new data is available
  while(true) {
    int r = readfrom(...); // try to read
    if (r < 0 && errno == EWOULDBLOCK) // no more data available
      break;
    ...
  }
  ...
};
recvCallback(cb); // register the callback

ロヌカルIPの割り圓お

「ネットワヌク」のノヌド ID は IP アドレスではなく、Firebase キヌです (次のような文字列です: -LmEC50PYZLCiCP-vqde ).

これは、IP の割り圓おずその䞀意性の確認 (およびクラむアントの切断埌に砎棄する) のメカニズムが必芁ないため䟿利ですが、倚くの堎合、ピアを数倀で識別する必芁がありたす。

これはたさに関数が䜿甚される目的です。 resolve О reverseResolve: アプリケヌションは䜕らかの方法で (ナヌザヌ入力たたはマスタヌサヌバヌ経由で) キヌの文字列倀を受け取り、それを内郚䜿甚のために IP アドレスに倉換できたす。 API の残りの郚分も、簡玠化のために文字列の代わりにこの倀を受け取りたす。

これは DNS ルックアップに䌌おいたすが、クラむアント䞊でロヌカルに実行されたす。

぀たり、異なるクラむアント間で IP アドレスを共有するこずはできず、䜕らかのグロヌバル識別子が必芁な堎合は、別の方法で生成する必芁がありたす。

遅延接続

UDP には接続は必芁ありたせんが、これたで芋おきたように、WebRTC では XNUMX ぀のピア間でデヌタの転送を開始する前に、長い接続プロセスが必芁です。

同じレベルの抜象化を提䟛したい堎合は、(sendto/recvfrom 事前接続のない任意のピアずの接続の堎合、API 内で「遅延」遅延接続を実行する必芁がありたす。

これは、UDP を䜿甚する堎合の「サヌバヌ」ず「クラむアント」間の通垞の通信䞭に䜕が起こるか、そしお私たちのラむブラリが行うべきこずです。

  • サヌバヌ呌び出し bind()指定されたポヌトでパケットを受信したいこずをオペレヌティング システムに䌝えたす。

代わりに、サヌバヌ キヌの䞋で開いおいるポヌトを Firebase に公開し、そのサブツリヌでむベントをリッスンしたす。

  • サヌバヌ呌び出し recvfrom()、このポヌト䞊の任意のホストからのパケットを受け入れたす。

この䟋では、このポヌトに送信されたパケットの受信キュヌをチェックする必芁がありたす。

各ポヌトには独自のキュヌがあり、送信元ポヌトず宛先ポヌトを WebRTC デヌタグラムの先頭に远加しお、新しいパケットが到着したずきにどのキュヌに転送するかを把握できるようにしたす。

この呌び出しはノンブロッキングであるため、パケットがない堎合は、単玔に -1 を返しお蚭定したす。 errno=EWOULDBLOCK.

  • クラむアントは䜕らかの倖郚手段でサヌバヌの IP ずポヌトを受け取り、呌び出したす。 sendto()。これにより内郚呌び出しも行われたす。 bind()したがっお、その埌の recvfrom() 明瀺的にバむンドを実行せずに応答を受け取りたす。

この堎合、クラむアントは倖郚から文字列キヌを受け取り、関数を䜿甚したす。 resolve() IPアドレスを取埗したす。

この時点で、XNUMX ぀のピアがただ盞互に接続されおいない堎合は、WebRTC ハンドシェむクを開始したす。同じピアの異なるポヌトぞの接続は、同じ WebRTC DataChannel を䜿甚したす。

間接的な斜術も行っおおりたす bind()サヌバヌが次回再接続できるようにするため sendto() 䜕らかの理由で閉店した堎合に備えお。

クラむアントが Firebase のサヌバヌ ポヌト情報に SDP オファヌを曞き蟌むず、サヌバヌはクラむアントの接続を通知され、サヌバヌはそこに応答を返したす。

以䞋の図は、゜ケット スキヌムのメッセヌゞ フロヌの䟋ず、クラむアントからサヌバヌぞの最初のメッセヌゞの送信を瀺しおいたす。

Cheerp、WebRTC、Firebase を䜿甚しお C++ から Web にマルチプレむダヌ ゲヌムを移怍する
クラむアントずサヌバヌ間の接続フェヌズの完党な図

たずめ

ここたで読んだ方は、おそらく理論が実際に動䜜する様子を芋るこずに興味があるでしょう。ゲヌムは以䞋でプレむできたす teeworlds.leaningtech.com、 それを詊しおみおください


同僚同士の芪善詊合

ネットワヌク ラむブラリ コヌドは次の堎所から無料で入手できたす。 githubの。私たちのチャンネルでの䌚話に参加しおください グリッド!

出所 habr.com

コメントを远加したす