IlmHamroh
JavaScript Full-stack/29-qism. Backend integratsiyalar: fayl, to'lov, bot, realtime39/69-dars18 daqiqa
Mundarija (30)

WebSocket protokoli: HTTP Upgrade, kadrlar va ws kutubxonasi

Qisqacha: WebSocket oddiy HTTP so'rov bilan boshlanadi: mijoz Upgrade: websocket deydi, server 101 Switching Protocols bilan rozi bo'ladi va shu TCP ulanish ikki tomonlama kadrlar kanaliga aylanadi. Node'da asosiy kutubxona — ws. Xavfsizlik va barqarorlik uchun to'rt narsa majburiy: auth va Origin tekshiruvi upgrade paytida, maxPayload limiti, ping/pong bilan o'lik ulanishlarni topish va bufferedAmount bilan sekin mijozni nazorat qilish. Kanon yana bir topilma berdi: error ishlovchisi bo'lmasa, bitta yomon mijoz butun serverni yiqitadi.

Bu darsda

  • HTTP Upgrade va Sec-WebSocket-Accept ni Web Crypto bilan o'zingiz hisoblaysiz.
  • WebSocket kadrini baytlarda yasaysiz va niqobli kadrni ochasiz.
  • Kutubxonasiz 30 qatorlik WebSocket server yozib, Node'ning o'rnatilgan WebSocket mijozi bilan gaplashasiz.
  • ws da auth va Origin ni upgrade paytida tekshirasiz, close kodlarini bilasiz.
  • Heartbeat va backpressure'ni kanon o'lchovlari bilan tushunasiz.

Oldin bilishingiz kerak: Realtime usullari va tanlov, net, dns va tls (TCP soket), Buffer va binar ma'lumot, crypto 1: tasodif, hash va HMAC, Backpressure.

Oldingi darsda SSE bilan server → mijoz oqimini qurdik. Ikki tomon ham tez-tez gapiradigan joyda (chat, o'yin) kerak bo'ladigan protokolni bugun baytlarigacha ochamiz.

1. Nega bu kerak?

Dilnoza bilan Aziz vazifa ustida yozishmoqda. Har xabar va har "yozmoqda…" belgisi ikki tomondan oqadi. SSE'da mijoz → server yo'nalishi har safar yangi HTTP so'rov bo'lardi: sarlavhalar, auth, marshrut. WebSocket'da esa ulanish bir marta ochiladi va ikki tomon ham bir necha baytlik kadrlar bilan gaplashadi.

O'xshatish: telefon qo'ng'irog'i. Raqam terish (HTTP so'rov) va "allo" (101 javob) — bir marta. Keyin ikki kishi xohlagan paytda gapiradi. Lekin qo'ng'iroq uzilganini sezish, juda uzun gapni to'xtatish, javob bermaydigan suhbatdoshni aniqlash — sizning ishingiz.

Bu darsning ko'p qismi o'sha "sizning ishingiz" haqida.

2. Handshake: HTTP'dan WebSocket'ga

2.1 So'rov va javob

Mijoz oddiy GET yuboradi, lekin maxsus sarlavhalar bilan:

http
GET /ws HTTP/1.1
Host: vazifalar.example.uz
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Origin: https://vazifalar.example.uz

Har sarlavha nima uchun kerak:

Sarlavha Kim yuboradi Vazifasi
Upgrade: websocket mijoz, keyin server "protokolni almashtiraylik" taklifi va roziligi
Connection: Upgrade mijoz, keyin server bu ulanish oddiy HTTP bo'lib qolmasin
Sec-WebSocket-Key mijoz tasodifiy 16 bayt (base64) — javobni tekshirish uchun
Sec-WebSocket-Version: 13 mijoz protokol versiyasi (RFC 6455)
Origin brauzer so'rov qaysi saytdan — xavfsizlik uchun muhim
Sec-WebSocket-Accept server kalitdan hisoblangan javob — keyingi bo'limda

Kanon net.Socket orqali xuddi shunday xom so'rov yubordi va ws 8.22.0 serveridan javob oldi (qatorlar | bilan birlashtirilgan):

text
HTTP/1.1 101 Switching Protocols | Upgrade: websocket | Connection: Upgrade | Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

101 dan keyin HTTP tugadi: shu TCP ulanishda endi WebSocket kadrlari oqadi.

sequenceDiagram
  participant M as Mijoz
  participant S as Server
  M->>S: GET /ws, Upgrade websocket, Key
  Note over S: auth va Origin tekshiruvi
  S-->>M: 101, Sec-WebSocket-Accept
  M->>S: matn kadri (niqobli)
  S-->>M: matn kadri (niqobsiz)
  S->>M: ping
  M-->>S: pong
  M->>S: close 1000
  S-->>M: close 1000

Diagrammaga qarang: HTTP faqat birinchi ikki qadamda. Auth aynan shu oraliqda — ulanish ochilishidan oldin tekshiriladi. Keyin ikki tomon ham istalgan paytda kadr yuboradi, ping/pong ulanish tirikligini tekshiradi, close kadrlari xayrlashuv.

2.2 Sec-WebSocket-Accept ni hisoblaymiz

Bu sarlavha — "men rostdan WebSocket serverman, tasodifan javob bergan oddiy HTTP server emasman" degan isbot. Qoida (RFC 6455): mijoz kalitiga o'zgarmas GUID qo'shiladi, SHA-1 hash olinadi va base64 qilinadi. Brauzerdagi Web Crypto bilan:

js
const GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";

async function qabulKaliti(kalit) {
  const baytlar = new TextEncoder().encode(kalit + GUID);
  const hash = await crypto.subtle.digest("SHA-1", baytlar);
  return btoa(String.fromCharCode(...new Uint8Array(hash)));
}

// RFC 6455 dagi namuna kalit
console.log(await qabulKaliti("dGhlIHNhbXBsZSBub25jZQ=="));

Konsolda:

text
s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

Natija RFC'dagi namuna va kanon serveri javobi bilan aynan bir xil. SHA-1 bugun parollar uchun yaroqsiz, lekin bu yerda maxfiylik yo'q — bu faqat protokol "imzosi". Xavfsizlik bermaydi: kalitni har kim ko'radi.

Tekshirib ko'ring: Sec-WebSocket-Accept hujumchidan himoya qiladimi?

Javob

Yo'q. U faqat ikki tomon bir xil protokolni tushunishini tasdiqlaydi — masalan, kesh proksi eski javobni qaytarib yubormasligi uchun. Kim ulanayotganini tekshirish (auth) va qaysi saytdan ulanayotganini tekshirish (Origin) — server zimmasida, pastdagi «Auth va Origin» bo'limida.

3. Kadrlar

3.1 Kadr tuzilishi

Upgrade'dan keyin ma'lumot kadrlar (frame) bilan yuboriladi. Kichik kadrning sarlavhasi atigi 2 bayt:

Bitlar Ma'nosi
1-baytning eng katta biti — FIN xabarning oxirgi bo'lagi
1-baytning past 4 biti — opcode 1 matn, 2 binar, 8 close, 9 ping, 10 pong
2-baytning eng katta biti — MASK yuk niqoblanganmi
2-baytning past 7 biti uzunlik: 0–125, 126 yoki 127

126 — "haqiqiy uzunlik keyingi 2 baytda", 127 — "keyingi 8 baytda". Mijoz → server kadrlari doim niqoblangan bo'ladi: 4 baytlik tasodifiy niqob va yukning har bayti niqob bilan XOR qilinadi. Server → mijoz kadrlari niqobsiz.

3.2 Kadrni yasaymiz va ochamiz

js
const hex = (b) =>
  [...b].map((x) => x.toString(16).padStart(2, "0")).join("");

// server → mijoz: niqobsiz matn kadri (uzunlik < 126)
function matnKadri(matn) {
  const yuk = new TextEncoder().encode(matn);
  const kadr = new Uint8Array(2 + yuk.length);
  kadr[0] = 0x80 | 0x1; // FIN=1, opcode=1 (matn)
  kadr[1] = yuk.length; // MASK=0
  kadr.set(yuk, 2);
  return kadr;
}

// mijoz → server: niqobli kadrni ochish
function niqobniOch(kadr) {
  const uzunlik = kadr[1] & 0x7f;
  const niqob = kadr.subarray(2, 6);
  const yuk = kadr.subarray(6, 6 + uzunlik);
  const ochiq = yuk.map((b, i) => b ^ niqob[i % 4]);
  return {
    fin: (kadr[0] & 0x80) !== 0,
    opcode: kadr[0] & 0x0f,
    niqobli: (kadr[1] & 0x80) !== 0,
    matn: new TextDecoder().decode(ochiq),
  };
}

console.log(hex(matnKadri("aks: salom")));

const niqob = [1, 2, 3, 4];
const salom = new TextEncoder().encode("salom");
const mijozKadri = new Uint8Array([
  0x81, 0x80 | salom.length, ...niqob,
  ...salom.map((b, i) => b ^ niqob[i % 4]),
]);
console.log(hex(mijozKadri));
console.log(niqobniOch(mijozKadri));

Konsolda:

text
810a616b733a2073616c6f6d
81850102030472636f6b6c
{ fin: true, opcode: 1, niqobli: true, matn: 'salom' }

Birinchi qatorni kanon natijasi bilan solishtiring:

text
server kadri (hex): 810a616b733a2073616c6f6d → FIN+text, niqobsiz, uzunlik 10 , matn: "aks: salom"

Bayt-baytgacha bir xil: 81 — FIN + matn, 0a — uzunlik 10, keyin aks: salom ning UTF-8 baytlari. Ikkinchi qatorda 85 = MASK biti + uzunlik 5, keyin niqob 01020304 va niqoblangan yuk. Kanon serveriga aynan shu niqob bilan salom yuborgan va aks: salom javobini olgan.

Niqob nega kerak? Shifrlash uchun emas — niqob kadrning o'zida ochiq turibdi. U eski kesh proksilarni "zaharlash" hujumidan himoya qiladi: brauzer kodi tarmoqqa oldindan bilinadigan baytlarni yubora olmasin.

3.3 Kutubxonasiz server

Shu bilim bilan eng oddiy WebSocket serverni yozsa bo'ladi. Mijoz — Node'ning o'rnatilgan WebSocket klassi (brauzerdagi bilan bir xil API):

js
import http from "node:http";
import { createHash } from "node:crypto";
import { once } from "node:events";

const GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
const server = http.createServer((req, res) => res.end("oddiy HTTP"));

server.on("upgrade", (req, socket) => {
  const kalit = req.headers["sec-websocket-key"];
  const qabul = createHash("sha1").update(kalit + GUID)
    .digest("base64");
  socket.write(
    "HTTP/1.1 101 Switching Protocols\r\n" +
    "Upgrade: websocket\r\nConnection: Upgrade\r\n" +
    `Sec-WebSocket-Accept: ${qabul}\r\n\r\n`,
  );
  socket.on("data", (kadr) => {
    const opcode = kadr[0] & 0x0f;
    if (opcode === 0x8) { socket.end(); return; } // close
    const n = kadr[1] & 0x7f; // < 126 deb faraz
    const niqob = kadr.subarray(2, 6);
    const matn = Buffer.from(
      kadr.subarray(6, 6 + n).map((b, i) => b ^ niqob[i % 4]),
    ).toString();
    console.log("server oldi:", matn);
    const javob = Buffer.from(`aks: ${matn}`);
    const bosh = Buffer.from([0x81, javob.length]);
    socket.write(Buffer.concat([bosh, javob]));
  });
});

server.listen(0, "127.0.0.1");
await once(server, "listening");
const ws = new WebSocket(`ws://127.0.0.1:${server.address().port}`);
ws.onopen = () => {
  console.log("mijoz: ochildi");
  ws.send("salom");
};
ws.onmessage = (e) => {
  console.log("mijoz oldi:", e.data);
  ws.close();
  server.close();
};

Konsolda:

text
mijoz: ochildi
server oldi: salom
mijoz oldi: aks: salom

Bu blok faqat Node'da (port ochadi) — o'z terminalingizda yurgizing. Ishladi, lekin productionga mutlaqo yaramaydi:

  • 125 baytdan uzun xabar, bo'lingan kadrlar (FIN=0), ping/pong, binar — yo'q;
  • bitta data hodisasida ikki kadr yoki yarim kadr kelishi mumkin — TCP oqim, kadr chegarasini bilmaydi (net, dns va tls);
  • limitlar, UTF-8 tekshiruvi, close kodlari — yo'q.

Bularning hammasini ws kutubxonasi qiladi. O'zingiz yozish — faqat tushunish uchun.

4. ws kutubxonasi

4.1 O'rnatish va ulash

bash
npm i ws@8.22.0

Ikki rejim bor: new WebSocketServer({ server }) — HTTP serverga o'zi ulanadi; { noServer: true } — upgrade'ni siz boshqarasiz. Kanon ikkinchisini tanladi: auth va Origin ni ulanish ochilishidan oldin tekshirish uchun.

4.2 Auth va Origin upgrade paytida

Kanon kodi (formatlangan, ma'nosi aynan):

js
import http from "node:http";
import { WebSocketServer } from "ws";

const srv = http.createServer((_q, r) => r.writeHead(404).end());
const wss = new WebSocketServer({
  noServer: true,
  maxPayload: 64 * 1024,
});
const TOKEN = "sessiya-123";
const ORIGIN = "https://vazifalar.example.uz";

srv.on("upgrade", (req, socket, head) => {
  // Auth va Origin — UPGRADE paytida (ulanish ochilmasdan):
  // cookie/sessiya yoki qisqa muddatli token (query).
  // Brauzer WS'ga Authorization sarlavhasi qo'ya olmaydi.
  const u = new URL(req.url, "http://x");
  if (req.headers.origin && req.headers.origin !== ORIGIN) {
    socket.end("HTTP/1.1 403 Forbidden\r\nConnection: close\r\n\r\n");
    return;
  }
  if (u.searchParams.get("t") !== TOKEN) {
    socket.end(
      "HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n",
    );
    return;
  }
  wss.handleUpgrade(req, socket, head, (ws) =>
    wss.emit("connection", ws, req));
});

Kanon uch xil ulanishni sinadi. Natija:

text
token yo'q         → rad: HTTP 401
noto'g'ri Origin   → rad: HTTP 403
to'g'ri            → ochildi

Nega aynan upgrade paytida? Ulanish ochilgandan keyin tekshirsangiz, hujumchi allaqachon xotira va fayl deskriptorini band qilgan. Upgrade'da rad etish — oddiy HTTP javob, arzon.

Token query'da — chunki brauzerdagi WebSocket sarlavha qo'sha olmaydi (SSE'dagi kabi). Lekin URL access loglarga yoziladi. Shuning uchun query'dagi token qisqa muddatli bo'lishi kerak (masalan, 30 soniyalik bir martalik "chipta"). Bir origin'da eng yaxshisi — sessiya cookie'si: brauzer uni upgrade so'roviga o'zi qo'shadi (28-qismda Cookie chuqur).

Diqqat: Origin tekshiruvi if (req.headers.origin && …) — sarlavha yo'q bo'lsa o'tkazadi. Brauzer har doim Origin yuboradi; uni yubormaydigan — boshqa server yoki skript (ws, curl). Ular uchun himoya — auth. Origin faqat brauzerdagi hujumdan himoya qiladi (pastdagi «Hujumchi nigohi»).

4.3 maxPayload va close kodlari

maxPayload: 64 * 1024 — bitta xabar 64 KB dan katta bo'lsa, ws ulanishni yopadi. Kanon 65 KB yubordi:

text
  [server ws error] WS_ERR_UNSUPPORTED_MESSAGE_LENGTH: Max payload size exceeded
65 KB xabar → close 1009 "" (1009 = Message Too Big)
mijoz close(4001) → 4001 "sessiya tugadi" (4000–4999 — ilova kodlari)

Ko'p uchraydigan close kodlari:

Kod Ma'nosi
1000 oddiy yopilish
1001 ketyapman (sahifa yopildi, server o'chmoqda)
1006 close kadrsiz uzildi (faqat mahalliy, yuborilmaydi)
1008 siyosat buzildi (masalan, ruxsat yo'q)
1009 xabar juda katta
1011 serverda kutilmagan xato
4000–4999 ilovangizning o'z kodlari

Kanon 4001 "sessiya tugadi" ni ishlatdi: mijoz bu kodni ko'rib qayta ulanmaydi, login sahifasiga o'tadi. 1006 da esa — qayta ulanishga urinadi.

4.4 Kanon topilmasi: error ishlovchisi majburiy

Yuqoridagi chiqishning birinchi qatori — server ulanishidagi error hodisasi. Kanon birinchi urinishda bu ishlovchini yozmagan edi. Natija: 64 KB dan katta xabar yuborgan bitta mijoz butun Node jarayonini yiqitdi. Sabab — EventEmitter chuqur darsidagi qoida: error hodisasini hech kim tinglamasa, u ushlanmagan istisno bo'lib otiladi.

js
wss.on("connection", (ws) => {
  // ishlovchisiz: maxPayload oshganda BUTUN jarayon yiqildi
  ws.on("error", (e) => log.warn({ kod: e.code }, e.message));
  ws.on("message", (d, binar) => { /* ... */ });
});

Har connection da birinchi qator — ws.on("error", …). Kanon smtp-server da ham xuddi shu xatoni topgan — bu Node kutubxonalarining umumiy tuzog'i.

5. Heartbeat: o'lik ulanishni topish

5.1 Muammo

Telefon metroga tushdi yoki Wi-Fi o'chdi. TCP serverga hech narsa demaydi: close kadri ham, xato ham kelmaydi. Server uchun ulanish "tirik" — xotirada turibdi, unga yozilgan xabarlar buferda yig'iladi. Bu yarim ochiq ulanish (half-open).

5.2 Ping/pong

WebSocket'da buning uchun maxsus kadrlar bor: server ping yuboradi, mijoz avtomatik pong qaytaradi (brauzer buni o'zi qiladi). Kanon naqshi (formatlangan):

js
const tirik = new Map();
wss.on("connection", (ws) => {
  tirik.set(ws, true);
  ws.on("pong", () => tirik.set(ws, true));
});

function sikl() {
  for (const ws of wss.clients) {
    if (!tirik.get(ws)) { ws.terminate(); continue; }
    tirik.set(ws, false);
    ws.ping();
  }
}
setInterval(sikl, 30_000);

Har siklda: oldingi ping'ga javob bermaganlar uziladi, qolganlarga yangi ping. Kanon autoPong: false mijoz bilan sinadi (pong qaytarmaydigan "o'lik" mijoz taqlidi), sikl qisqartirilgan:

text
autoPong:false mijoz: uzildi (253 ms, prodda tsikl ~30 s)

Ikki siklda uzildi. Productionda sikl odatda 30 soniya: o'lik ulanish ko'pi bilan ~1 daqiqa yashaydi. terminate() — close kadrsiz darhol uzish (javob bermaydigan mijoz bilan "xayrlashish" ma'nosiz).

Mijoz tomonda ham shunday mantiq kerak: brauzer serverning ping'iga javob beradi, lekin server o'lganini o'zi sezmaydi. Socket.IO bu ikki tomonni o'zi qiladi (Socket.IO).

6. Backpressure: bufferedAmount

6.1 Kanon o'lchovi

ws.send() darhol qaytadi — ma'lumot tarmoqqa ketguncha xotirada navbatda turadi. Mijoz o'qimasa (sekin internet, osilib qolgan tab), navbat o'sadi. Kanon mijoz soketini to'xtatib (pause()), unga 50 × 1 MB yubordi:

text
bufferedAmount tekshirmasdan 50 × 1 MB: server xotirasida navbat 48.0 MB (eng ko'pi 48.0 MB)
chegara 4 MB bilan: 50 ta xabar yuborilmadi (tashlash/yig'ish yoki mijozni uzish — siyosat)

Bitta sekin mijoz — 48 MB server xotirasi. Ming shunday mijoz — server o'ladi.

6.2 Siyosat

ws.bufferedAmount — hali yuborilmagan baytlar. Yuborishdan oldin tekshiring:

js
const CHEGARA = 4 * 1024 * 1024;
function xavfsizYubor(ws, xabar) {
  if (ws.bufferedAmount > CHEGARA) {
    ws.terminate(); // yoki: tashlash, yig'ib keyin yuborish
    return false;
  }
  ws.send(xabar);
  return true;
}

Uch siyosat bor, tanlov xabar turiga bog'liq:

  1. Tashlash — "yozmoqda…", kursor joyi, narx tikeri: eskisi kerak emas, yangisi keladi.
  2. Yig'ish — bir nechta yangilanishni bittaga birlashtirish ("5 ta yangi vazifa").
  3. Uzish — mijoz qayta ulanib, yo'qotganini so'raydi. SSE'dagi SseHub ham shunday qiladi (256 KB).

Bu Backpressure darsidagi write() → false g'oyasining WebSocket varianti.

7. Hujumchi nigohi

Hujum Himoya Kanon
begona sayt foydalanuvchi cookie'si bilan ulanadi (CSWSH) Origin oq ro'yxati upgrade'da 403
tokensiz ulanish auth upgrade'da 401
1 GB xabar maxPayload close 1009
xato kadr bilan serverni yiqitish ws.on("error") topilma
minglab ochiq, jim ulanish ping/pong, ulanish limiti 2 siklda uzildi
o'qimaydigan mijoz bufferedAmount chegarasi 48 MB → 4 MB

CSWSH (Cross-Site WebSocket Hijacking) — eng nozigi. WebSocket CORS qoidalariga bo'ysunmaydi. Hujumchi saytidagi skript new WebSocket("wss://vazifalar.example.uz/ws") qiladi — brauzer upgrade so'roviga sizning cookie'ingizni qo'shadi. Server faqat cookie'ni tekshirsa, hujumchi sizning nomingizdan gaplashadi. Yagona belgi — Origin sarlavhasi: brauzer uni soxtalashtirishga ruxsat bermaydi. Shuning uchun cookie bilan auth qilsangiz, Origin tekshiruvi majburiy (CORS chuqur darsidagi Origin bilan bir xil g'oya).

Yana ikki qoida: wss:// (TLS) — productionda doim; har kiruvchi xabarni Zod bilan validatsiya qiling — WebSocket xabari ham foydalanuvchi kiritgan ma'lumot.

8. Ko'p uchraydigan xatolar

Xato Natijasi To'g'risi
ws.on("error") yo'q bitta mijoz jarayonni yiqitadi har ulanishda ishlovchi
auth connection dan keyin resurs band bo'lgach rad upgrade hodisasida
Origin tekshirilmaydi CSWSH oq ro'yxat
uzun muddatli token query'da loglarda sizadi qisqa muddatli chipta yoki cookie
ping/pong yo'q yarim ochiq ulanishlar yig'iladi 30 s sikl, terminate()
bufferedAmount ga qaramaslik sekin mijoz xotirani yeydi chegara + siyosat
nginx'da Upgrade sarlavhasi uzatilmaydi ulanish 400/404 proxy_set_header Upgrade (33-qismda)

9. Mashqlar

Mashqlar kurs/mashqlar/29/39-websocket/ da.

1-mashq (oson): to'g'ri yoki noto'g'ri

  1. Sec-WebSocket-Accept hujumchini to'xtatadi.
  2. Mijoz → server kadrlari doim niqoblangan.
  3. Brauzerdagi WebSocket ga Authorization sarlavhasini qo'shsa bo'ladi.
  4. Close kodi 1006 tarmoq orqali yuboriladi.
  5. ws.send() qaytgan bo'lsa, xabar mijozga yetgan.
Yechim
  1. Noto'g'ri — u faqat protokol tasdig'i; kalit ochiq.
  2. To'g'ri — RFC 6455 talabi; niqobsiz kadrni server rad etadi.
  3. Noto'g'ri — faqat URL va subprotokol; auth — cookie yoki qisqa muddatli token.
  4. Noto'g'ri — 1006 "close kadrsiz uzildi" degani, u faqat mahalliy hodisada ko'rinadi.
  5. Noto'g'ri — u faqat navbatga qo'yadi; bufferedAmount buni ko'rsatadi.

2-mashq (o'rta): kadr sarlavhasini o'qish

kadrSarlavhasi(b) funksiyasini yozing: Uint8Array dan { fin, opcode, niqobli, uzunlik, bosh } qaytarsin, bosh — yuk boshlanadigan bayt raqami. Uzunlik 126 bo'lsa — keyingi 2 bayt (big-endian), 127 — keyingi 8 bayt (DataView.getBigUint64, natijani Number ga). Niqobli bo'lsa bosh ga 4 qo'shiladi. To'rt holat: 81 05, 82 fe 01 2c, 89 80 va 70 000 baytlik binar kadrning boshi. Ishora: bit operatorlari &, |, << (Bitwise operatorlar).

Yechim
js
function kadrSarlavhasi(b) {
  const fin = (b[0] & 0x80) !== 0;
  const opcode = b[0] & 0x0f;
  const niqobli = (b[1] & 0x80) !== 0;
  let uzunlik = b[1] & 0x7f;
  let bosh = 2;
  if (uzunlik === 126) {
    uzunlik = (b[2] << 8) | b[3]; // keyingi 2 bayt, big-endian
    bosh = 4;
  } else if (uzunlik === 127) {
    uzunlik = Number(new DataView(b.buffer, b.byteOffset)
      .getBigUint64(2));
    bosh = 10;
  }
  if (niqobli) bosh += 4;
  return { fin, opcode, niqobli, uzunlik, bosh };
}

console.log(kadrSarlavhasi(new Uint8Array([0x81, 0x05])));
console.log(kadrSarlavhasi(new Uint8Array([0x82, 0xfe, 0x01, 0x2c])));
console.log(kadrSarlavhasi(new Uint8Array([0x89, 0x80])));
const katta = new Uint8Array(10);
katta.set([0x02, 0x7f]);
new DataView(katta.buffer).setBigUint64(2, 70000n);
console.log(kadrSarlavhasi(katta));

Konsolda:

text
{ fin: true, opcode: 1, niqobli: false, uzunlik: 5, bosh: 2 }
{ fin: true, opcode: 2, niqobli: true, uzunlik: 300, bosh: 8 }
{ fin: true, opcode: 9, niqobli: true, uzunlik: 0, bosh: 6 }
{ fin: false, opcode: 2, niqobli: false, uzunlik: 70000, bosh: 10 }

0x012c = 300. Uchinchisi — mijozning bo'sh ping kadri (opcode 9). To'rtinchisida FIN=0: bu katta xabarning birinchi bo'lagi, davomi keyingi kadrlarda. ws aynan shu tekshiruvlarni qiladi va uzunlik > maxPayload bo'lsa yukni o'qimasdan 1009 bilan yopadi.

3-mashq (qiyin): heartbeat sikli

Tarmoqsiz, kanon sikl mantig'ini modellang. Uch mijoz bor, har siklda pong qaytaradimi — berilgan: Aziz [true, true, true], Malika [true, false, false], Bobur [false, false, false]. Boshida hammasi "tirik". Har siklda: tirik emas — uziladi va N-sikl: nom uzildi chiqadi; aks holda "ping yuborildi" (tirik = false) va javob kelsa tirik = true. 3 sikldan keyin qolganlarni chiqaring. Ishora: Map, [...map] bilan nusxa ustida yurish (Map).

Yechim
js
// mijoz pong qaytaradimi: har sikl uchun true/false
const javoblar = {
  aziz: [true, true, true],
  malika: [true, false, false],
  bobur: [false, false, false],
};

const tirik = new Map(Object.keys(javoblar).map((n) => [n, true]));

function sikl(raqam) {
  for (const [nom, holat] of [...tirik]) {
    if (!holat) {
      tirik.delete(nom);
      console.log(`${raqam}-sikl: ${nom} uzildi`);
      continue;
    }
    tirik.set(nom, false); // ping yubordik
    if (javoblar[nom][raqam - 1]) tirik.set(nom, true); // pong keldi
  }
}

for (let i = 1; i <= 3; i++) sikl(i);
console.log("qoldi:", [...tirik.keys()].join(", "));

Konsolda:

text
2-sikl: bobur uzildi
3-sikl: malika uzildi
qoldi: aziz

Bobur birinchi sikldan beri jim, lekin faqat ikkinchi siklda uzildi: birinchisida ping endi yuborildi. Malika 2-siklda javob bermadi — 3-siklda uzildi. Demak o'lik ulanish 1–2 sikl yashaydi: 30 soniyalik siklda ko'pi bilan ~60 soniya. Kanondagi autoPong: false mijoz ham aynan 2 siklda uzildi.

10. Loyiha qadami: kanon tajribasi

vazifalar-api ga WebSocket kirmaydi: Realtime usullari va tanlov darsidagi qaror bo'yicha vazifa o'zgarishlari SSE orqali. Bu darsdagi hamma natija — kanon o'lchovidan (IlmHamroh, 2026-10) (ws 8.22.0, Node 24.21.0, 2026-10-27): xom upgrade, auth va Origin, maxPayload, close kodlari, heartbeat va backpressure. Kutubxonasiz server, Sec-WebSocket-Accept va kadr hisoblari — biz ishga tushirdik.

Ikki tomonlama kanal kerak bo'ladigan joy — chat. Uni Amaliyot: chat va bildirishnomalar darsida Socket.IO bilan quramiz; kanon qarori bo'yicha chat — 38-qismdagi jamoa loyihasi uchun naqsh.

11. Real ishda

  • ws — Node'dagi eng ko'p yuklanadigan WebSocket kutubxonasi; Socket.IO, ko'plab freymvorklar ichida ham u.
  • Node 22+ da WebSocket mijozi global (brauzer API'si) — server uchun baribir kutubxona kerak.
  • Birja, o'yin, hamkorlik vositalari (Figma, Google Docs) ikki tomonlama kanal sifatida WebSocket ishlatadi.
  • Intervyuda: "WebSocket handshake qanday ishlaydi?", "CSWSH nima?", "Yarim ochiq ulanishni qanday topasiz?", "Sekin mijoz bilan nima qilasiz?"

Xulosa

  • WebSocket HTTP GET + Upgrade bilan boshlanadi; 101 dan keyin ulanish kadrlar kanaliga aylanadi.
  • Sec-WebSocket-Accept = base64(SHA-1(kalit + GUID)) — protokol tasdig'i, xavfsizlik emas.
  • Kadr: FIN, opcode, MASK, uzunlik (7/16/64 bit); mijoz kadrlari doim niqoblangan.
  • Auth va Origin — upgrade hodisasida; Origin — CSWSH'dan yagona himoya.
  • maxPayload (1009), ws.on("error") (aks holda jarayon yiqiladi), ping/pong (2 siklda uzish), bufferedAmount (48 MB → chegara).

Keyingi dars: Socket.IO — WebSocket ustidagi qatlam: hodisalar, xonalar, ack va avtomatik qayta ulanish; uning tezligini SSE va ws bilan o'lchab solishtiramiz.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!