Polling vagy Long Polling
Ha valaki már próbálkozott chatet, vagy más valós eseményeken alapuló alkalmazást írni, PHP-HTML-Javascript környezetben, akkor biztosan találkozott már a Polling, Long Polling technikával. Rövid ismertető és buktatók.
A kliens és szerver közötti kommunikáció - egyéb kliens és szerveroldali modulok hiányában - egyirányú, mégpedig a szerver irányában. Ez így azért túlzás, de röviden az a lényeg, hogy a szerver nem tud kezdeményezni a kliens felé kéréseket. Vagyis mindig a kliens kezdeményez, kérdezgeti a szervert, ami válaszképp némi adatot küld. Ez viszont azt eredményezi, hogy ha - és vegyünk egy egyszerű chatalkalmazást - nem csak adott kliens beszélget önmagában a szerverrel, hanem egy másik is - így a két klienst a szerver mintegy összeköti -, akkor viszonylag nehézkes a két klienst valós időben értesíteni, hogy melyik-melyik éppen mit csinál.
Nos, kicsit leegyszerűsítve - maradjunk a chatnél -, ha „A” üzen „B” kliensnek, akkor azt a szerveren keresztül teszik. Így a szerver fogadja az üzenetet, majd a többi kliensnek kiszolgálja.
Polling
A buktató ott van, hogy a szerver nem tud kérést indítani a kliensek felé, hogy esemény történt - „A” üzent „B”-nek - , ezért a klienseknek kell a szervert kérdezgetni, hogy történt-e már valami. Ezt a kérdezgetést - a legegyszerűbb esetben - elfogadhatóan rövid időközönként a szerver felé történő kérdezgetéssel oldhatjuk meg. Ekkor a kliens megkérdezi a szervert jött-e új üzenet a számára, a szerver megnézi, majd válaszul közli az eredményt. A kliens pedig a válasz után röviddel (1-2 másodperc), ismét megkérdezi a szervert. Ezt angolul Polling-nak hívjuk. Ez meglehetősen erőforrás-igényes megoldás, mert minden ilyen kérésnél fel kell építeni egy kapcsolatot a szerverrel, majd a szervernek ki kell értékelnie az eredményeket és azt visszaadni a kliensnek. Mind a két fél dolgozik veszettül, holott, könnyen lehet, hogy 10-15 másodpercig sem történik semmi, így 10-15 kérés teljesen feleslegesen történt.
A Polling technika kliens és szerver közti kommunikációja
- A kliens kérést indít a szerver felé
- Beérkezik a szerverre a kérés
- A szerver kiértékeli az eredményeket és visszaküldi a választ
- Megérkezik a klienshez a válasz
Természetesen a 2. és 3. lépések között eltelik némi idő, amíg a szerver kiértékeli a választ.
Long Polling
A Polling, egy tovább gondolt változata a Long Polling, ami végül is megegyezik a sima Polling-val, a különbség ott van, hogy a Long Polling esetében a szerver nem ad azonnal választ, hanem kivár, amíg történik is valami. Csak akkor ad vissza eredményt, ha van is mit visszaadni, illetve egy technikailag és felhasználói szemszögből is elfogadható idő (~20-30 másodperc) eltelik eredménytelenül (timeout).
Picit részletesebben nézve, a Long Polling esetében a kérést elküldjük a szerver felé, majd a szerver és a kliens is várakozik. Amikor a szerver tudomást vesz olyan eseményről, amit az adott kliens felé ki kell szolgálni, akkor a várakozást megszakítja és visszaadja az eredmény. A kliens a kapott eredményt kiértékeli, és máris küldi a következő kérést a szerver felé, ami szintén várakozik.
A Long Polling technika kliens és szerver közti kommunikációja
- A kliens kérést indít a szerver felé
- Beérkezik a kérés a szerverre, a szerver várakozik
- Amint van közölhető eredmény, a szerver visszaküldi azt
- Megérkezik a klienshez az eredmény és rögtön küldi is a következő kérést.
Buktatók, avagy amire figyelni érdemes
Kliens oldalon az AJAX kérés valamilyen úton-módon véget fog érni, vagy a szerver ad valamilyen választ, vagy megszakad a kapcsolat, a lényeg, hogy minden lehetőséget le kell kezelni. Mivel a kérés mindig akkor küldődik a szerver felé, amikor az előző visszatér és kiértékelődik, ezért minden lehetőséget kezelni kell, különben nem biztosítjuk, hogy például egy időtúllépés esetén a megszakadt kérés után egy új kérés induljon. Ezt mind a Polling, mind a Long Polling esetén vegyük figyelembe.
Kliens oldalon a Long Polling esetén, külön figyelni kell arra, hogy amíg várakozunk egy kérésre, addig nekünk küldeni is kell tudni a szerver felé eseményt. Ez így önmagában nem gond, mert egy másik AJAX kéréssel simán el tudjuk küldeni a szerver felé az adatunkat, viszont a szerver oldalon az egészen addig nem fog kiértékelődni, amíg a várakozó kérésünk véget nem ér. Ez igazából szerveroldali (session) kezelést igényel, mert amikor az első - várakozó - kérés beérkezik és várakozik, mondjuk 20 másodpercet, akkor a kódunkban - általában - indul egy session_start, ez lezárja a session fájlt a szerveren, megakadályozva ezzel azt, hogy amíg a scriptünk dolgozik, addig egy konkuráló script felülírja a session fájlt. Ezért van az, hogy amíg a várakozó kérésünk nem ér véget, az időközben küldött rövid kérésünk - ami csak tényt közölne a szerverrel - addig nem értékelődik ki, amíg a várakozó kérésünk véget nem ér. Ezt ki tudjuk kerülni a session_write_close függvénnyel, ami feloldja a session fájl zárását. Ezért amikor a várakozó (Long Polling) kérésünk befut és a session feladatokkal végeztünk (authentikálás, egyéb feladatok), kiadjuk a session_write_close parancsot és máris feldolgozza a szerverünk az időközben befutott rövid kérést. Természetesen a session_write_close parancs kiadásakor csak a session fájl zárolásának feloldása történik, a szerver attól még ugyan úgy várakozik.
Szerver oldalon amikor Long Polling esetén várakozunk, akkor tulajdonképpen egy várakozó ciklust kell megvalósítanunk. A ciklusunk lehet egy sima while, ami minden iterálás után vár fél- egy másodpercet, az iterálásoknál pedig ellenőrizni kell, hogy jött-e időközben kiértesítendő esemény, ha igen, akkor a ciklus megszakad és visszaadjuk az eredményt. A ciklusnak érdemes egy időtúllépés elleni védelmet adni, mondjuk egy meghatározott visszaszámlálást, például 1 másodperces késleltetés esetén 20-25 iterációt maximalizáljunk. Így egy Long Polling kérés 20-25 másodperces várakozásnak felel meg, ha időközben nem kapunk kiértékelendő eseményt.
Figyelnünk kell arra is, hogy egy-egy iteráció mennyi ideig fut le, mert ha túl bonyolultak az egy iteráció alatt végzendő műveletek, akkor megközelítőleg a teljes kérés nem fog 30 másodpercbe beleférni (lefuttatott parancsok + 1mp várakozás != 1mp). Így könnyen megszakadhat a kliens oldalon a kérés, még mielőtt a szerver visszaadná az eredményt. Ha ilyen bonyolult műveleteket végzünk, érdemes az iterálás elején és végén időt mérni és azzal kompenzálni az iterálások számát.
Ha szerver oldalon például MySQL-vel tárolunk adatokat, akkor nem érdemes a fent említett iterációnként egy-egy kérést küldeni a MySQL felé, hogy jött-e már valami, mivel ez igen költséges. Itt könnyen nyerhetünk erőforrást az alábbi technikával. Amikor esemény érkezik - write -, akkor egy a sessionök között is olvasható (de a MySQL-nél költséghatékonyabb) helyre leteszünk egy flaget, egy jelzőt, hogy esemény jött. Így amikor a szerver várakozik, akkor nem kell minden iterálásnál a MySQL-t hívogatni, elég csak ezt a közös felületet olvasgatni, hogy történt-e már valami, ha igen, akkor kell csak a MySQL-t meghívni, lekérdezni. Ilyen közös felület lehet egy fájl, vagy akár - és általam javasolt is - az APC is.
Kliens oldali példa:
function getDatas() {
$.ajax({
type: "POST",
url: 'a kérés url-je',
data: {adat: érték},
dataType: "json"
}).done(function(msg) {
// a kapott eredmény kiértékelése
getDatas(); // újrahívás
});
}
Szerver oldali példa:
// ha kell sessionnel kapcsolatos feladatok elvégzése
// lezárjuk a session file-t, ami már nem kell a továbbiakban
session_write_close();
$counter = 20;
while ($counter > 0) {
// ellenőrizzük jött-e új üzenet
// ha van eredmény, akkor break
// csökkentjük a lehetséges iterálások számát
$counter--;
// várunk 1mp-t
usleep(1000000);
}
// vissza adjuk az eredményt