Pulse - asinkroni PHP za dvosmjernu komunikaciju server-client

Malo sam se zaigrao,

ako nekoga bude zanimalo kako sam ovo izveo s obzirom da PHP uopće nema mogućnost asinkronog izvršavanja procesa. (Ili ipak ima? :grinning:)

Uglavnom, ako bude zainteresiranih, rado ću prodiskutirati kako je ovo izvedeno. …a možda netko ima ideju?

P.S. što se tiče dokumenta, sve prigovore adresirajte na chatGPT xd. Ja njemu šibnem code, on meni dokument, hehe.

Websockets ili ajax long polling + SSE

Eh da, sa SSE bi bilo relativno lako ovako nešto postići, no meni SSE nije htio nikako proraditi.
Znači on je uvjek čekao da se php izvrši i onda je cijeli output pucao u jednom komadu … tipa za:

echo “1”;
sleep(5);
echo “2”;

…nije htio slati prema klijentu ispis u dva sloga, nego bi klijent sa odgodom od 5 sec primio u jednom komadu “12”.

Probao sam sve što mi je chatGPT ponudio i kako ništa nije proradilo niti meni lokalno niti na mom online serveru, reko “zajebi”, idem ovo složiti na bazi ajax long poling. Što mi se činilo kao jednostavan pristup, opće suportan…koji nebi trebao skrivati iznenađenja.

No s obzirom na izostanak SSE, svi php procesi su mi ograničeni na 1 proces = 1 output prema klijentu …a tu onda počinje igra za ovo složit, hehe.

Pa koristio si exec shell_exec, system ili proc_open za pokretanje pozadinskih procesa. Ali samo ću da ti kažem da to nije pravo asinhrono izvršavanje već samo njegova simulacija, jer koristiš dodatne alate, a nij pravo asinhrono izvršavanje u samom jeziku kao što su JavaScript, Go ili Python sa asyncio.

A tek sam sada pročitala ovo gore za Pulse, onda mislim da si koristio AJAX-a i Long Polling ili nešto slično za dvostruku komunikaciju. Kaži nam šta si koristio nemoj da nas držiš u neizvesnosti.

Nisam još tako nešto radio sa PHP-om, a kada bi tako i dizao druge procese, nisam siguran da bi sa tim procesom mogao poslati informaciju prma klijentu.

Nije mi to bio cilj, nego mi se bilo glupo raspisivati ako baš nikoga ne zanima tema…

Uglavnom, koristio sam vrlo basic stvari, tj. samo AJAX i tehniku Long Polling, tko ne zna o čemu je riječ, to je tehnika gdje pošaljemo ajax request na server, ali server ne odgovara odmah nego drži otovoren request dok god se ne pojavi potreba da obavijesti klijenta o nečemu. Na taj način smo zapravo omogućili da server trigira moment kada želi poslati informaciju klijentu.

E sad, kako sam pomoću te tehnike postigao obostranu komunikaciju sa mogućnosti slanja paralelnih poruka i da komunikacija preživljava i reload stranice …

Znači osnovna ideja je:
Client šalje ajax request
Server drži request dok ne poželi poslati poruku klijentu
Klijent kada primi response (poruku), on podiže novi php proces, slanjem novog ajaxa.
Na taj način smo omogućili kontinuiran prijenos informacije sa servera na klijenta.

No moja tedencija je bila da napravim da php klasa upravlja “živim” varijablama tijekom cijele komunikacije, a kako se php proces završava kod slanja svakog odgovora, tako se restoraju i sve varijable. Preživljavanje varijabli je riješeno zapisivanjem static stanja na HDD od kuda ih novi proces oživi, pa unutar Pulse klase imamo osjećaj da proces cijelo vrijeme živi. (Iako se php procesi izmjenjuju) Problem kod ovoga je da sve treba uskladiti, jer ako će procesi paralelno ažurirati static varijable, može doći do gubitka podataka.

Tako da je prvi problem bio kako izvesti da client može poslati paralelno poruku dok Pulse-resolver živi.
Naime, klijent kada pošalje poruku na server, tj. kada podigne paralelni ajax request, taj request zna da je Pulse-resolver živ i čeka da se aktivni ciklus resolvera završi.(Ujedno javi resolveru da se aborta, kako bi se on mogao ubaciti)
Resolver tako na kraju svog ciklusa se aborta, spremi sva static stanja u HDD, a zadnji Ajax request preuzme njegovu ulogu i ujedno prosljedi resolveru poruku koju je client poslao. Taj novi resolver oživi sva static stanja svog prethodnika, pa unutar php Pulse klase imamo osjećaj da se sve kontinuirano dešava.

Na ovaj način možemo imati koliko god paralelnih poruka poslanih istovremeno, ona će jedna po jedna čekati svoj red da se ubace, tj. da preuzmu taj proces resolvera. Mi tako unutar Pulse klase, imamo osjećaj da upravljamo samo jednim resolverom. Iako on crkne/oživi po dolasku svake poruke.

I to je skoro pa sve, no ima jedan problemčić.

Da bi resolver kontinuirano mogao raditi, on mora sam sebe oživiti nakon svake poslane poruke.
To sam već objasnio: resolver šalje poruku na klijenta (time on crkne), client nakon što primi poruku, vrati ajax request koji ponovno oživi resolver i on tako nastavlja svoj posao …

No problem je u tome, ako se desi da klijent iz bilo kojeg razloga postane nedostupan (offline, stranica se reloada, ili nešto treće??) …onda će resolver ostati “mrtav” nakon što pošalje svoju zadnju “deadly” poruku. (Neće ga imati tko oživiti)

A kako je ovdje resolver vodeći igrač, mi želimo (ako želimo) da on obavlja svoj posao neovisno o tome jel klijent privremeno nedostupan.

Ovo je rješeno na sljedeći način:

Client kada otvara kanal komunikacije, kod prvog ajax requesta šalje još jedan ajax request. (Nazivam ga bulk request). Taj bulk request služi kao safe guard za slučaj da resolver crkne uslijed nedostupnog clienta kod slanja svoje zadnje “deadly” poruke.
Znači bulk radi sljedeće:

  • prati jel resolver živ
  • ako resolver ne pokaže znakove aktivnosti neko (zadano) vrijeme, bulk ga smatra da je crkao i preuzima njegovu ulogu. Znači oživi static varijablu (koja je uredno spremljena kod isporuke zadnje “deadly” poruke) i nastavlja posao resolvera tamo gdje je on stao.

No bulk se ponaša malo drugačije, on ne smije poslati poruku na klijenta, jer će i on crknuti ako je klijent i dalje nedostupan. Tako da resolver dok je u bulk modu, neće slati poruke na klijenta nego će sve poruke koje bi bile poslane, spremat će ih u svoju bulk listu…i to će raditi onoliko dugo dok:

  • mu ne istekne zadani timeout
  • dok ga ne ugasi logika unutar samog resolvera (posavljena od onoga tko koristi Pulse klasu)
  • ili dok ne shvati da je klijent opet živ.

No bulk proces ne može niti direktno shvatiti da je klijent živ, niti on više može poslati poruku na klijenta, jer ako je bulk preuzeo proces koji je crkao zbog nedostupnosti klijenta, onda i ovaj bulk više nema tu konekciju po kojoj je nastao…

Pa klijent ako se vrati u život i ako pokrene isti kanal, taj prvi ajax request koji podiže kanal komunikacije će shvatiti da je bulk mode aktiviran i napravit će sljedeće: Vratit će poruku klijentu da je bulk mode aktivan i da treba u pozadinu staviti novi bulk da bude safe guard. Klijent tako primajući tu poruku šalje pojačanje, šalje i novi bulk request i novi ajax request koji će nastaviti život resolvera. Kada oni dvoje stignu na server, kažu aktivnom bulk resolveru da je sve ok i da se sada može ugasiti. Pričekaju da bulk resolver završi svoj trenutni ciklus i da se zagasi, preuzmu sve prikupljene poruke u bulk listi (opet preko HDD-a) i sve te poruke pošalju na klijenta. Klijent tako prima sve poruke koje su se desile dok je bio nedostupan. (Na programeru je kako će hendlati te poruke, ali one se nisu izgubile) …i od tog trenutka proces se opet uredno nastavlja, imamo novi safe bulk koji opet dežura da spasi stvar po potrebi i ajax requestove koji se naizmjenično izmjenjuje i razmjenjuje poruke.

Evo i jednostavna primjer kako to radi u praski:

Ovdje je setup sljedeći:
Pulse resolver ima zadatak da u svakom svom ciklusu podigne brojač za jedan i dobiveni broj pošalje kao poruku na klijenta.

Klijent ima zadatak da ispiše svaku poruku koju dobije, tako dobijemo brojač koji ispisuje niz: 1,2,3,4,5,6,7,8,9,…

E sad, ja na 9 reloadam stranicu i nakon reloada pokrećem isti kanal komunikacije. Nakon ponovnog pokretanja kanala dobivamo istovremeno poruke 10,11,12,13,14 i nakon toga nastavlja se dalje odbrojavanje.

Poruke 10,11,12,13,14 su one koje je resolver slao dok smo mi bili nedostupni i poslao ih je kod našeg spajanja na isti kanal…i od tuda se proces komunikacije dalje nastavio po već zadanom setupu scene.

Zašto se nije više brojeva prikupilo prilikom reloada stranice, zato jer je bulk čekao 3 sekunde dok shvati da je resolver crkao i tek tada je nastavio njegov posao. Taj delay u većini sučajeva ne predstavlja problem, dok bi gubitak informacije itekako predstavljao.

Ovime je upravo to postignuto. Kanal komunikacije koji preživljava reload klijenta. Vjerujem da bi se sa nekim drugim backand jezicima ovo izvelo daleko jednostavnije, ali onda nebi bilo ovoliko zabavno, hehe.

1 Like

To nije dvosmerna komunikacija, jer uvek saljes request sa klijenta, ne mozes da posaljes direktno sa servera na klijent - za to se koristi websocket ili SSE. Da mozes da posaljes tipa notifikaciju sa servera, bez da je klijent poslao ikakav request, to je dvosmerna komunikacija.

Ovo što je uradio sa Long Pollingom je simulacija dvosmerne komunikacije. Naravno da su WebSocket ili SSE bolji izbor, posebno kada postoji većii broj korisnika koja mora dobiti podatke u realnom vremenu. Ali ovo što je @bozoou odradio je stvarno profi i pametno odrađeno posebno ako se uzmu u obzir ograničenjaa koja imaju PHP i Long pollling. A sem toga ako njegovo rešenje radi onaj posao za koji je namenjen nije bitno da li je prava dvosmerna komunikacija ili simulacija. I da budemo realni ovako nešto ne može da napravi bilo ko, potrebno je duboko razumevanje kako stvari funkcionišu. Od mene ima respect :pray: :+1: :ok_hand:

1 Like

Pa postignut je konačni cilj, dvosmjerna komunikacija. Nevezano što je preduvjet da se klijent spoji na kanal komunikacije, dalje server može poslati poruku u bilo kojem momentu i koliko god poruka želi.

Pa i SSE neće raditi prije nego klijent pošalje zahtjev serveru, koji zatim ostavlja vezu otvorenom kako bi slao poruke klijentu. (Samo je to jednosmjerna komunikacija, jer bez dodatne implementacije tim kanalom mogu ići samo poruke sa servera na klijenta)

I kod websocketa vrijedi isto, neće nastati kanal komunikacije prije nego klijent pošalje HTTP zahtjev prema webscoketu, čime se otvara kanal.

Tako da taj prvi ajax request koji šalje klijent u ovom mom slučaju, gledaj na njega ekvivalent tome HTTP zahtjevu koji otvara kanal komunikacije, što on upravo i jeste.

Mislim, ne možeš niti u jednom slučaju imati kanal komunikacije bez da se klijent predstavi i kaže “tu sam”, ajmo pričati. Klijent može da se prvi javi serveru, jer za server se zna na kojoj adresi je dostupan.

1 Like

Da li si razmišljaoo da umesto HDD koristiš redis ili memcached baze za čuvanje stanja ovako bi se sigurno dobila veća brzina???

Pa da, prvi zahtev uvek dolazi od klijenta bilo da je komunikacija jednosmerna ili dvosmerna. Server nema pojama gde se ti nalaziš dok mmu se ne javiš i ne kažeš, evo me ovde sam.

Hehe, hvala ti. :slightly_smiling_face: :upside_down_face: :slightly_smiling_face:

Znači nije mi još nikako pala na pamet ova tehnika Long Polling i kada sam čuo za nju, odmah mi je ovo palo na pamet i bilo mi je baš forica zadatak, da sam se bacio odmah na posao iako mi trenutno ovo i ne treba. (dobra)2 dana štrikanja, hehe.

Koliko sam upućen, to su sve implementacije koje zahtjevaju instalacije na serveru čemu trenutno na ovom serveru nemam pristup. Zato sam i išao na ovakvo jedno rješenje, koje radi sa basic dostupnim stvarima i neovisi o dodatnim instalacijama.

Ako brzina postane usko grlo, nadograđivat će se.

Ali da, malo me plašilo korištenje svakog upisivanja u HDD i praktički sam birao varijablama kraća imena, samo da se manje upisuje, haha.
Trudio sam se svakako sve izvesti sa što manje requestova za pisanje po HDD.
…a lako se to prebaci na neki drugačiji način zapisa stanja, ako zatreba.

1 Like

Poenta price je da server odgovara na zahtev koji je klijent poslao, to nije dvosmerna komunikacija. Ti ne mozes da odgovoris sa servera a da pre toga klijent nije zahtevao nesto (poslao request). Ovo je samo odlozeni request, ali princip je isti kao i kod obicnog request → response i to nije dvosmerna komunikacija. Websocket funkcionise skroz drugacije, to je prava dvosmerna komunikacija. To sto ti pricas da je slicno, to je samo kada radis handshake kod Websocketa, zatim ti se otvara direktna dvosmerna linija server/client, sto funkcionise na skroz drugaciji nacin nego long polling. Ti sa ovim principom i dalje mozes mnogo requestova da bacis u vodu u situaciji kad server nema sta da odgovori i da imas mnogo requestova otvorenih u isto vreme, sto nije idealno. Kod websocketa je to nemoguce. Zato je i izmisljen websocket da resi ove probleme, a long polling je mnogo starija tehnika. Ima mnogo mana ovaj princip, kad koristis load balancer i imas vise servera, tesko je da pratis kojem serveru si poslao request. Onda kod ovog principa ti ne mozes da znas da li se klijent diskontektovao ili je i dalje tu, dok god websocketa znas da li je klijent ulogovan ili ne itd… U sustini, sta god da pravis websocket ce bolje raditi, tako da ne treba izmisljati toplu vodu :slight_smile:

Slažem se sa svime što si napisao, kada pričaš o prednostima i manama pojedinog pristupa.

No, ne mogu se i dalje složiti da ovo nije dvosmjerna komunikacija, ili ne razumijem značenje te riječi.
Dali se može slati poruka u dva smjera? Može. (Ne vjerujem da ta riječ ima neko dublje značenje od toga)

Prednosti i mane te komunikacije su već nešto drugo, no to ne mjenja činjenicu da je to dvosmjerna komunikacija. Pa čak i ako postoji mogućnost na grešku.

Npr. I mobitel može napraviti grešku prilikom slanja sms-a, no to ne mjenja činjenicu da sms omogućava dvosmjernu komunikaciju.

Da ovo nije idealno niti suviše optimizirano, to mi je jasno.

P.S. dvosmjernoj komunikaciji možeš dodjeliti epitete:
Stabilna, nestabilna, skupa, jeftina, brza, spora, jednostavna, složena, sigurna od špijuniranja, nesigurna … itd. no da je ona i najnestabilnija na ovom svijetu, to neće promjeniti činjenicu da je to dvosmjerna komunikacija.

No pustimo natezanje oko značenja riječi, volio bi da malo detaljnije napišeš što vidiš kao glavne mane ovog pristupa. Ako mozes pliz ovo malo detaljnije …

…skužih da si mi zadao još jednu stvar koju treba riješiti. Naime po trenutnoj logici koju imam, ako server nema što odgovoriti, Long Polling tehnika će dugo zadržavati aktivan process na serveru nakon što se klijent isključi.
No to je lako rješivo, dovoljno je zadati neki malo dulji interval gdje će se klijent svako toliko javiti da je živ. Metoda resolver isto tako treba unutar tog istog duljeg intervala provjeriti jel se klijent javio da je živ i isključiti se ako nije.
Taj interval ako se stavi tipa “1 minuta”, niti je skupo za provjeravati, a niti je neki veliki delay nakon kojega će se resolver odjaviti ako se klijent isključi.

Ma on misli na činjenicu da sa websocketom server može poslati poruku klijentu bez čekanja zahteva, što tehnički i jeste tačno…Ali ovo što si ti napravio uspešno simulira dvosmernu komunikaciju jer server može da odgovori kada ima podatke, a da klijetnu šalje poruke nezavisno.

U tvom primeru, server odgovara po istom principu kao obican HTTP request, znaci klijent posalje request → server odgovara, to nije dvosmerna komunikacija, jer onda je i najobicniji HTTP request dvosmerna komunikacija. Dvosmerna komunikacija znaci da server moze da posalje podatke klijentu, bez da je klijent to zahtevao… Long polling je isti djavo kao i obican ajax request ili short polling, samo je request odlozen, server ne odgovara odma nego kada podaci postanu dostupni ali je princip isti. Kod websocketa imas pravu dvosmernu liniju i to je poenta moje price, no nebitno… Ne kazem da je ova tehnika losa, ni nista suprotno, samo govorim da postoji mnogo bolja opcija, jer je ovo stara tehnika i ima dosta mana. Dobra je za neke jednostavnije projekte, sve komplikovanije stvari se radi na drugaciji nacin. Svaka cast za vezbu, ja govorim samo da je ovo klasican long polling, nista novo, ti si ovde resio samo persistance kod long pollinga, sto je reseno vec u mnogim drugim paketima. Isti problem ima i websocket sa persistance, sto je isto reseno vec. Ne znam sto se ljuti gospodja iznad, ja objasnjavam samo razlike, da se ne frljamo sa izrazima ovde i da pricamo da je nesto sto nije :slight_smile:

Nisam ja gospođa, nego devojka. A sem toga ako se malo bolje skoncetrišeš i vidiš šta sam gore napisala, primetićeš da sam isto kao i ti napisala da nije prava dvosmerna komunikacije, već simulacija dvosmerne komunikacije. Ali bezobzira na to, Bozoou je uradio odličan posao, rešio mnoge probleme i i to bez dodatnih alata.

Ovo je delimično tačno, Long Polling je produžetak običnog ajax request-a, gde server odlaže odgovor dok ne bude imao podatke. Ali razlika je u tome što long polling smanjuje broj zahteva u odnosu na short polling i što je long polling puno efikasniji od short pollinga.

Kužim ja dobro što on misli, ali njegov problem je u tome što riječi “dvosmjerna komunikacija” dodaje razne epitete koji u suštini te riječi nisu sadržani. Ta riječ je prosta da ne može biti prostija i tako je treba i shvaćati.

I malo dijete će dobro razumjeti jel nešto dvosmjerna komunikacija ili nije i to bez ikakvog razumjevanja što se nalazi u sustavu, dovoljno će mu biti promotriti kako taj sustav radi kada se promatra izvana.

Tako da ako dam dvojici ljudi štap i kažem im da ne smiju pričati, ali se smiju bockati štapom kada nešto žele signalizirati drugome i pitam to malo djete jel oni imaju dvosmjernu komunikaciju, dijete će točno zaključiti da imaju.

Onda jednom od tih dvojice zavežem ruke da ne može primiti štap u ruke i pitam opet dijete isto pitanje, ono će tada dobro zaključiti da to više nije dvosmjerna komunikacija, jer samo jedan ima mogućnost da bocka drugoga.

Eto, toliko je ta riječ prosta, apsolutna je nevezana od sistema koji prenosi informaciju, ona samo sagleda dali informacija može ići u oba smjera , promatrajući kompletan sistem za kojega tvrdimo da li omogućava ili ne omogućava dvosmjernu komunikaciju.

Kolega @shimi pri tome griješi jer on gleda isključivo na jedan ajax request jel to dvosmjerna (kontinuirana) komunikacija, što je točno da nije…no on po tome zaključuje da sustav Pulse ne omogućava dvosmjernu komunikaciju.

Ali cijeli sustav koji je tako izgrađen da koristi te pojedinačne ajax requestove da omogući kontinuirani protok informacije u oba smjera, jeste dvosmjerana kontinuirana komunikacija.

Jer ako informacija kola u oba smjera, onda očito jeste. To je ono što i malo dijete zna, bez poznavanja kako sistem unutar sebe radi, samo promatrajući ga izvana.

Jedan HTTP request ne omogućava dvosmjernu kontinuiranu komunikacija. (Btw. mi ovdje zapravo cijelo vrijeme pričamo o kontinuiranoj dvosmjernoj komunikaciji)
A nije zato jer klijent može iskoristiti taj request da pošalje samo jednu poruku serveru, a server ga koristi da pošalje samo jedan odgovor klijentu. Znači nije kontinuirano.

Dok browser, koji kao sistem koristi više HTTP requestova, on dakako da omogućava kontinuiranu dvosmjernu komunikaciju između poslužitelja (servera) i same web stranice. Što je očito da je tako, jer unutar browsera web stranica i poslužitelj mogu kontinuirano dvojsmjerno komunicirati.

Prema tome, jedan ajax request, nije kontinuirana dvosmjerna komunikacija.
Ali sistem “Pulse” koji sam napravio i koji koristi puno takvih pojedinačnih ajax requestova, on omogućava kontinuiranu dvosmjernu komunikaciju. I to po meni nebi smjelo uopće biti sporno, dok god bilježimo da sistem ima tu mogućnost slanja informacije u oba smjera.

Ali treba razlikovati zahtjevanje od “nazvati” ili ti ga “spojiti se na kanal”.
I kod websocketa client se mora prvo spojiti na kanal da bi započela komunikacija, nakon toga više ne mora zahtjevati svaku pojedinačnu poruku.

Isto je i u mom slučaju, klijent prvo mora “nazvati”, spojiti se na kanal, jel …nakon toga poruke mogu ići kontinuirano bez da to ijedna strana zahtjeva.

A isto je i sa mobitelom …prvo moraš nazvati drugu stranu, a nakon toga možete razmjenjivati informacije koliko god želite bez da zahtjevaš da druga strana nešto govori.

Naravno, dok god se jedna strana ne isključi iz razgovora, što opet vrijedi i za mobitel, i za websocket i za Pulse.

Postaješ još zanimljivija :sweat_smile:

1 Like

Došlo vrijeme da te roboti bolje razumiju nego ljudi :see_no_evil: :see_no_evil: