Bi Cheerp, WebRTC û Firebase lîstikek pirlîstikvan ji C++ veguhezîne ser malperê

Pîrozbahiyê

şîrketa me Leaning Technologies Ji bo veguheztina sepanên sermaseya kevneşopî li ser tevneyê çareseriyan peyda dike. Berhevkarê me yê C++ şahî Têkiliyek WebAssembly û JavaScript-ê diafirîne, ku her du jî peyda dike pêwendiya gerokê ya hêsan, û performansa bilind.

Wekî mînakek serîlêdana wê, me biryar da ku lîstikek pir-lîstikvan li ser malperê derxînin û hilbijart Teeworlds. Teeworlds lîstikek retro ya XNUMXD-ya piralî ye ku bi civatek piçûk lê çalak a lîstikvanan re (ez jî tê de!). Ew hem ji hêla çavkaniyên dakêşandî û hem jî ji hêla pêdiviyên CPU û GPU ve piçûk e - berendamek îdeal.

Bi Cheerp, WebRTC û Firebase lîstikek pirlîstikvan ji C++ veguhezîne ser malperê
Di geroka Teeworlds de dixebite

Me biryar da ku em vê projeyê ji bo ceribandinê bikar bînin çareseriyên giştî ji bo veguheztina koda torê li ser malperê. Ev bi gelemperî bi awayên jêrîn pêk tê:

  • XMLHttpDaxwaz/bigirin, heke beşa torê tenê ji daxwazên HTTP pêk tê, an
  • soketên webê.

Her du çareserî pêdivî ye ku meriv beşek serverê li ser milê serverê mêvandar bike, û ne jî destûr nade ku wekî protokola veguheztinê were bikar anîn UDP. Ev ji bo serîlêdanên rast-demê yên wekî nermalava konfêransa vîdyoyê û lîstikan girîng e, ji ber ku ew radestkirin û fermana pakêtên protokolê garantî dike. TCP dibe ku bibe asteng ji bo derengiya kêm.

Rêyek sêyemîn heye - torê ji gerokê bikar bînin: WebRTC.

RTCDataChannel Ew hem veguheztina pêbawer û ne pêbawer piştgirî dike (di doza paşîn de ew hewl dide ku gava ku gengaz be UDP wekî protokola veguhastinê bikar bîne), û dikare hem bi serverek dûr û hem jî di navbera gerokan de were bikar anîn. Ev tê vê wateyê ku em dikarin hemî serîlêdanê li gerokê, tevî pêkhateya serverê, bar bikin!

Lêbelê, ev bi dijwariyek din re tê: berî ku du heval WebRTC karibin bi hev re têkilî daynin, ew hewce ne ku ji bo girêdanê destanek tevlihev a tevlihev pêk bînin, ku çend saziyên sêyemîn hewce dike (pêşkêşkerek nîşankirinê û yek an bêtir pêşkêşker STUN/ZÎVIR).

Bi îdeal, em dixwazin API-yek torê biafirînin ku WebRTC di hundurê xwe de bikar tîne, lê bi qasî ku pêkan nêzîkê navbeynkarek UDP Sockets e ku ne hewce ye ku pêwendiyek saz bike.

Ev ê dihêle ku em ji WebRTC sûd werbigirin bêyî ku em hûrguliyên tevlihev li ser koda serîlêdanê derxînin (ya ku me dixwest di projeya xwe de bi qasî ku gengaz biguhezînin).

Kêmtirîn WebRTC

WebRTC komek API-yan e ku di gerokan de peyda dibe ku veguheztina peer-to-peer ya deng, vîdyo û daneyên keyfî peyda dike.

Têkiliya di navbera hevalan de tê saz kirin (tevî ku li yek an her du aliyan NAT hebe) bi karanîna serverên STUN û/an TURN bi navgîniya mekanîzmaya ku jê re ICE tê gotin. Heval bi pêşniyar û bersiva protokola SDP-ê agahdariya ICE û parametreyên kanalê diguhezînin.

Wow! Di carekê de çend kurtasî? Ka em bi kurtî rave bikin ka wateya van peyvan çi ye:

  • Karûbarên Veguhastina Rûniştinê ji bo NAT (STUN) - protokolek ji bo derbaskirina NAT û bidestxistina cotek (IP, port) ji bo danûstandina daneyan rasterast bi mêvandar re. Ger ew karibe peywira xwe biqedîne, wê hingê heval dikarin serbixwe daneyan bi hev re biguhezînin.
  • Veguheztin Bikaranîna Relayên li dora NAT (ZÎVIR) di heman demê de ji bo derbasbûna NAT-ê jî tê bikar anîn, lê ew vê yekê bi şandina daneyan bi navgînek ku ji her du hevalan re xuya ye pêk tîne. Ew derengiyê zêde dike û ji STUN-ê bihatir e (ji ber ku ew di tevahiya danişîna ragihandinê de tê sepandin), lê carinan ew vebijarka yekane ye.
  • Damezrandina Têkiliya Înteraktîf (QEŞA) ji bo hilbijartina rêbaza çêtirîn a gengaz a girêdana du hevalan li ser bingeha agahdariya ku ji rasterast girêdana hevalan hatî wergirtin, û her weha agahdariya ku ji hêla hejmarek serverên STUN û TURN ve hatî wergirtin hilbijêrin.
  • Protokola Danasîna Danişînê (SDP) formatek e ji bo danasîna pîvanên kanala girêdanê, wek nimûne, berendamên ICE, kodekên multimedia (di doza kanalek dengî/vîdyoyê de), hwd... Yek ji hevalan Pêşniyarek SDP dişîne, û yê duyemîn bi Bersiva SDP bersiv dide. .. Piştî vê yekê, kanalek tê çêkirin.

Ji bo afirandina têkiliyek wusa, pev re hewce ye ku agahdariya ku ji serverên STUN û TURN werdigirin berhev bikin û bi hev re biguherînin.

Pirsgirêk ev e ku ew hîn ne xwediyê şiyana ku rasterast danûstendinê bikin, ji ber vê yekê pêdivî ye ku mekanîzmayek derveyî-bandê hebe ku van daneyan biguhezîne: serverek îşaretkirinê.

Pêşkêşkarek îşaretkirinê dikare pir hêsan be ji ber ku karê wê tenê şandina daneyan di navbera hevalan de di qonaxa destanan de ye (wek ku di xêza jêrîn de tê destnîşan kirin).

Bi Cheerp, WebRTC û Firebase lîstikek pirlîstikvan ji C++ veguhezîne ser malperê
Diyagrama rêza destanên WebRTC ya hêsankirî

Teeworlds Network Pêşniyara Model

Mîmariya torê ya Teeworlds pir hêsan e:

  • Pêkhateyên xerîdar û server du bernameyên cûda ne.
  • Xerîdar bi girêdana bi yek ji çend serveran re, ku her yek di demekê de tenê lîstikek mêvandar dike, dikevin lîstikê.
  • Hemî veguheztina daneya di lîstikê de bi serverê ve tête kirin.
  • Serverek masterê ya taybetî tê bikar anîn da ku navnîşek hemî pêşkêşkerên gelemperî yên ku di muwekîlê lîstikê de têne xuyang kirin berhev bike.

Spas ji karanîna WebRTC-ê ji bo danûstendina daneyê, em dikarin hêmana servera lîstikê veguhezînin geroka ku xerîdar lê ye. Ev derfetek mezin dide me…

Ji pêşkêşkeran xilas bibin

Kêmasiya mantiqa serverê xwedan avantajek xweş e: em dikarin tevahiya serîlêdanê wekî naverokek statîk li ser Rûpelên Github an li ser hardware ya xweya li pişt Cloudflare bicîh bikin, bi vî rengî dakêşanên bilez û dema bilind belaş belaş peyda bikin. Bi rastî, em dikarin wan ji bîr bikin, û ger em bextewar bin û lîstik populer bibe, wê hingê pêdivî ye ku binesaziyê nûjen nebe.

Lêbelê, ji bo ku pergalê bixebite, em hîn jî pêdivî ye ku mîmariyek derveyî bikar bînin:

  • Yek an bêtir pêşkêşkerên STUN: Gelek vebijarkên me yên belaş hene ku ji wan hilbijêrin.
  • Bi kêmanî yek serverek TURN: Li vir vebijarkên belaş tune, ji ber vê yekê em dikarin xweya xwe saz bikin an ji bo karûbarê bidin. Xweşbextane, pir caran pêwend dikare bi serverên STUN ve were saz kirin (û p2p rast peyda bike), lê TURN wekî vebijarkek paşverû hewce ye.
  • Pêşkêşkara îşaretkirinê: Berevajî her du aliyên din, îşaretkirin ne standardkirî ye. Tiştê ku servera nîşankirinê dê bi rastî berpirsiyar be hinekî bi serîlêdanê ve girêdayî ye. Di rewşa me de, berî ku têkiliyek were saz kirin, pêdivî ye ku meriv hejmarek piçûk daneyê biguhezîne.
  • Teeworlds Master Server: Ew ji hêla serverên din ve tê bikar anîn da ku hebûna xwe reklam bikin û ji hêla xerîdar ve ji bo peydakirina serverên gelemperî tê bikar anîn. Digel ku ew ne hewce ye (mişterî her gav dikarin bi serverek ku bi destan pê dizanin ve girêbidin), dê xweş be ku hebe da ku lîstikvan karibin beşdarî lîstikên bi mirovên rasthatî bibin.

Me biryar da ku em serverên STUN-ên belaş ên Google-ê bikar bînin, û serverek TURN bixwe bi cih kirin.

Ji bo du xalên dawîn me bikar anîn Firebase:

  • Pêşkêşkara sereke ya Teeworlds pir hêsan tête bicîh kirin: wekî navnîşek tiştên ku agahdariya (nav, IP, nexşe, mod, ...) ya her serverek çalak vedihewîne. Pêşkêşker tişta xwe diweşînin û nûve dikin, û xerîdar tevahiya navnîşê digirin û ji lîstikvanê re nîşan didin. Em di heman demê de navnîşê li ser rûpelê malê wekî HTML-ê destnîşan dikin da ku lîstikvan bi hêsanî li ser serverê bikirtînin û rasterast werin lîstikê.
  • Signaling ji nêz ve bi pêkanîna soketên me ve girêdayî ye, ku di beşa paşîn de hatî destnîşan kirin.

Bi Cheerp, WebRTC û Firebase lîstikek pirlîstikvan ji C++ veguhezîne ser malperê
Lîsteya pêşkêşkerên li hundurê lîstikê û li ser rûpelê malê

Pêkanîna soketan

Em dixwazin API-yek ku bi qasî ku pêkan nêzî Posix UDP Sockets e biafirînin da ku hejmara guhertinên hewce kêm bike.

Di heman demê de em dixwazin ji bo danûstendina daneya herî hêsan a li ser torê kêmtirînên pêwîst bicîh bînin.

Mînakî, em ne hewceyî rêvekirina rastîn in: hemî heval li ser heman "LAN-ya virtual" ne ku bi mînakek databasa Firebase ya taybetî ve girêdayî ye.

Ji ber vê yekê, em ne hewceyî navnîşanên IP-ya yekta ne: Nirxên mifteya Firebase yên yekta (wek navên domainê) ji bo yekta nasîna hevalan bes in, û her peer bi herêmî navnîşanên IP-yê "sexte" ji her mifteya ku divê were wergerandin re destnîşan dike. Ev bi tevahî hewcedariya peywirdarkirina navnîşana IP-ya gerdûnî, ku karekî ne-pîwan e, ji holê radike.

Li vir herî kêm API-ya ku divê em bicîh bikin heye:

// 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 sade û mîna Posix Sockets API ye, lê çend cûdahiyên girîng hene: têketina bangên paşveçûnê, destnîşankirina IP-yên herêmî, û girêdanên lal.

Tomarkirina Callbacks

Her çend bernameya orîjînal I/O-ya ne-astengker bikar bîne jî, pêdivî ye ku kod ji nû ve were nûve kirin da ku di gerokek webê de bixebite.

Sedema vê yekê ev e ku lûleya bûyerê ya di gerokê de ji bernameyê veşartiye (bi JavaScript an WebAssembly be).

Di hawîrdora xwemalî de em dikarin kodek weha binivîsin

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

Ger lûleya bûyerê ji me re veşartî ye, wê hingê pêdivî ye ku em wê veguherînin tiştek mîna vê:

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

Peywira IP ya herêmî

Nasnameyên girêkên di "tora" me de ne navnîşanên IP-yê ne, lê mifteyên Firebase ne (ew rêzikên ku bi vî rengî xuya dikin in: -LmEC50PYZLCiCP-vqde ).

Ev rehet e ji ber ku em ne hewceyî mekanîzmayek ji bo veqetandina IP-yan û kontrolkirina yekta wan (û her weha rakirina wan piştî qutbûna xerîdar) ne, lê bi gelemperî hewce ye ku hevalan bi nirxek jimareyî nas bikin.

Ya ku fonksiyonan ji bo vê yekê têne bikar anîn ev e. resolve и reverseResolve: Serlêdan bi rengekî nirxa rêza mifteyê (bi navgîniya têketina bikarhêner an bi riya servera sereke) distîne û dikare wê ji bo karanîna hundurîn veguhezîne navnîşanek IP-yê. API-ya mayî jî li şûna rêzek ji bo sadebûnê vê nirxê distîne.

Ev mîna lêgerîna DNS-ê ye, lê li ser muwekîlê herêmî pêk tê.

Ango, navnîşanên IP-yê di navbera xerîdarên cûda de nayên parve kirin, û heke celebek nasnameyek gerdûnî hewce be, ew ê bi rengek cûda were çêkirin.

Girêdana lazy

UDP ne hewceyê pêwendiyê ye, lê wekî ku me dît, WebRTC pêvajoyek pêwendiyek dirêj hewce dike berî ku ew dest bi veguheztina daneyan di navbera du hevalan de bike.

Ger em dixwazin heman astê abstrakasyonê peyda bikin, (sendto/recvfrom bi hevalên keyfî bêyî girêdana pêşîn), wê hingê divê ew di hundurê API-ê de pêwendiyek "tebel" (dereng) pêk bînin.

Ya ku di dema ragihandina normal a di navbera "server" û "muwekîlê" de dema ku UDP bikar tîne diqewime, û ya ku divê pirtûkxaneya me bike:

  • Server bang dike bind()ji pergala xebitandinê re bêje ku ew dixwaze pakêtan li ser porta diyarkirî bistîne.

Di şûna wê de, em ê portek vekirî ya Firebase di bin mifteya serverê de biweşînin û li bûyeran di bindara wê de guhdarî bikin.

  • Server bang dike recvfrom(), pakêtên ku ji her mêvandarê li ser vê portê têne qebûl kirin.

Di doza me de, pêdivî ye ku em rêza hatina pakêtên ku ji vê portê re hatine şandin kontrol bikin.

Her portek rêza xwe heye, û em portên çavkanî û meqsedê li destpêka datagramên WebRTC zêde dikin da ku em zanibin dema ku pakêtek nû hat kîjan rêzê bişopînin.

Bang ne-asteng e, ji ber vê yekê heke pakêt tune bin, em tenê -1 vedigerin û destnîşan dikin errno=EWOULDBLOCK.

  • Xerîdar IP û porta serverê bi hin awayên derveyî werdigire, û bang dike sendto(). Ev jî bangeke navxweyî dike. bind(), ji ber vê yekê paşê recvfrom() dê bersivê bêyî pêkanîna girêdanek eşkere bistînin.

Di rewşa me de, xerîdar ji derve mifteya rêzê distîne û fonksiyonê bikar tîne resolve() ji bo bidestxistina navnîşana IP-ê.

Di vê nuqteyê de, heke her du heval hîn bi hevûdu ve girêdayî nebin, em destekek WebRTC didin destpêkirin. Girêdanên bi benderên cihêreng ên heman peer re heman WebRTC DataChannel bikar tînin.

Em nerasterast jî pêk tînin bind()da ku server di paşerojê de ji nû ve were girêdan sendto() eger ew ji ber hin sedeman girtî.

Dema ku xerîdar pêşniyara xwe ya SDP di binê agahdariya porta serverê de li Firebase dinivîse, server ji pêwendiya xerîdar tê agahdar kirin, û server li wir bi bersiva xwe re bersivê dide.

Diagrama jêrîn mînakek herikîna peyamê ya ji bo nexşeyek soket û veguheztina peyama yekem ji xerîdar ji serverê re nîşan dide:

Bi Cheerp, WebRTC û Firebase lîstikek pirlîstikvan ji C++ veguhezîne ser malperê
Diagrama bêkêmasî ya qonaxa pêwendiyê di navbera xerîdar û serverê de

encamê

Ger we heya nuha xwendibe, dibe ku hûn bala xwe bidin ku teoriyê di çalakiyê de bibînin. Lîstik dikare li ser were lîstin teeworlds.leaningtech.com, biceribîne!


Maça hevaltî di navbera hevalan de

Koda pirtûkxaneya torê bi serbestî li wir heye Github. Tevlî sohbeta li ser kanala me bibin Gitter!

Source: www.habr.com

Add a comment