KÄ tÄs pielietojuma piemÄru mÄs nolÄmÄm vairÄku spÄlÄtÄju spÄli portÄt tÄ«meklÄ« un izvÄlÄjÄmies Teeworlds. Teeworlds ir vairÄku spÄlÄtÄju XNUMXD retro spÄle ar nelielu, bet aktÄ«vu spÄlÄtÄju kopienu (tostarp es!). Tas ir mazs gan lejupielÄdÄto resursu, gan CPU un GPU prasÄ«bu ziÅÄ ā ideÄls kandidÄts.
Darbojas pÄrlÅ«kprogrammÄ Teeworlds
MÄs nolÄmÄm izmantot Å”o projektu, lai eksperimentÄtu vispÄrÄ«gi risinÄjumi tÄ«kla koda pÄrneÅ”anai uz tÄ«mekli. To parasti veic Å”Ädos veidos:
XMLHttpRequest/fetch, ja tÄ«kla daļa sastÄv tikai no HTTP pieprasÄ«jumiem, vai
WebSockets.
Abiem risinÄjumiem ir nepiecieÅ”ama servera komponenta mitinÄÅ”ana servera pusÄ, un neviens no tiem neļauj izmantot kÄ transporta protokolu UDP. Tas ir svarÄ«gi reÄllaika lietojumprogrammÄm, piemÄram, videokonferenÄu programmatÅ«rai un spÄlÄm, jo āātas garantÄ protokolu pakeÅ”u piegÄdi un secÄ«bu. TCP var kļūt par ŔķÄrsli zemam latentumam.
Ir treÅ”ais veids - izmantojiet tÄ«klu no pÄrlÅ«kprogrammas: WebRTC.
RTCDataChannel TÄ atbalsta gan uzticamu, gan neuzticamu pÄrraidi (pÄdÄjÄ gadÄ«jumÄ tÄ mÄÄ£ina izmantot UDP kÄ transporta protokolu, kad vien iespÄjams), un to var izmantot gan ar attÄlo serveri, gan starp pÄrlÅ«kprogrammÄm. Tas nozÄ«mÄ, ka mÄs varam pÄrnest visu lietojumprogrammu uz pÄrlÅ«kprogrammu, ieskaitot servera komponentu!
TomÄr tas rada papildu grÅ«tÄ«bas: pirms divi WebRTC lÄ«dzinieki var sazinÄties, tiem ir jÄveic salÄ«dzinoÅ”i sarežģīts rokasspiediens, lai izveidotu savienojumu, kam nepiecieÅ”amas vairÄkas treÅ”Äs puses entÄ«tijas (signalizÄcijas serveris un viens vai vairÄki serveri ApdullinÄt/Pagrieziens).
IdeÄlÄ gadÄ«jumÄ mÄs vÄlÄtos izveidot tÄ«kla API, kas iekÅ”Äji izmanto WebRTC, bet ir pÄc iespÄjas tuvÄk UDP Sockets saskarnei, kurai nav jÄizveido savienojums.
Tas ļaus mums izmantot WebRTC priekÅ”rocÄ«bas, nepakļaujot sarežģītas detaļas lietojumprogrammas kodam (ko mÄs savÄ projektÄ vÄlÄjÄmies mainÄ«t pÄc iespÄjas mazÄk).
MinimÄlais WebRTC
WebRTC ir pÄrlÅ«kprogrammÄs pieejama API kopa, kas nodroÅ”ina vienÄdranga audio, video un patvaļīgu datu pÄrraidi.
Savienojums starp vienaudžiem tiek izveidots (pat ja vienÄ vai abÄs pusÄs ir NAT), izmantojot STUN un/vai TURN serverus, izmantojot mehÄnismu, ko sauc par ICE. VienÄdrangi apmainÄs ar ICE informÄciju un kanÄlu parametriem, izmantojot SDP protokola piedÄvÄjumu un atbildi.
Oho! Cik saÄ«sinÄjumu vienlaikus? ÄŖsi paskaidrosim, ko nozÄ«mÄ Å”ie termini:
Sesijas apceļoÅ”anas utilÄ«tas NAT (ApdullinÄt) ā protokols NAT apieÅ”anai un pÄra (IP, porta) iegÅ«Å”anai datu apmaiÅai tieÅ”i ar resursdatoru. Ja viÅam izdodas paveikt savu uzdevumu, vienaudži var patstÄvÄ«gi apmainÄ«ties ar datiem savÄ starpÄ.
PÄreja, izmantojot relejus ap NAT (Pagrieziens) tiek izmantots arÄ« NAT ŔķÄrsoÅ”anai, taÄu tas tiek Ä«stenots, pÄrsÅ«tot datus caur starpniekserveri, kas ir redzams abiem vienaudžiem. Tas palielina latentumu, un to ievieÅ”ana ir dÄrgÄka nekÄ STUN (jo tas tiek lietots visas saziÅas sesijas laikÄ), taÄu dažreiz tÄ ir vienÄ«gÄ iespÄja.
InteraktÄ«vas savienojamÄ«bas izveide (ICE) izmanto, lai izvÄlÄtos labÄko iespÄjamo metodi divu vienaudžu savienoÅ”anai, pamatojoties uz informÄciju, kas iegÅ«ta no vienaudžiem tieÅ”i, kÄ arÄ« informÄciju, ko saÅem jebkurÅ” STUN un TURN serveru skaits.
Sesijas apraksta protokols (SDP) ir formÄts savienojuma kanÄla parametru aprakstÄ«Å”anai, piemÄram, ICE kandidÄti, multivides kodeki (audio/video kanÄla gadÄ«jumÄ) utt... Viens no vienaudžiem nosÅ«ta SDP piedÄvÄjumu, bet otrs atbild ar SDP Answer .. PÄc tam tiek izveidots kanÄls.
Lai izveidotu Å”Ädu savienojumu, vienaudžiem ir jÄapkopo informÄcija, ko viÅi saÅem no STUN un TURN serveriem, un jÄapmainÄs ar to savÄ starpÄ.
ProblÄma ir tÄ, ka viÅiem vÄl nav iespÄjas tieÅ”i sazinÄties, tÄpÄc Å”o datu apmaiÅai ir jÄbÅ«t Ärpusjoslas mehÄnismam: signalizÄcijas serverim.
SignalizÄcijas serveris var bÅ«t ļoti vienkÄrÅ”s, jo tÄ vienÄ«gais uzdevums ir pÄrsÅ«tÄ«t datus starp vienaudžiem ārokasspiedienaā fÄzes laikÄ (kÄ parÄdÄ«ts tÄlÄk esoÅ”ajÄ diagrammÄ).
Teeworlds tÄ«kla arhitektÅ«ra ir ļoti vienkÄrÅ”a:
Klienta un servera komponenti ir divas dažÄdas programmas.
Klienti spÄlÄ ievadiet savienojumu ar vienu no vairÄkiem serveriem, no kuriem katrs vienlaikus mitina tikai vienu spÄli.
Visa spÄles datu pÄrsÅ«tÄ«Å”ana tiek veikta caur serveri.
ÄŖpaÅ”s galvenais serveris tiek izmantots, lai apkopotu visu publisko serveru sarakstu, kas tiek parÄdÄ«ti spÄles klientÄ.
Pateicoties WebRTC izmantoÅ”anai datu apmaiÅai, mÄs varam pÄrsÅ«tÄ«t spÄles servera komponentu uz pÄrlÅ«kprogrammu, kurÄ atrodas klients. Tas mums sniedz lielisku iespÄju...
Atbrīvojieties no serveriem
Servera loÄ£ikas trÅ«kumam ir jauka priekÅ”rocÄ«ba: mÄs varam izvietot visu lietojumprogrammu kÄ statisku saturu Github Pages vai mÅ«su paÅ”u aparatÅ«rÄ aiz Cloudflare, tÄdÄjÄdi nodroÅ”inot Ätru lejupielÄdi un ilgu darbÄ«bas laiku bez maksas. PatiesÄ«bÄ par tiem varam aizmirst, un, ja paveiksies un spÄle kļūs populÄra, tad infrastruktÅ«ra nebÅ«s jÄmodernizÄ.
TomÄr, lai sistÄma darbotos, mums joprojÄm ir jÄizmanto ÄrÄja arhitektÅ«ra:
Viens vai vairÄki STUN serveri: mums ir vairÄkas bezmaksas iespÄjas, no kurÄm izvÄlÄties.
Vismaz viens TURN serveris: Å”eit nav bezmaksas iespÄju, tÄpÄc mÄs varam iestatÄ«t paÅ”i vai samaksÄt par pakalpojumu. Par laimi, lielÄko daļu laika savienojumu var izveidot, izmantojot STUN serverus (un nodroÅ”inÄt patiesu p2p), taÄu TURN ir nepiecieÅ”ama kÄ rezerves opcija.
SignalizÄcijas serveris: atŔķirÄ«bÄ no pÄrÄjiem diviem aspektiem signalizÄcija nav standartizÄta. Tas, par ko patiesÄ«bÄ bÅ«s atbildÄ«gs signalizÄcijas serveris, ir nedaudz atkarÄ«gs no lietojumprogrammas. MÅ«su gadÄ«jumÄ pirms savienojuma izveides ir nepiecieÅ”ams apmainÄ«ties ar nelielu datu apjomu.
Teeworlds Master Server: to izmanto citi serveri, lai reklamÄtu savu esamÄ«bu, un klienti, lai atrastu publiskos serverus. Lai gan tas nav nepiecieÅ”ams (klienti vienmÄr var izveidot savienojumu ar serveri, par kuru viÅi zina manuÄli), bÅ«tu jauki, ja spÄlÄtÄji varÄtu piedalÄ«ties spÄlÄs ar nejauÅ”iem cilvÄkiem.
MÄs nolÄmÄm izmantot Google bezmaksas STUN serverus un paÅ”i izvietojÄm vienu TURN serveri.
Par pÄdÄjiem diviem punktiem mÄs izmantojÄm Firebase:
Teeworlds galvenais serveris ir ieviests ļoti vienkÄrÅ”i: kÄ objektu saraksts, kas satur informÄciju (nosaukums, IP, karte, režīms, ...) par katru aktÄ«vÄ servera. Serveri publicÄ un atjaunina savu objektu, un klienti Åem visu sarakstu un parÄda to atskaÅotÄjam. MÄs arÄ« parÄdÄm sarakstu sÄkumlapÄ HTML formÄtÄ, lai spÄlÄtÄji varÄtu vienkÄrÅ”i noklikŔķinÄt uz servera un tikt novirzÄ«ti tieÅ”i uz spÄli.
SignalizÄcija ir cieÅ”i saistÄ«ta ar mÅ«su kontaktligzdu ievieÅ”anu, kas aprakstÄ«ta nÄkamajÄ sadaļÄ.
Serveru saraksts spÄles iekÅ”ienÄ un mÄjaslapÄ
Kontaktligzdu ievieŔana
MÄs vÄlamies izveidot API, kas ir pÄc iespÄjas tuvÄk Posix UDP Sockets, lai samazinÄtu nepiecieÅ”amo izmaiÅu skaitu.
TÄpat vÄlamies ieviest nepiecieÅ”amo minimumu vienkÄrÅ”Äkai datu apmaiÅai tÄ«klÄ.
PiemÄram, mums nav nepiecieÅ”ama reÄla marÅ”rutÄÅ”ana: visi lÄ«dzÄ«gie ir tajÄ paÅ”Ä "virtuÄlajÄ LAN", kas saistÄ«ts ar konkrÄtu Firebase datu bÄzes gadÄ«jumu.
TÄpÄc mums nav vajadzÄ«gas unikÄlas IP adreses: unikÄlas Firebase atslÄgas vÄrtÄ«bas (lÄ«dzÄ«gas domÄna nosaukumiem) ir pietiekamas, lai unikÄli identificÄtu lÄ«dziniekus, un katrs partneris lokÄli pieŔķir āviltusā IP adreses katrai atslÄgai, kas jÄtulko. Tas pilnÄ«bÄ novÄrÅ” globÄlÄs IP adreses pieŔķirÅ”anas nepiecieÅ”amÄ«bu, kas ir nenozÄ«mÄ«gs uzdevums.
TÄlÄk ir norÄdÄ«ts minimÄlais API, kas mums jÄievieÅ”:
// 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 ir vienkÄrÅ”a un lÄ«dzÄ«ga Posix Sockets API, taÄu tai ir dažas svarÄ«gas atŔķirÄ«bas: Atzvanu reÄ£istrÄÅ”ana, vietÄjo IP pieŔķirÅ”ana un slinki savienojumi.
Atzvanu reÄ£istrÄÅ”ana
Pat ja sÄkotnÄjÄ programma izmanto nebloÄ·ÄjoÅ”u I/O, kods ir jÄpÄrveido, lai tas darbotos tÄ«mekļa pÄrlÅ«kprogrammÄ.
Iemesls tam ir tas, ka notikumu cilpa pÄrlÅ«kprogrammÄ ir paslÄpta no programmas (vai tÄ bÅ«tu JavaScript vai WebAssembly).
VietÄjÄ vidÄ mÄs varam rakstÄ«t Å”Ädu kodu
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;
...
}
...
}
Ja notikuma cikls mums ir paslÄpts, mums tas jÄpÄrvÄrÅ” par kaut ko lÄ«dzÄ«gu:
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
VietÄjÄ IP pieŔķirÅ”ana
MÅ«su "tÄ«kla" mezglu ID nav IP adreses, bet gan Firebase atslÄgas (tÄs ir virknes, kas izskatÄs Å”Ädi: -LmEC50PYZLCiCP-vqde ).
Tas ir Ärti, jo mums nav nepiecieÅ”ams mehÄnisms IP pieŔķirÅ”anai un to unikalitÄtes pÄrbaudei (kÄ arÄ« atbrÄ«voÅ”anai no tiem pÄc klienta atvienoÅ”anas), bet bieži vien ir nepiecieÅ”ams identificÄt lÄ«dziniekus pÄc skaitliskÄs vÄrtÄ«bas.
TieÅ”i Å”im nolÅ«kam Ŕīs funkcijas tiek izmantotas. resolve Šø reverseResolve: Lietojumprogramma kaut kÄdÄ veidÄ saÅem atslÄgas virknes vÄrtÄ«bu (izmantojot lietotÄja ievadi vai galveno serveri) un var pÄrvÄrst to par IP adresi iekÅ”Äjai lietoÅ”anai. ArÄ« pÄrÄjÄ API daļa vienkÄrŔības labad saÅem Å”o vÄrtÄ«bu, nevis virkni.
Tas ir lÄ«dzÄ«gs DNS uzmeklÄÅ”anai, bet tiek veikts lokÄli klientam.
Tas ir, IP adreses nevar koplietot starp dažÄdiem klientiem, un, ja ir nepiecieÅ”ams kaut kÄds globÄlais identifikators, tas bÅ«s jÄÄ£enerÄ citÄ veidÄ.
Slinks savienojums
UDP nav nepiecieÅ”ams savienojums, taÄu, kÄ mÄs redzÄjÄm, WebRTC ir nepiecieÅ”ams ilgstoÅ”s savienojuma process, pirms tas var sÄkt datu pÄrsÅ«tÄ«Å”anu starp diviem vienaudžiem.
Ja mÄs vÄlamies nodroÅ”inÄt tÄdu paÅ”u abstrakcijas lÄ«meni, (sendto/recvfrom ar patvaļīgiem vienaudžiem bez iepriekÅ”Äja savienojuma), tad tiem API iekÅ”ienÄ ir jÄveic āslinksā (aizkavÄts) savienojums.
LÅ«k, kas notiek parastas saziÅas laikÄ starp āserveriā un āklientuā, izmantojot UDP, un tas, kas jÄdara mÅ«su bibliotÄkai:
TÄ vietÄ mÄs publicÄsim atvÄrtu portu uz Firebase zem servera atslÄgas un klausÄ«simies notikumus tÄ apakÅ”kokÄ.
Servera zvani recvfrom(), pieÅemot paketes, kas nÄk no jebkura saimniekdatora Å”ajÄ portÄ.
MÅ«su gadÄ«jumÄ mums ir jÄpÄrbauda uz Å”o portu nosÅ«tÄ«to pakeÅ”u ienÄkoÅ”Ä rinda.
Katram portam ir sava rinda, un mÄs pievienojam avota un mÄrÄ·a portus WebRTC datagrammu sÄkumam, lai mÄs zinÄtu, uz kuru rindu pÄrsÅ«tÄ«t, kad pienÄk jauna pakete.
Zvans ir nebloÄ·ÄjoÅ”s, tÄpÄc, ja nav pakeÅ”u, mÄs vienkÄrÅ”i atgriežam -1 un iestatÄm errno=EWOULDBLOCK.
Klients saÅem servera IP un portu ar ÄrÄjiem lÄ«dzekļiem un zvana sendto(). Tas arÄ« veic iekÅ”Äju zvanu. bind(), tÄpÄc turpmÄk recvfrom() saÅems atbildi, nepÄrprotami neizpildot saistÄ«Å”anu.
MÅ«su gadÄ«jumÄ klients ÄrÄji saÅem virknes atslÄgu un izmanto funkciju resolve() lai iegÅ«tu IP adresi.
Å ajÄ brÄ«dÄ« mÄs uzsÄkam WebRTC rokasspiedienu, ja abi partneri vÄl nav savienoti viens ar otru. Savienojumi ar dažÄdiem viena un tÄ paÅ”a partnera portiem izmanto vienu un to paÅ”u WebRTC DataChannel.
MÄs veicam arÄ« netieÅ”u bind()lai serveris varÄtu atkÄrtoti izveidot savienojumu nÄkamajÄ sendto() ja kÄda iemesla dÄļ tas slÄgts.
Serveris tiek informÄts par klienta savienojumu, kad klients Firebase zem servera porta informÄcijas ieraksta savu SDP piedÄvÄjumu, un serveris atbild ar savu atbildi tur.
TÄlÄk redzamajÄ diagrammÄ ir parÄdÄ«ts ligzdas shÄmas ziÅojumu plÅ«smas piemÄrs un pirmÄ ziÅojuma pÄrsÅ«tÄ«Å”ana no klienta uz serveri:
PilnÄ«ga diagramma savienojuma fÄzei starp klientu un serveri
SecinÄjums
Ja esat izlasÄ«jis tik tÄlu, jÅ«s, iespÄjams, interesÄ teorija darbÄ«bÄ. SpÄli var spÄlÄt tÄlÄk teeworlds.leaningtech.com, PamÄÄ£ini!
DraudzÄ«bas spÄle starp kolÄÄ£iem
TÄ«kla bibliotÄkas kods ir brÄ«vi pieejams vietnÄ GitHub. Pievienojieties sarunai mÅ«su kanÄlÄ plkst skala!