6 сентябр 2025

Long Polling (Uzoq so'rov)

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:

  1. Xabarlar 10 soniyagacha kechikish bilan uzatiladi (so’rovlar orasida).
  2. 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:

  1. Serverga so’rov yuboriladi.
  2. Server yuborish uchun xabar bo’lgunga qadar ulanishni yopmaydi.
  3. Xabar paydo bo’lganda – server so’rovga u bilan javob beradi.
  4. 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 ko’plab kutilayotgan ulanishlar bilan ishlashga tayyor bo’lishi kerak

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):

Natija
browser.js
server.js
index.html
// 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.

O'quv qo'llanma xaritasi

Izohlar

izoh berishdan oldin buni o'qing…
  • Agar sizda nimani yaxshilash kerakligi haqida takliflaringiz bo'lsa - iltimos, GitHub muammosini yuboring yoki izoh berish o'rniga so'rov yuboring.
  • Agar siz maqolada biror narsani tushunolmasangiz - iltimos, batafsilroq ma'lumot bering.
  • Bir nechta so'z so'zlarini kiritish uchun <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…)