Long polling – bu WebSocket yoki Server Side Events kabi maxsus protokollardan foydalanmagan holda server bilan doimiy aloqani ta’minlashning eng oddiy usuli.
Amalga oshirish juda oson bo’lib, ko’p hollarda yetarlicha yaxshi.
Oddiy Polling
Serverdan yangi ma’lumot olishning eng oddiy usuli – bu davriy so’rov. Ya’ni, serverga muntazam so’rovlar: “Salom, men shu yerdaman, mening uchun biror ma’lumot bormi?”. Masalan, har 10 soniyada bir marta.
Javobda server birinchi navbatda mijoz onlayn ekanligini o’ziga qayd etadi, ikkinchidan esa o’sha paytgacha olgan xabarlar paketini yuboradi.
Bu ishlaydi, lekin kamchiliklari bor:
- Xabarlar 10 soniyagacha kechikish bilan uzatiladi (so’rovlar orasida).
- Hatto xabarlar bo’lmasa ham, server har 10 soniyada so’rovlar bilan bombardimon qilinadi, hatto foydalanuvchi boshqa joyga o’tgan yoki uxlab qolgan bo’lsa ham. Bu ishlash nuqtai nazaridan ko’rib chiqganda, ancha katta yuklamadir.
Shunday qilib, agar biz juda kichik xizmat haqida gapiraysak, bu yondashuv maqbul bo’lishi mumkin, lekin umuman olganda, yaxshilanish kerak.
Long Polling
“Long polling” deb ataladigan usul – bu serverga so’rov yuborishning ancha yaxshi usuli.
Uni amalga oshirish ham juda oson va xabarlarni kechiktirishlarsiz yetkazadi.
Jarayon:
- Serverga so’rov yuboriladi.
- Server yuborish uchun xabar bo’lgunga qadar ulanishni yopmaydi.
- Xabar paydo bo’lganda – server so’rovga u bilan javob beradi.
- Brauzer darhol yangi so’rov yaratadi.
Brauzer so’rov yuborgan va server bilan kutilayotgan ulanishga ega bo’lgan vaziyat bu usul uchun standartdir. Faqat xabar yetkazilganda ulanish qayta tiklanadi.
Agar tarmoq xatosi tufayli ulanish uzilsa, brauzer darhol yangi so’rov yuboradi.
Uzoq so’rovlar qiladigan mijoz tomonidagi subscribe
funktsiyasining eskizi:
async function subscribe() {
let response = await fetch("/subscribe");
if (response.status == 502) {
// Status 502 - bu ulanish vaqti tugashi xatosi,
// ulanish juda uzoq kutilganda sodir bo'lishi mumkin,
// va masofaviy server yoki proksi uni yopgan
// qayta ulanaylik
await subscribe();
} else if (response.status != 200) {
// Xato - uni ko'rsataylik
showMessage(response.statusText);
// Bir soniyadan keyin qayta ulanish
await new Promise(resolve => setTimeout(resolve, 1000));
await subscribe();
} else {
// Xabarni olish va ko'rsatish
let message = await response.text();
showMessage(message);
// Keyingi xabarni olish uchun subscribe() ni qayta chaqirish
await subscribe();
}
}
subscribe();
Ko’rib turganingizdek, subscribe
funktsiyasi fetch qiladi, keyin javobni kutadi, uni boshqaradi va o’zini qayta chaqiradi.
Server arxitekturasi ko’plab kutilayotgan ulanishlar bilan ishlashga qodir bo’lishi kerak.
Muayyan server arxitektualari har bir ulanish uchun bitta jarayonni ishga tushiradi, natijada ulanishlar soni qancha bo’lsa, shuncha jarayon bo’ladi, har bir jarayon esa ancha xotira sarflaydi. Shunday qilib, juda ko’p ulanishlar hammasi sarflab yuboradi.
Bu ko’pincha PHP va Ruby kabi tillarda yozilgan backend’lar uchun xosdir.
Node.js yordamida yozilgan serverlar odatda bunday muammolarga duch kelmaydi.
Biroq, bu dasturlash tili muammosi emas. Ko’pgina zamonaviy tillar, jumladan PHP va Ruby ham to’g’ri backend’ni amalga oshirish imkonini beradi. Faqat server arxitekturangiz ko’plab bir vaqtdagi ulanishlar bilan yaxshi ishlashiga ishonch hosil qiling.
Demo: chat
Mana demo chat, siz uni yuklab olishingiz va mahalliy ravishda ishga tushirishingiz mumkin (agar Node.js bilan tanish bo’lsangiz va modullarni o’rnatishingiz mumkin bo’lsa):
// Xabarlarni yuborish, oddiy POST
function PublishForm(form, url) {
function sendMessage(message) {
fetch(url, {
method: "POST",
body: message,
});
}
form.onsubmit = function () {
let message = form.message.value;
if (message) {
form.message.value = "";
sendMessage(message);
}
return false;
};
}
// Uzoq so'rovga ega xabarlarni qabul qilish
function SubscribePane(elem, url) {
function showMessage(message) {
let messageElem = document.createElement("div");
messageElem.append(message);
elem.append(messageElem);
}
async function subscribe() {
let response = await fetch(url);
if (response.status == 502) {
// Ulanish vaqti tugaydi
// ulanish juda uzoq vaqt davomida kutilayotganda sodir bo'ladi
// qayta ulanamiz
await subscribe();
} else if (response.status != 200) {
// Xatoni ko'rsatish
showMessage(response.statusText);
// Bir soniyada qayta ulanamiz
await new Promise((resolve) => setTimeout(resolve, 1000));
await subscribe();
} else {
// xabarni qabul qilamiz
let message = await response.text();
showMessage(message);
await subscribe();
}
}
subscribe();
}
let http = require("http");
let url = require("url");
let querystring = require("querystring");
let static = require("node-static");
let fileServer = new static.Server(".");
let subscribers = Object.create(null);
function onSubscribe(req, res) {
let id = Math.random();
res.setHeader("Content-Type", "text/plain;charset=utf-8");
res.setHeader("Cache-Control", "no-cache, must-revalidate");
subscribers[id] = res;
req.on("close", function () {
delete subscribers[id];
});
}
function publish(message) {
for (let id in subscribers) {
let res = subscribers[id];
res.end(message);
}
subscribers = Object.create(null);
}
function accept(req, res) {
let urlParsed = url.parse(req.url, true);
// yangi mijoz xabarlarni xohlaydi
if (urlParsed.pathname == "/subscribe") {
onSubscribe(req, res);
return;
}
// xabar yuborilmoqda
if (urlParsed.pathname == "/publish" && req.method == "POST") {
// POST ni qabul qilmoqda
req.setEncoding("utf8");
let message = "";
req
.on("data", function (chunk) {
message += chunk;
})
.on("end", function () {
publish(message); // uni hammaga e'lon qiling
res.end("ok");
});
return;
}
// qolganlari statikdir
fileServer.serve(req, res);
}
function close() {
for (let id in subscribers) {
let res = subscribers[id];
res.end();
}
}
// -----------------------------------
if (!module.parent) {
http.createServer(accept).listen(8080);
console.log("8080 portida ishlaydigan server");
} else {
exports.accept = accept;
if (process.send) {
process.on("message", (msg) => {
if (msg === "shutdown") {
close();
}
});
}
process.on("SIGINT", close);
}
<!DOCTYPE html>
<script src="browser.js"></script>
Ushbu sahifaning barcha tashrif buyuruvchilari bir-birining xabarlarini ko'radi.
<form name="publish">
<input type="text" name="message" />
<input type="submit" value="Yuborish" />
</form>
<div id="subscribe"></div>
<script>
new PublishForm(document.forms.publish, "publish");
// keshlash muammolarini oldini olish uchun tasodifiy url parametri
new SubscribePane(
document.getElementById("subscribe"),
"subscribe?random=" + Math.random()
);
</script>
Brauzer kodi browser.js
da.
Foydalanish sohasi
Long polling xabarlar kamdan-kam kelganda juda yaxshi ishlaydi.
Agar xabarlar juda tez-tez kelsa, yuqorida chizilgan so’rov-qabul qilish xabarlari jadvali arra ko’rinishida bo’ladi.
Har bir xabar – bu header’lar, autentifikatsiya yuklamasi va boshqalar bilan ta’minlangan alohida so’rov.
Shunday qilib, bu holatda Websocket yoki Server Sent Events kabi boshqa usullar afzalroq.
Izohlar
<code>
yorlig'ini ishlating, bir nechta satrlar uchun - ularni<pre>
yorlig'i bilan o'rab qo'ying, 10 satrdan ortiq bo'lsa - sandbox (plnkr, jsbin, codepen…)