Cheerp, WebRTC va Firebase yordamida ko'p o'yinchi o'yinini C++ dan internetga o'tkazish

kirish

Bizning kompaniyamiz Yo'naltirilgan texnologiyalar an'anaviy ish stoli ilovalarini Internetga ko'chirish uchun echimlarni taqdim etadi. Bizning C++ kompilyatorimiz quvnoq WebAssembly va JavaScript kombinatsiyasini yaratadi, bu ikkalasini ham ta'minlaydi oddiy brauzer o'zaro ta'siri, va yuqori ishlash.

Uning qo'llanilishiga misol sifatida biz ko'p o'yinchi o'yinini Internetga o'tkazishga qaror qildik va tanladik Teeworlds. Teeworlds - bu kichik, ammo faol o'yinchilar hamjamiyatiga ega bo'lgan ko'p o'yinchili 2D retro o'yini (shu jumladan men ham!). Yuklab olingan resurslar va CPU va GPU talablari jihatidan ham kichik - ideal nomzod.

Cheerp, WebRTC va Firebase yordamida ko'p o'yinchi o'yinini C++ dan internetga o'tkazish
Teeworlds brauzerida ishlaydi

Biz ushbu loyihani tajriba qilish uchun ishlatishga qaror qildik tarmoq kodini Internetga ko'chirish uchun umumiy echimlar. Bu odatda quyidagi usullarda amalga oshiriladi:

  • XMLHttpRequest/fetch, agar tarmoq qismi faqat HTTP so'rovlaridan iborat bo'lsa yoki
  • Veb -rozetkalar.

Ikkala yechim ham server komponentini server tomonida joylashtirishni talab qiladi va ikkalasi ham transport protokoli sifatida foydalanishga ruxsat bermaydi. UDP. Bu video konferentsiya dasturlari va o'yinlar kabi real vaqtda ilovalar uchun muhim, chunki u protokol paketlarini yetkazib berish va tartibini kafolatlaydi. TCP past kechikish uchun to'siq bo'lishi mumkin.

Uchinchi yo'l bor - brauzerdan tarmoqdan foydalaning: WebRTC.

RTCDataChannel U ishonchli va ishonchsiz uzatishni qo'llab-quvvatlaydi (ikkinchi holatda u imkon qadar transport protokoli sifatida UDP dan foydalanishga harakat qiladi) va masofaviy server bilan ham, brauzerlar o'rtasida ham foydalanish mumkin. Bu shuni anglatadiki, biz butun dasturni, jumladan, server komponentini brauzerga o'tkazishimiz mumkin!

Biroq, bu qo'shimcha qiyinchilik bilan birga keladi: ikkita WebRTC tengdoshlari muloqot qilishdan oldin, ulanish uchun nisbatan murakkab qo'l siqishini amalga oshirishlari kerak, bu esa bir nechta uchinchi tomon ob'ektlarini (signalizatsiya serveri va bir yoki bir nechta serverlar) talab qiladi. SUN/Qaytish).

Ideal holda, biz WebRTC-dan ichki foydalanadigan, lekin ulanishni o'rnatishni talab qilmaydigan UDP Sockets interfeysiga imkon qadar yaqin bo'lgan tarmoq API yaratmoqchimiz.

Bu bizga WebRTC-dan foydalanishga imkon beradi (biz loyihamizda iloji boricha kamroq o'zgartirmoqchi bo'lgan) dastur kodiga murakkab tafsilotlarni oshkor qilmasdan.

Minimal WebRTC

WebRTC - bu audio, video va o'zboshimchalik bilan ma'lumotlarni tengdoshga uzatishni ta'minlaydigan brauzerlarda mavjud bo'lgan API to'plami.

Tengdoshlar o'rtasidagi aloqa ICE deb nomlangan mexanizm orqali STUN va/yoki TURN serverlari yordamida (bir yoki ikkala tomonda NAT mavjud bo'lsa ham) o'rnatiladi. Tengdoshlar SDP protokolining taklifi va javobi orqali ICE ma'lumotlari va kanal parametrlarini almashadilar.

Voy-buy! Bir vaqtning o'zida nechta qisqartma? Keling, ushbu atamalar nimani anglatishini qisqacha tushuntirib beraylik:

  • NAT uchun sessiyalarni o'tkazish uchun yordamchi dasturlar (SUN) β€” NAT ni chetlab o'tish va xost bilan to'g'ridan-to'g'ri ma'lumot almashish uchun juftlik (IP, port) olish protokoli. Agar u o'z vazifasini bajara olsa, tengdoshlar bir-birlari bilan mustaqil ravishda ma'lumot almashishlari mumkin.
  • NAT atrofida o'rni yordamida o'tish (Qaytish) NAT o'tish uchun ham ishlatiladi, lekin u buni ikkala tengdoshga ko'rinadigan proksi-server orqali ma'lumotlarni uzatish orqali amalga oshiradi. U kechikish vaqtini oshiradi va uni amalga oshirish STUNga qaraganda qimmatroq (chunki u butun aloqa seansi davomida qo'llaniladi), lekin ba'zida bu yagona variant.
  • Interaktiv ulanishni o'rnatish (ICE) to'g'ridan-to'g'ri ulanishdan olingan ma'lumotlarga, shuningdek STUN va TURN serverlarining istalgan soni tomonidan olingan ma'lumotlarga asoslanib, ikkita tengdoshni ulashning eng yaxshi usulini tanlash uchun foydalaniladi.
  • Sessiya tavsifi protokoli (RDS) ulanish kanali parametrlarini tavsiflash uchun formatdir, masalan, ICE nomzodlari, multimedia kodeklari (audio/video kanali uchun) va hokazo... Tengdoshlardan biri SDP taklifini yuboradi, ikkinchisi esa SDP javobi bilan javob beradi. .. Shundan so'ng, kanal yaratiladi.

Bunday aloqani yaratish uchun tengdoshlar STUN va TURN serverlaridan olgan ma'lumotlarni yig'ib, bir-birlari bilan almashishlari kerak.

Muammo shundaki, ular hali to'g'ridan-to'g'ri muloqot qilish qobiliyatiga ega emaslar, shuning uchun bu ma'lumotlarni almashish uchun tarmoqdan tashqari mexanizm mavjud bo'lishi kerak: signalizatsiya serveri.

Signal serveri juda oddiy bo'lishi mumkin, chunki uning yagona vazifasi qo'l siqish bosqichida tengdoshlar o'rtasida ma'lumotlarni uzatishdir (quyidagi diagrammada ko'rsatilganidek).

Cheerp, WebRTC va Firebase yordamida ko'p o'yinchi o'yinini C++ dan internetga o'tkazish
Soddalashtirilgan WebRTC qoΚ»l siqish ketma-ketligi diagrammasi

Teeworlds tarmoq modeliga umumiy nuqtai

Teeworlds tarmoq arxitekturasi juda oddiy:

  • Mijoz va server komponentlari ikki xil dasturdir.
  • Mijozlar har birida bir vaqtning o'zida faqat bitta o'yinni o'z ichiga olgan bir nechta serverlardan biriga ulanish orqali o'yinga kirishadi.
  • O'yindagi barcha ma'lumotlarni uzatish server orqali amalga oshiriladi.
  • O'yin mijozida ko'rsatiladigan barcha umumiy serverlar ro'yxatini to'plash uchun maxsus master server ishlatiladi.

Ma'lumotlar almashinuvi uchun WebRTC-dan foydalanish tufayli biz o'yinning server komponentini mijoz joylashgan brauzerga o'tkazishimiz mumkin. Bu bizga katta imkoniyat beradi...

Serverlardan xalos bo'ling

Server mantig'ining yo'qligi yaxshi afzalliklarga ega: biz butun dasturni statik tarkib sifatida Github sahifalarida yoki Cloudflare orqasidagi o'z uskunamizda joylashtirishimiz mumkin, shu bilan tez yuklab olish va yuqori ish vaqtini bepul ta'minlaymiz. Aslida, biz ular haqida unutishimiz mumkin va agar omadimiz kelsa va o'yin mashhur bo'lib qolsa, unda infratuzilmani modernizatsiya qilish shart emas.

Biroq, tizim ishlashi uchun biz hali ham tashqi arxitekturadan foydalanishimiz kerak:

  • Bir yoki bir nechta STUN serverlari: Bizda tanlash uchun bir nechta bepul variantlar mavjud.
  • Hech bo'lmaganda bitta TURN serveri: bu yerda bepul variantlar yo'q, shuning uchun biz o'zimizni sozlashimiz yoki xizmat uchun to'lashimiz mumkin. Yaxshiyamki, ko'pincha ulanish STUN serverlari orqali o'rnatilishi mumkin (va haqiqiy p2p bilan ta'minlaydi), ammo TURN qayta variant sifatida kerak.
  • Signal serveri: Boshqa ikki jihatdan farqli o'laroq, signalizatsiya standartlashtirilmagan. Signal serveri aslida nima uchun javobgar bo'lishi dasturga bog'liq. Bizning holatda, ulanishni o'rnatishdan oldin, kichik hajmdagi ma'lumotlarni almashish kerak.
  • Teeworlds Master Server: U boshqa serverlar tomonidan ularning mavjudligini e'lon qilish va mijozlar tomonidan umumiy serverlarni topish uchun foydalaniladi. Bu talab qilinmasa ham (mijozlar har doim o'zlari biladigan serverga qo'lda ulanishi mumkin), o'yinchilar tasodifiy odamlar bilan o'yinlarda qatnashishi yaxshi bo'lar edi.

Biz Google'ning bepul STUN serverlaridan foydalanishga qaror qildik va o'zimiz bitta TURN serverini joylashtirdik.

Oxirgi ikki nuqta uchun biz foydalandik Firebase:

  • Teeworlds asosiy serveri juda sodda tarzda amalga oshiriladi: har bir faol serverning ma'lumotlarini (nomi, IP, xarita, rejim, ...) o'z ichiga olgan ob'ektlar ro'yxati sifatida. Serverlar o'zlarining ob'ektlarini nashr etadilar va yangilaydilar va mijozlar butun ro'yxatni olib, uni o'yinchiga ko'rsatadilar. Shuningdek, biz ro'yxatni bosh sahifada HTML sifatida ko'rsatamiz, shuning uchun o'yinchilar shunchaki serverni bosishlari va to'g'ridan-to'g'ri o'yinga o'tishlari mumkin.
  • Signalizatsiya keyingi bo'limda tasvirlangan rozetkalarni amalga oshirish bilan chambarchas bog'liq.

Cheerp, WebRTC va Firebase yordamida ko'p o'yinchi o'yinini C++ dan internetga o'tkazish
O'yin ichidagi va bosh sahifadagi serverlar ro'yxati

Soketlarni amalga oshirish

Biz kerakli o'zgarishlar sonini kamaytirish uchun Posix UDP soketlariga iloji boricha yaqinroq bo'lgan API yaratmoqchimiz.

Shuningdek, biz tarmoq orqali eng oddiy ma'lumotlar almashinuvi uchun zarur bo'lgan minimal miqdorni amalga oshirishni xohlaymiz.

Misol uchun, bizga haqiqiy marshrut kerak emas: barcha tengdoshlar ma'lum bir Firebase ma'lumotlar bazasi namunasi bilan bog'langan bir xil "virtual LAN"da.

Shuning uchun bizga noyob IP-manzillar kerak emas: noyob Firebase kalit qiymatlari (domen nomlariga o'xshash) tengdoshlarni noyob aniqlash uchun etarli va har bir tengdosh mahalliy ravishda tarjima qilinishi kerak bo'lgan har bir kalitga "soxta" IP-manzillarni tayinlaydi. Bu global IP-manzilni belgilash zaruratini butunlay yo'q qiladi, bu oddiy bo'lmagan vazifadir.

Mana biz amalga oshirishimiz kerak bo'lgan minimal 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 oddiy va Posix Sockets API-ga o'xshaydi, lekin bir nechta muhim farqlarga ega: qayta qo'ng'iroqlarni ro'yxatga olish, mahalliy IP-larni belgilash va dangasa ulanishlar.

Qayta qo'ng'iroqlarni ro'yxatdan o'tkazish

Dastlabki dastur bloklanmaydigan kiritish-chiqarishdan foydalansa ham, veb-brauzerda ishlash uchun kodni qayta tiklash kerak.

Buning sababi shundaki, brauzerdagi voqealar tsikli dasturdan yashiringan (JavaScript yoki WebAssembly).

Mahalliy muhitda biz shunday kod yozishimiz mumkin

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

Agar voqea tsikli biz uchun yashirin bo'lsa, biz uni shunga o'xshash narsaga aylantirishimiz kerak:

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

Mahalliy IP tayinlash

Bizning "tarmog'imiz" dagi tugun identifikatorlari IP manzillar emas, balki Firebase kalitlari (ular quyidagicha ko'rinadigan satrlardir: -LmEC50PYZLCiCP-vqde ).

Bu qulay, chunki bizga IP-larni belgilash va ularning o'ziga xosligini tekshirish (shuningdek, mijoz uzilganidan keyin ularni yo'q qilish) mexanizmi kerak emas, lekin ko'pincha raqamli qiymat bo'yicha tengdoshlarni aniqlash kerak bo'ladi.

Funktsiyalar aynan shu maqsadda qo'llaniladi. resolve ΠΈ reverseResolve: Ilova qandaydir tarzda kalitning satr qiymatini oladi (foydalanuvchi kiritish orqali yoki asosiy server orqali) va uni ichki foydalanish uchun IP-manzilga aylantirishi mumkin. APIning qolgan qismi ham soddaligi uchun satr o'rniga ushbu qiymatni oladi.

Bu DNS qidiruviga o'xshaydi, lekin mijozda mahalliy sifatida amalga oshiriladi.

Ya'ni, IP-manzillarni turli mijozlar o'rtasida bo'lishish mumkin emas va agar qandaydir global identifikator kerak bo'lsa, uni boshqa yo'l bilan yaratish kerak bo'ladi.

Yalang'och aloqa

UDP ulanishga muhtoj emas, lekin biz ko'rganimizdek, WebRTC ikki tengdosh o'rtasida ma'lumotlarni uzatishni boshlashdan oldin uzoq ulanish jarayonini talab qiladi.

Agar biz bir xil darajadagi mavhumlikni ta'minlamoqchi bo'lsak, (sendto/recvfrom oldindan ulanishsiz o'zboshimchalik bilan tengdoshlar bilan), keyin ular API ichida "dangasa" (kechiktirilgan) ulanishni amalga oshirishlari kerak.

UDP dan foydalanganda "server" va "mijoz" o'rtasidagi oddiy aloqa paytida nima sodir bo'ladi va kutubxonamiz nima qilishi kerak:

  • Server qo'ng'iroqlari bind()operatsion tizimga ko'rsatilgan portda paketlarni olishni xohlashini aytish.

Buning o'rniga, biz server kaliti ostida Firebase-ga ochiq portni nashr qilamiz va uning pastki daraxtidagi voqealarni tinglaymiz.

  • Server qo'ng'iroqlari recvfrom(), ushbu portdagi istalgan xostdan keladigan paketlarni qabul qilish.

Bizning holatda, biz ushbu portga yuborilgan paketlarning kiruvchi navbatini tekshirishimiz kerak.

Har bir portning o'z navbati bor va biz yangi paket kelganda qaysi navbatga yo'naltirish kerakligini bilishimiz uchun WebRTC datagrammalarining boshiga manba va maqsad portlarini qo'shamiz.

Qo'ng'iroq bloklanmaydi, shuning uchun paketlar bo'lmasa, biz shunchaki -1 ni qaytaramiz va o'rnatamiz errno=EWOULDBLOCK.

  • Mijoz serverning IP va portini ba'zi tashqi vositalar orqali oladi va qo'ng'iroq qiladi sendto(). Bu ichki qo'ng'iroqni ham amalga oshiradi. bind(), shuning uchun keyingi recvfrom() bog'lanishni aniq bajarmasdan javob oladi.

Bizning holatda mijoz string kalitni tashqaridan oladi va funksiyadan foydalanadi resolve() IP manzilini olish uchun.

Ushbu nuqtada, agar ikkala tengdosh hali bir-biriga ulanmagan bo'lsa, biz WebRTC qo'l siqishini boshlaymiz. Xuddi shu tengdoshning turli portlariga ulanishlar bir xil WebRTC DataChannel-dan foydalanadi.

Biz bilvosita ham bajaramiz bind()Shunday qilib, server keyingisida qayta ulanishi mumkin sendto() agar biron sababga ko'ra yopilsa.

Mijoz o'zining SDP taklifini Firebase'dagi server port ma'lumotlari ostida yozganda, server mijozning ulanishi haqida xabardor qilinadi va server o'z javobi bilan u erda javob beradi.

Quyidagi diagrammada rozetka sxemasi uchun xabarlar oqimi va mijozdan serverga birinchi xabarni uzatish misoli ko'rsatilgan:

Cheerp, WebRTC va Firebase yordamida ko'p o'yinchi o'yinini C++ dan internetga o'tkazish
Mijoz va server o'rtasidagi ulanish bosqichining to'liq diagrammasi

xulosa

Agar siz shu paytgacha o'qigan bo'lsangiz, ehtimol siz nazariyani amalda ko'rishga qiziqasiz. O'yinni o'ynash mumkin teeworlds.leaningtech.com, Urunib ko'r!


Hamkasblar o'rtasidagi o'rtoqlik uchrashuvi

Tarmoq kutubxonasi kodi bepul Github. Kanalimizdagi suhbatga qo'shiling Gitter!

Manba: www.habr.com

a Izoh qo'shish