Porting kaulinan multiplayer ti C ++ ka web kalawan Cheerp, WebRTC na Firebase

perkenalan

pausahaan urang Téhnologi condong nyadiakeun solusi pikeun porting aplikasi desktop tradisional ka web. C++ compiler kami surak dibangkitkeun kombinasi WebAssembly na JavaScript, nu nyadiakeun na interaksi browser basajan, jeung kinerja luhur.

Salaku conto aplikasi na, urang mutuskeun port kaulinan multiplayer ka web tur milih Teeworlds. Teeworlds mangrupikeun kaulinan retro XNUMXD Multiplayer sareng komunitas pamaén anu leutik tapi aktip (kaasup kuring!). Éta leutik boh tina segi sumber anu diunduh sareng syarat CPU sareng GPU - calon idéal.

Porting kaulinan multiplayer ti C ++ ka web kalawan Cheerp, WebRTC na Firebase
Ngajalankeun dina browser Teeworlds

Kami mutuskeun pikeun ngagunakeun proyék ieu pikeun ékspérimén solusi umum pikeun porting kode jaringan ka web. Ieu biasana dilakukeun ku cara di handap ieu:

  • XMLHttpRequest / dipulut, lamun bagian jaringan ngan diwangun ku requests HTTP, atawa
  • sockets wéb.

Duanana solusi merlukeun hosting komponén server di sisi server, sarta henteu ngamungkinkeun pikeun pamakéan salaku protokol angkutan UDP. Ieu penting pikeun aplikasi sacara real-time sapertos parangkat lunak konferensi video sareng kaulinan, sabab ngajamin pangiriman sareng urutan pakét protokol. TCP bisa jadi halangan pikeun low latency.

Aya cara katilu - nganggo jaringan tina browser: WebRTC.

Saluran RTCData Éta ngadukung transmisi anu tiasa dipercaya sareng teu tiasa dipercaya (dina kasus anu terakhir éta nyobian nganggo UDP salaku protokol angkutan sabisana), sareng tiasa dianggo sareng server jauh sareng antara browser. Ieu ngandung harti yén urang bisa port sakabéh aplikasi pikeun browser nu, kaasup komponén server!

Sanajan kitu, ieu hadir kalawan kasusah tambahan: saméméh dua peers WebRTC bisa komunikasi, maranéhna kudu ngalakukeun sasalaman kawilang kompléks pikeun nyambung, nu merlukeun sababaraha éntitas pihak-katilu (a server signalling sarta hiji atawa leuwih server. STUN/Bélok).

Ideally, urang hoyong nyieun API jaringan anu ngagunakeun WebRTC internal, tapi sacaket mungkin ka panganteur UDP Sockets nu teu perlu nyieun sambungan.

Ieu bakal ngidinan urang ngamangpaatkeun WebRTC tanpa kudu ngalaan detil kompléks kana kode aplikasi (anu urang hayang ngarobah saeutik-gancang dina proyék urang).

WebRTC minimum

WebRTC mangrupikeun sakumpulan API anu sayogi dina panyungsi anu nyayogikeun transmisi audio, video sareng data sawenang-wenang peer-to-peer.

Sambungan antara peers ngadegkeun (sanajan aya NAT dina hiji atawa dua sisi) ngagunakeun STUN jeung / atawa TURN server ngaliwatan mékanisme disebut ICE. Peers tukeur inpormasi ICE sareng parameter saluran via tawaran sareng jawaban tina protokol SDP.

Wow! Sabaraha singgetan dina hiji waktu? Hayu urang ngajelaskeun sakeudeung naon istilah ieu hartosna:

  • Sesi Traversal Utiliti pikeun NAT (STUN) - protokol pikeun bypassing NAT tur meunangkeun pasangan (IP, port) pikeun bursa data langsung jeung host. Upami anjeunna tiasa ngarengsekeun tugasna, maka peers tiasa silih tukeur data sacara mandiri.
  • Traversal Ngagunakeun Relays sabudeureun NAT (Bélok) ogé dipaké pikeun traversal NAT, tapi implements ieu ku diteruskeun data ngaliwatan proxy nu katingali ku duanana peers. Éta nambihan latency sareng langkung mahal pikeun diimplementasikeun tibatan STUN (sabab diterapkeun sapanjang sési komunikasi sadayana), tapi sakapeung éta hiji-hijina pilihan.
  • Ngadegna Konéktipitas Interaktif (ICE) dipaké pikeun milih metodeu pangalusna pikeun nyambungkeun dua peers dumasar kana informasi diala tina nyambungkeun peers langsung, kitu ogé informasi narima ku sagala Jumlah server STUN na TURN.
  • Sidang Déskripsi Protocol (RDS) mangrupakeun format pikeun ngajelaskeun parameter saluran sambungan, contona, calon ICE, codec multimédia (dina kasus saluran audio / video), jsb ... Salah sahiji peers ngirimkeun hiji Tawaran SDP, sarta kadua ngabales kalawan SDP Jawaban. .. Sanggeus ieu, saluran dijieun.

Pikeun nyieun sambungan sapertos kitu, peers kedah ngumpulkeun inpormasi anu aranjeunna nampi ti server STUN sareng TURN sareng silih tukeur.

Masalahna nyaeta aranjeunna teu acan gaduh kamampuhan pikeun komunikasi langsung, jadi hiji mékanisme out-of-band kudu aya pikeun tukeur data ieu: server signalling.

Pangladén sinyal tiasa saderhana pisan sabab ngan ukur padamelan pikeun neraskeun data antara peers dina fase sasalaman (sapertos anu dipidangkeun dina diagram di handap).

Porting kaulinan multiplayer ti C ++ ka web kalawan Cheerp, WebRTC na Firebase
Diagram runtuyan sasalaman WebRTC saderhana

Ihtisar Modél Network Teeworlds

Arsitéktur jaringan Teeworlds saderhana pisan:

  • Komponén klien sareng server mangrupikeun dua program anu béda.
  • Klién asup kana kaulinan ku cara ngahubungkeun ka salah sahiji sababaraha server, nu masing-masing host ngan hiji kaulinan dina hiji waktu.
  • Sadaya transfer data dina kaulinan dilaksanakeun ngaliwatan server.
  • A server master husus dipaké pikeun ngumpulkeun daptar sadaya server publik nu dipintonkeun dina klien kaulinan.

Hatur nuhun kana pamakéan WebRTC pikeun bursa data, urang tiasa nransferkeun komponén server game ka browser dimana klien nu lokasina. Ieu masihan kami kasempetan anu saé ...

Nyingkirkeun server

Kurangna logika server ngagaduhan kauntungan anu saé: urang tiasa nyebarkeun sadaya aplikasi salaku eusi statik dina Github Pages atanapi dina hardware urang sorangan di tukangeun Cloudflare, ku kituna mastikeun unduhan gancang sareng uptime tinggi gratis. Nyatana, urang tiasa hilap ngeunaan aranjeunna, sareng upami urang untung sareng kaulinan janten populer, maka infrastrukturna henteu kedah dimodernisasi.

Nanging, supados sistem tiasa jalan, urang tetep kedah nganggo arsitéktur éksternal:

  • Hiji atanapi langkung server STUN: Kami ngagaduhan sababaraha pilihan gratis pikeun dipilih.
  • Sahenteuna hiji server TURN: teu aya pilihan gratis di dieu, ku kituna urang tiasa nyetél sorangan atanapi mayar jasa éta. Untungna, lolobana waktu sambungan bisa dijieun ngaliwatan server STUN (jeung nyadiakeun p2p leres), tapi TURN diperlukeun salaku pilihan fallback.
  • Server Signaling: Beda sareng dua aspék anu sanés, sinyal henteu standar. Naon server signalling sabenerna bakal jawab gumantung rada kana aplikasi. Dina kasus urang, saméméh nyieun sambungan, perlu pikeun tukeur jumlah leutik data.
  • Teeworlds Master Server: Hal ieu dipaké ku server séjén pikeun Ngaiklan ayana maranéhanana sarta ku klien pikeun manggihan server umum. Bari teu diperlukeun (klien salawasna bisa nyambung ka server maranéhna terang ngeunaan sacara manual), eta bakal hade ka gaduh ambéh pamaén bisa ilubiung dina kaulinan kalayan jalma acak.

Kami mutuskeun pikeun ngagunakeun server STUN gratis Google, sareng nyebarkeun hiji server TURN sorangan.

Pikeun dua titik panungtungan kami dipaké Firebase:

  • Server master Teeworlds dilaksanakeun saderhana pisan: salaku daptar objék anu ngandung inpormasi (ngaran, IP, peta, modeu, ...) unggal server aktip. Server nyebarkeun jeung ngamutahirkeun obyék sorangan, sarta klien nyokot sakabéh daptar tur nembongkeun ka pamuter nu. Urang ogé mintonkeun daptar dina kaca imah salaku HTML jadi pamaén saukur bisa klik dina server jeung dibawa langsung ka buruan.
  • Signaling raket patalina jeung palaksanaan sockets kami, dijelaskeun dina bagian salajengna.

Porting kaulinan multiplayer ti C ++ ka web kalawan Cheerp, WebRTC na Firebase
Daptar server jero kaulinan sarta dina kaca imah

Palaksanaan sockets

Kami hoyong ngadamel API anu caket sareng Posix UDP Sockets sabisa pikeun ngaleutikan jumlah parobihan anu diperyogikeun.

Simkuring oge hoyong nerapkeun minimum diperlukeun diperlukeun pikeun bursa data pangbasajanna ngaliwatan jaringan.

Contona, urang teu kedah routing nyata: kabeh peers on sarua "LAN maya" pakait sareng conto database Firebase husus.

Ku alatan éta, kami henteu peryogi alamat IP anu unik: nilai konci Firebase anu unik (sarupa sareng nami domain) cekap pikeun ngaidentipikasi peers sacara unik, sareng unggal peer sacara lokal masihan alamat IP "palsu" ka unggal konci anu kedah ditarjamahkeun. Ieu lengkep ngaleungitkeun kabutuhan ngerjakeun alamat IP global, anu mangrupikeun tugas anu teu penting.

Ieu mangrupikeun API minimum anu urang kedah laksanakeun:

// 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 saderhana sareng mirip sareng Posix Sockets API, tapi gaduh sababaraha béda anu penting: logging callbacks, assigning IP lokal, sarta sambungan puguh.

Ngadaptar Callbacks

Sanajan program aslina ngagunakeun non-blocking I / O, kode kudu refactored ngajalankeun dina web browser.

Alesan pikeun ieu nyaéta yén loop acara dina browser disumputkeun tina program (naha JavaScript atanapi WebAssembly).

Di lingkungan asli urang tiasa nyerat kode sapertos kieu

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;
    ...
  }
  ...
}

Upami loop acara disumputkeun ka urang, maka urang kedah ngagentoskeunana sapertos kieu:

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

ngerjakeun IP lokal

ID titik dina "jaringan" urang sanes alamat IP, tapi konci Firebase (aranjeunna mangrupikeun senar anu siga kieu: -LmEC50PYZLCiCP-vqde ).

Ieu merenah sabab urang teu butuh mékanisme pikeun assigning IP jeung mariksa uniqueness maranéhanana (ogé disposing aranjeunna sanggeus klien disconnects), tapi mindeng diperlukeun pikeun ngaidentipikasi peers ku nilai numerik.

Ieu persis naon fungsi nu dipaké pikeun. resolve и reverseResolve: Aplikasi nu kumaha bae nampi nilai string sahiji konci (via input pamaké atawa via server master), sarta bisa ngarobah kana alamat IP pikeun pamakéan internal. Sesa API ogé nampi nilai ieu tinimbang string pikeun kesederhanaan.

Ieu sarupa DNS lookup, tapi dipigawé sacara lokal dina klien nu.

Hartina, alamat IP teu bisa dibagikeun antara klien béda, sarta lamun sababaraha jenis identifier global diperlukeun, éta kudu dihasilkeun dina cara béda.

Puguh sambungan

UDP henteu peryogi sambungan, tapi sakumaha anu urang tingali, WebRTC ngabutuhkeun prosés sambungan anu panjang sateuacan tiasa ngamimitian nransfer data antara dua peers.

Upami urang hoyong nyayogikeun tingkat abstraksi anu sami, (sendto/recvfrom kalawan peers wenang tanpa sambungan prior), mangka maranéhanana kudu ngalakukeun "puguh" (nunda) sambungan jero API.

Ieu naon anu lumangsung nalika komunikasi normal antara "server" sareng "klien" nalika nganggo UDP, sareng naon anu kedah dilakukeun ku perpustakaan urang:

  • nelepon server bind()pikeun ngabejaan sistem operasi nu eta hayang nampa pakét dina port dieusian.

Gantina, urang bakal nyebarkeun hiji port kabuka pikeun Firebase handapeun konci server jeung ngadangukeun acara di subtree na.

  • nelepon server recvfrom(), narima pakét datang ti host mana wae dina port ieu.

Dina hal urang, urang kudu pariksa antrian asup pakét dikirim ka port ieu.

Unggal port boga antrian sorangan, sarta kami nambahkeun sumber jeung palabuhan tujuan ka awal WebRTC datagrams ku kituna urang nyaho antrian mana anu diteruskeun nalika pakét anyar datang.

Teleponna henteu meungpeuk, janten upami teu aya pakét, urang ngan ukur uih deui -1 sareng nyetél errno=EWOULDBLOCK.

  • klien nu narima IP na port of server ku sababaraha cara éksternal, sarta nelepon sendto(). Ieu oge nelepon internal. bind(), kituna saterusna recvfrom() bakal nampa respon tanpa eksplisit executing bind.

Dina kasus urang, klien externally narima konci string sarta ngagunakeun fungsi resolve() pikeun meunangkeun alamat IP.

Dina titik ieu, urang initiate sasalaman WebRTC lamun dua peers teu acan disambungkeun ka silih. Sambungan ka palabuhan béda tina peer sarua ngagunakeun WebRTC DataChannel sarua.

Urang ogé ngalakukeun teu langsung bind()supados server tiasa nyambung deui dina salajengna sendto() bisi ditutup pikeun sababaraha alesan.

Server dibéjakeun ngeunaan sambungan klien nalika klien nulis tawaran SDP na handapeun informasi port server di Firebase, sarta server responds kalawan respon na aya.

Diagram di handap nembongkeun conto aliran pesen pikeun skéma stop kontak jeung pangiriman pesen munggaran ti klien ka server:

Porting kaulinan multiplayer ti C ++ ka web kalawan Cheerp, WebRTC na Firebase
Diagram lengkep tina fase sambungan antara klien sareng server

kacindekan

Upami anjeun parantos maca dugi ka ieu, anjeun panginten resep ningali téori dina tindakan. Kaulinan bisa dicoo dina teeworlds.leaningtech.com, cobian!


Pertandingan Friendly antara batur sapagawean

Kode perpustakaan jaringan sadia kalawan bébas di Github. Miluan paguneman dina saluran kami di Pait!

sumber: www.habr.com

Tambahkeun komentar