Razvoj WebSocket aplikacija u realnom vremenu

WebSocket aplikacije drže jednu konekciju otvorenom, tako da server može odmah gurnuti poruku u chatu, promjenu cijene ili pomak kursora. Browser više ne pita „ima li šta novo?“ svakih nekoliko sekundi, a ekran koji dijeli nekoliko ljudi ostaje usaglašen bez osvježavanja.

U CodeWizu dizajniramo i gradimo taj živi sloj za web aplikacije, Laravel i Node.js backend, i interne alate. Trajni zapis i dalje živi u PostgreSQL-u, MySQL-u ili Redisu. Socket nosi događaje koji ne mogu čekati sljedeći zahtjev: novu poruku, promjenu prisutnosti, potez u igri, znak ukucan u zajednički dokument.

Usluga je za proizvode kojima treba chat ili živi feed aktivnosti, za operativne timove koji žele obavijesti u aplikaciji a ne samo u emailu, i za alate u kojima dvije osobe uređuju istu stvar odjednom. Ako interfejs kasni jer čeka na tajmer, trajna konekcija je obično dio koji nedostaje. Isti obrazac pokriva i internu tablu na kojoj dispečer vidi status naloga čim se promijeni, bez ručnog osvježavanja.

Kod teme aplikacije alati otvorenog koda su prvi izbor kada odgovaraju poslu.

Aplikacije: Osoba kuca poruku u chatu na pametnom telefonu pored ledene kafe

WebSocket aplikacije za chat u realnom vremenu

Chat izgleda mali dok se ista poruka ne pošalje dvaput na nestabilnoj mreži, privatna soba ne postane vidljiva tuđima ili restart ne obriše sačuvane poruke. Socket tretiramo kao živi rep razgovora čiji izvor istine ostaje baza podataka.

Aplikacije kreće od sistema koji već radi, a ne od praznog šablona.

Šta protokol mora riješiti

  • Svaka poruka ima id koji generiše klijent, tako da ponovni pokušaj nakon puknute konekcije ne ubaci duplikat.
  • Soba je granica autorizacije. Ulazak u sobu je provjerena operacija, a ne string koji klijent smije izmisliti.
  • Starije poruke učitavaju se običnim HTTP-om, po stranicama. Socket isporučuje samo ono što se desilo nakon povezivanja, plus kratko preklapanje da ništa ne upadne u rupu.
  • Prisutnost, kucanje i potvrde čitanja su prolazni. To nisu redovi koje vrijedi čuvati zauvijek, i ističu kada heartbeat stane.

Šta predajemo

  • Kratak katalog događaja: poruka, izmjena, brisanje, kucanje, prisutnost, pročitano. Svaki događaj ima payload i pravilo kome stiže.
  • Server na Node.js-u (ws, uWebSockets.js ili Socket.IO) ili na Laravelu (Reverb, a na klijentu Echo).
  • Pomoćne funkcije na klijentu koje se ponovo povezuju uz backoff, ponovo prijave na sobe i odbacuju duplikate po id-u poruke.
  • Trajnost, korake moderacije (blokada, brisanje, slow mode) i način da se soba izveze kada neko traži zapis.

Browserska strana konekcije opisana je u WebSockets API-ju na MDN-u. Taj transport slijedimo, a pravila proizvoda stavljamo u događaje iznad njega.

Obavijesti uživo dok je aplikacija otvorena

Obavijest se raspoređuje drugačije od chata. Jedan događaj, plaćena narudžba ili završen deploy, mora stići do mnogo korisnika koji ne sjede u razgovoru. Kanal po korisniku isporuči ga jednom i osvježi bedž, što prozivanje svakog otvorenog taba nikad ne uradi kako treba.

  • Fan-out po korisniku, a ne globalni broadcast. Događaj za jedan nalog ide na konekcije tog naloga. Događaj za tim ide na kanal tima. Ništa se ne šalje svakom socketu na serveru.
  • Socket je kanal za ljude koji su online. Web push, email ili mobilni push i dalje pokrivaju zaključan telefon. Isti zapis događaja hrani sve kanale, pa se formulacija ne razilazi.
  • Potvrde. Klijent potvrđuje id-eve koje je prikazao. Nakon ponovnog povezivanja traži sve novije od zadnjeg id-a, što zatvara rupu koju ostavi mrtva konekcija.
  • Tihi sati i isključivanje zvuka žive u skladištu podešavanja. Socket server ih pročita prije slanja, tako da utišana soba ostane tiha na svakom uređaju.

Igre i autoritativno stanje

Multiplayer igre koriste isti transport uz stroži sat. Kartaška igra, potezna taktika, kviz uživo ili ležerni zajednički svijet mogu ići preko WebSocketa. Brza akcijska igra kojoj trebaju nepouzdani, neuređeni paketi obično bolje leži na WebRTC data kanalima ili UDP protokolu, i to ćemo reći prije nego što izgradimo pogrešnu petlju.

Ruke drže kontroler za igre ispred ekrana tokom igranja
  • Server drži pravila. Klijenti šalju ulaze („odigraj ovu kartu“, „pomjeri se na ovo polje“). Rezultat, inventar ili poziciju ne šalju kao činjenicu.
  • Tick ili potez pomjera stanje na jednom mjestu. Gledaoci i igrači dobijaju isti snimak ili istu listu ulaza, tako da se dva ekrana ne mogu razići oko toga čiji je potez.
  • Zakasnjeli i ponovljeni ulazi odbacuju se po rednom broju. Ponovno povezivanje dobija snimak plus ulaze koje je propustilo, a ne reprizu cijelog meča od nule, osim ako je meč dovoljno kratak.
  • Lobiji, uparivanje i rezultati žive u bazi. Socket soba postoji samo dok traje meč.

Kviz ili živa tabla traže skroman tick u jednoj regiji. Globalna mreža za igre pogrešna je veličina za igrače koji sjede u jednoj zemlji i igraju na potez.

Alati za saradnju nad zajedničkim stanjem

Zajedničko uređivanje je slučaj u kojem „zadnji upis pobjeđuje“ uništi nečiji rad. Dvije osobe na istom računu, tabli ili planu časa kucaće u isto vrijeme. Socket nosi izmjene; strategija spajanja odlučuje šta dokument postaje.

Zajednički drveni radni sto sa laptopima, telefonima i slušalicama u svijetloj kancelariji
  • CRDT-ovi kao Yjs odgovaraju tekstu, listama i mapama koje se moraju spojiti bez centralne brave. Svaki klijent primjenjuje tuđe izmjene kako stižu, uključujući one koje su bile na putu tokom prekida.
  • Operational transform i dalje odgovara nekim editorima koji ga već govore. Ne uvodimo ga na novi dokument ako CRDT biblioteka već pokriva tipove podataka.
  • Awareness (kursori, selekcije, „Ana gleda ovu stranicu“) ide kao prolazno stanje, odvojeno od dokumenta, i nestaje kada konekcija padne.
  • Snimci se pišu u bazu na tajmer i pri izlasku, tako da novi učesnik učita snimak plus izmjene nakon njega, umjesto da reprodukuje godine pritisaka na tastere.

Komentari, dodjele i dozvole ostaju obični zapisi. Živi sloj ne postaje drugi sistem dozvola.

WebSocket aplikacije na više od jednog procesa

Jedan Node.js proces često drži desetine hiljada neaktivnih konekcija, ograničen memorijom po socketu, a ne brojem zahtjeva. Granica se pojavi kada pokrenete više od jednog procesa, jer poruka objavljena na procesu A mora stići do socketa koji je slučajno završio na procesu B. Zato kapacitet planiramo prema vrhuncu istovremenih konekcija i prema tome koliko poruka jedna soba šalje u sekundi, a ne prema ukupnom broju registrovanih korisnika.

PristupKako poruka nađe pravi socketKada odgovara
Jedan procesU memoriji, na tom procesuJedan server i broj konekcija koji ste izmjerili
Sticky sessionsLoad balancer drži klijenta na jednom procesuNekoliko čvorova, i broadcasti koji i dalje moraju preći između procesa
Pub/sub backplaneRedis ili NATS nose događaje; svaki čvor isporučuje svojim socketimaHorizontalni rast, deploy koji restartuje čvorove, više instanci aplikacije
Poseban realtime serverSoketi, Centrifugo ili Laravel Reverb drže konekcije; aplikacija objavljuje događajeHTTP API i flota socketa treba da rastu odvojeno

Redis adapter u Socket.IO-u uobičajen je backplane kada već koristimo Socket.IO sobe; model je opisan u dokumentaciji Redis adaptera. Na Laravelu istu ulogu igra Reverb, a Echo je klijent. Hosting u više regija posebna je odluka, opisana u našim bilješkama o deploymentu preko DigitalOceana, AWS-a, Hetznera i bare metala.

Proxyji i neaktivne konekcije

WebSocket počinje kao HTTP i zatim se nadograđuje. Nginx, Traefik i Cloudflare mogu proksirati tu nadogradnju. Običan Varnish keš ne treba: putanja socketa ga zaobilazi i slijeće na aplikaciju. Svaka konekcija ima heartbeat, jer se neaktivan TCP prekida, a ponovna povezivanja koriste backoff da se svi klijenti ne vrate u istom trenutku.

WebSocket, server-sent events ili polling

Trajni socket je pravi alat kada klijent takođe šalje česte male poruke, kao u chatu, igrama i zajedničkom uređivanju. Jednosmjerni živi feed često je jednostavniji kao server-sent events. Polling ostaje razuman izbor za podatke koji smiju biti stari nekoliko sekundi i za klijente koji ne mogu držati konekciju.

WebSocketServer-sent eventsKratki polling
SmjerKlijent i serverServer prema klijentuKlijent pita, server odgovara
Podrška u browseruŠiroka, uz malu klijentsku biblioteku gdje koristiŠiroka, sa ugrađenim reconnectom na EventSourceSve što može poslati HTTP
InfrastrukturaUpgrade, heartbeat, backplane kada se širiteObičan HTTP, lako iza proxyjaObičan HTTP, više zahtjeva kako publika raste
OdgovaraChat, kursori, igre, dvosmjerni alatiFeedovi, logovi, obavijesti dok je tab otvorenTable koje podnose kašnjenje, i rezervni put

Sigurnost na dugotrajnoj konekciji

WebSocket je dugačak zahtjev koji zapravo ne završava. Autentifikacija se dešava na handshakeu, a autorizacija ponovo pri svakom ulasku u sobu i svakom objavljivanju. Važeća prijava nije dozvola da se sluša svaki kanal koji proces poznaje. Zato spisak soba i pravilo ko u koju smije ući stoje u katalogu događaja, zajedno sa formatom poruke, a ne kao naknadna zakrpa kada se prva tuđa poruka pojavi kod pogrešnog klijenta.

  • Transport je samo wss://. Session kolačić ili kratkotrajna karta dokazuju identitet tokom upgradea. Dugotrajne tajne ne stoje u query stringu, gdje ih access logovi čuvaju.
  • Zaglavlje Origin provjerava se prema sajtovima koje zaista servirate.
  • Svaka poruka ima najveću veličinu, svaka konekcija najveći tempo, a bučan klijent se prekida. Ista ideja kao HTTP rate limiting na Node.js-u i Expressu, primijenjena na frameove umjesto na rute.
  • Sobe provjeravaju članstvo na serveru. Klijent ne može da se pretplati na user:42 ili order:1842 pogađanjem imena.
  • Backpressure: spor klijent ne dobija neograničen red u memoriji. Preko limita konekcija se zatvara, a taj klijent se ponovo uskladi iz baze.

Česte WebSocket greške koje ispravljamo

  • Jedina kopija poruka drži se u memoriji jednog procesa, pa soba nestane na restartu.
  • Nema heartbeata, pa mrtvi telefoni zauzimaju mjesto dok proxy ne prekine TCP.
  • Svaki događaj ide na svaku konekciju, pa jedna zaposlena soba postane problem cijelog servera.
  • Vjeruje se rezultatu, cijeni ili zastavici dozvole koja je stigla iz browsera.
  • Ponovno povezivanje u tijesnoj petlji nakon deploya, što napravi veći prekid nego sam deploy.
  • Socket stoji iza keš sloja koji ne govori upgrade, pa handshake nikad ne završi i uzrok se traži na pogrešnom mjestu.

Šta određuje cijenu funkcije u realnom vremenu

Procjenu pravimo iz događaja, broja ljudi povezanih u isto vrijeme i ponašanja funkcije kada konekcija umre. Ta tri pitanja određuju veličinu posla više nego izbor biblioteke.

  • Vrhunac istovremenih konekcija, i da li su to neaktivni slušaoci ili ljudi koji šalju mnogo poruka u sekundi.
  • Koliko stare prepiske, pretrage i moderacije proizvod traži pored živog repa.
  • Prisutnost, kucanje i potvrde čitanja, koje je jeftino opisati, a lako pogrešno uraditi na skali.
  • Jedna regija ili više, i da li HTTP aplikacija i socket server rastu odvojeno.
  • Putevi van mreže: web push, email, mobilna obavijest, i zajednički zapis događaja iza njih.
  • Jednokratno puštanje, ili stalno dotezanje kada se u metrikama pojave stvarne sobe i trajanja mečeva.

WebSocket aplikacije procjenjujemo prema toj listi, nakon pogleda na trenutnu aplikaciju i prije implementacije. Feed koji smije kasniti deset sekundi možda i ne traži socket.

Kako radimo

  1. Analiza: navedemo događaje, ko ih smije slati, ko ih mora primiti i šta mora važiti nakon osvježavanja.
  2. Plan: biramo server, model soba, trajnost i backplane, i sa vama napišemo katalog događaja.
  3. Izrada: implementiramo handshake, autorizaciju, isporuku, ponovno povezivanje i klijentske pomoćne funkcije u aplikaciji koju već imate.
  4. Testiranje: kidamo konekcije, ponavljamo isti id poruke, ulazimo u sobe koje korisnik ne smije vidjeti i opterećujemo vruću putanju.
  5. Puštanje u rad: isporučujemo iza postojeće prijave, pratimo broj konekcija i neuspjehe poruka, i držimo polling ili SSE rezervu tamo gdje klijent to traži.
  6. Podrška: doterujemo heartbeat, slow mode i kapacitet prema stvarnom saobraćaju. Self-hosted provjera dostupnosti pokriva HTTP stranu; zdravlje socketa stoji pored nje kao zasebna metrika.

Zašto CodeWiz

  • Full-stack: gradimo Laravel, Node.js i WordPress aplikacije kojima se socket mora pridružiti, pa živi događaji prate domenski model umjesto da ga zaobilaze.
  • Open-source na prvom mjestu: ws, Socket.IO, Laravel Reverb, Soketi, Redis i NATS, bez računa po konekciji od hostovanog realtime dobavljača.
  • Veličina prema proizvodu: jedan proces i čist protokol kada je to dovoljno; backplane kada se spremate da pokrenete drugi čvor.
  • Fer cijene: vrhunski razvoj bez vrhunskih cijena, iz Banje Luke, na daljinu sa klijentima iz regiona i svijeta od 2023.
  • Performanse i sigurnost u istoj isporuci: heartbeat, Cloudflare ispred upgradea i autorizacija na svakoj sobi. Više o studiju je na stranici o CodeWizu.

Često postavljana pitanja

Koliko koštaju WebSocket aplikacije?

Jedan živi kanal na postojećoj aplikaciji, sa sačuvanim porukama u bazi i klijentom koji se ponovo povezuje, mali je projekat. Chat sa moderacijom, prisutnošću i backplaneom, ili zajednički dokument, veći je posao. Pisanu procjenu dobijate nakon što vidimo događaje i očekivani broj istovremenih konekcija.

Da li koristiti sirovi WebSocket ili Socket.IO?

Sirovi WebSocket je mali protokol i dobar izbor kada kontrolišemo svakog klijenta. Socket.IO dodaje sobe, potvrde, automatski reconnect i long-polling rezervu, uz teži protokol. Biramo Socket.IO kada su browseri publika i rezerva je bitna, a sirovi ws ili uWebSockets.js kada putanja poruke mora ostati mala.

Koliko korisnika drži jedan server?

Neaktivne konekcije su jeftine: jedan Node.js proces obično drži desetine hiljada, a granica je memorija. Aktivan chat ili tick igre košta procesor i protok po poruci. Mjerimo fan-out, pa biramo jedan proces, sticky sessions ili Redis backplane.

Da li obavijesti traže WebSocket?

Samo one koje se moraju pojaviti dok neko gleda aplikaciju. Sažetak, račun ili uzbuna koja može sačekati minutu dobar je posao za email, web push ili poll. Socket koristimo za otvoreni tab, a push kanal za sve ostalo, vođeno istim događajem.

Da li ovo radi iza Cloudflarea i load balancera?

Radi. Cloudflare proksira WebSocket upgrade, a Nginx ili Traefik rade isto na vašem serveru. Putanja ne smije ići kroz keš koji očekuje kratke HTTP odgovore. Kada pokrenete više od jednog procesa aplikacije, sticky sessions ili pub/sub backplane drže poruku na čvoru koji drži socket.

Možete li dodati chat uživo na postojeći Laravel ili Node.js?

Možemo. Laravel Reverb i Echo, ili Node.js socket server pored trenutnog API-ja, mogu dijeliti istu sesiju prijave i istu bazu. Aplikaciju ne gradimo iznova da bismo dodali sobu i tabelu poruka.

Spremni za živi sloj?

Recite nam koji događaji treba da stižu odmah i koliko je ljudi povezano kada je proizvod zauzet. Predložićemo protokol, server i uvođenje koje odgovara aplikaciji koju već vodite. Kontaktirajte nas da razgovaramo, ili pogledajte naše usluge WebSocket aplikacija.