Mundarija (30)
- Bu darsda
- 1. Nega bu kerak?
- 2. Handshake: HTTP'dan WebSocket'ga
- 2.1 So'rov va javob
- 2.2 Sec-WebSocket-Accept ni hisoblaymiz
- 3. Kadrlar
- 3.1 Kadr tuzilishi
- 3.2 Kadrni yasaymiz va ochamiz
- 3.3 Kutubxonasiz server
- 4. ws kutubxonasi
- 4.1 O'rnatish va ulash
- 4.2 Auth va Origin upgrade paytida
- 4.3 maxPayload va close kodlari
- 4.4 Kanon topilmasi: error ishlovchisi majburiy
- 5. Heartbeat: o'lik ulanishni topish
- 5.1 Muammo
- 5.2 Ping/pong
- 6. Backpressure: bufferedAmount
- 6.1 Kanon o'lchovi
- 6.2 Siyosat
- 7. Hujumchi nigohi
- 8. Ko'p uchraydigan xatolar
- 9. Mashqlar
- 1-mashq (oson): to'g'ri yoki noto'g'ri
- 2-mashq (o'rta): kadr sarlavhasini o'qish
- 3-mashq (qiyin): heartbeat sikli
- 10. Loyiha qadami: kanon tajribasi
- 11. Real ishda
- Xulosa
- Manbalar
WebSocket protokoli: HTTP Upgrade, kadrlar va ws kutubxonasi
Qisqacha: WebSocket oddiy HTTP so'rov bilan boshlanadi: mijoz
Upgrade: websocketdeydi, server101 Switching Protocolsbilan 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 vaOrigintekshiruvi upgrade paytida,maxPayloadlimiti, ping/pong bilan o'lik ulanishlarni topish vabufferedAmountbilan sekin mijozni nazorat qilish. Kanon yana bir topilma berdi:errorishlovchisi bo'lmasa, bitta yomon mijoz butun serverni yiqitadi.
Bu darsda
- HTTP Upgrade va
Sec-WebSocket-Acceptni Web Crypto bilan o'zingiz hisoblaysiz. - WebSocket kadrini baytlarda yasaysiz va niqobli kadrni ochasiz.
- Kutubxonasiz 30 qatorlik WebSocket server yozib, Node'ning o'rnatilgan
WebSocketmijozi bilan gaplashasiz. wsda auth vaOriginni 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:
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.uzHar 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):
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 1000Diagrammaga 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:
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:
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-Accepthujumchidan 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
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:
810a616b733a2073616c6f6d
81850102030472636f6b6c
{ fin: true, opcode: 1, niqobli: true, matn: 'salom' }Birinchi qatorni kanon natijasi bilan solishtiring:
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):
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:
mijoz: ochildi
server oldi: salom
mijoz oldi: aks: salomBu 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
datahodisasida 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
npm i ws@8.22.0Ikki 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):
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:
token yo'q → rad: HTTP 401
noto'g'ri Origin → rad: HTTP 403
to'g'ri → ochildiNega 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:
Origintekshiruviif (req.headers.origin && …)— sarlavha yo'q bo'lsa o'tkazadi. Brauzer har doimOriginyuboradi; uni yubormaydigan — boshqa server yoki skript (ws,curl). Ular uchun himoya — auth.Originfaqat 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:
[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.
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):
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:
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:
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:
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:
- Tashlash — "yozmoqda…", kursor joyi, narx tikeri: eskisi kerak emas, yangisi keladi.
- Yig'ish — bir nechta yangilanishni bittaga birlashtirish ("5 ta yangi vazifa").
- Uzish — mijoz qayta ulanib, yo'qotganini so'raydi. SSE'dagi
SseHubham 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
Sec-WebSocket-Accepthujumchini to'xtatadi.- Mijoz → server kadrlari doim niqoblangan.
- Brauzerdagi
WebSocketgaAuthorizationsarlavhasini qo'shsa bo'ladi. - Close kodi 1006 tarmoq orqali yuboriladi.
ws.send()qaytgan bo'lsa, xabar mijozga yetgan.
Yechim
- Noto'g'ri — u faqat protokol tasdig'i; kalit ochiq.
- To'g'ri — RFC 6455 talabi; niqobsiz kadrni server rad etadi.
- Noto'g'ri — faqat URL va subprotokol; auth — cookie yoki qisqa muddatli token.
- Noto'g'ri — 1006 "close kadrsiz uzildi" degani, u faqat mahalliy hodisada ko'rinadi.
- Noto'g'ri — u faqat navbatga qo'yadi;
bufferedAmountbuni 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
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:
{ 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
// 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:
2-sikl: bobur uzildi
3-sikl: malika uzildi
qoldi: azizBobur 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
WebSocketmijozi 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 +
Upgradebilan boshlanadi;101dan 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—upgradehodisasida;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
- RFC 6455, The WebSocket Protocol: https://www.rfc-editor.org/rfc/rfc6455 (handshake, kadr formati va close kodlari bo'limlari)
- ws: https://github.com/websockets/ws (README: "How to detect and close broken connections",
noServer,maxPayload) - MDN, WebSocket: https://developer.mozilla.org/en-US/docs/Web/API/WebSocket
- OWASP WebSocket Security Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/WebSocket_Security_Cheat_Sheet.html
- Kanon tajribasi (IlmHamroh, 2026-10-27): ws 8.22.0, Node 24.21.0, Windows 11.
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!