Men uni haqiqatan tushunish uchun xom TCP-dan WebSocket serverni qurdim
Nashr etilgan 5-avgust, 2025 · 7 daqiqa o'qish
Nima uchun allaqachon mavjud narsani qurish kerak?
Halol javob: chunki men yillar davomida WebSocket-lardan foydalanardim va hali ham "frame nima?" yoki "masking nima uchun mavjud?" ga javob bera olmardim.
Shuning uchun men noServer-ni qurdim: xom TCP-da noldan Node.js HTTP va WebSocket serveri. Express yo'q, ws kutubxonasi yo'q — faqat Node-ning net moduli va RFC.
node server.js # :3000-da tinglayapti # GET / → 200 # GET /ws → 101 Switching Protocols
Xom TCP — boshlash joyi
Node-ning net.createServer() har bir ulanish uchun soket beradi. Bu soket kiruvchi baytlar, chiquvchi baytlar. Protokol yo'q, freymlash yo'q.
import net from 'net';
const server = net.createServer((socket) => {
socket.on('data', (chunk: Buffer) => {
// Xom baytlar. HTTP bo'lishi mumkin. WebSocket bo'lishi mumkin.
});
});
server.listen(3000);Birinchi vazifa: bu oddiy so'rovmi yoki yangilash so'rovimi aniqlash uchun kiruvchi baytlarni HTTP sifatida tahlil qilish.
Handshake
RFC 6455 sehrli GUID-ni belgilaydi: 258EAFA5-E914-47DA-95CA-C5AB0DC85B11. Uning yagona maqsadi: oddiy HTTP serverlar tasodifan yangilash so'rovlarini qabul qilmasligi uchun.
import crypto from 'crypto';
const MAGIC = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
function acceptKey(clientKey: string): string {
return crypto
.createHash('sha1')
.update(clientKey + MAGIC)
.digest('base64');
}101 javobidan keyin ulanish yangilangan. Endi HTTP yo'q. Bu yerdan WebSocket freymlash protokoli.
Freymlar: haqiqiy protokol
WebSocket xabari freymlariga bo'linadi. Har bir freymda ikkilik sarlavha, keyin yuk bor.
function parseFrame(buffer: Buffer) {
const fin = (buffer[0] & 0x80) !== 0;
const opcode = buffer[0] & 0x0f;
const masked = (buffer[1] & 0x80) !== 0;
let payloadLen = buffer[1] & 0x7f;
let offset = 2;
if (payloadLen === 126) {
payloadLen = buffer.readUInt16BE(offset);
offset += 2;
}
const mask = masked ? buffer.slice(offset, offset + 4) : null;
if (masked) offset += 4;
const payload = buffer.slice(offset, offset + payloadLen);
return { fin, opcode, payload, mask };
}Masking va nima uchun mavjud
Mijoz freymlariga masking LOZIM. Server freymlariga masking KERAK EMAS. Bu ixtiyoriy emas.
Masking 4 baytli kalit bilan XOR:
function unmask(payload: Buffer, mask: Buffer): Buffer {
const result = Buffer.alloc(payload.length);
for (let i = 0; i < payload.length; i++) {
result[i] = payload[i] ^ mask[i % 4];
}
return result;
}Bu shifrlash emas. Bu xavfsizlik emas. Uning yagona maqsadi: WebSocket freymlarining HTTP kabi ko'rinishining oldini olish, proksi-serverlar ularni keshlash yoki o'zgartirmasligi uchun.
Endi bilgan narsalarim
ws kutubxonasi taxminan 2000 qator. noServer qurganidan keyin, bu qatorlarning ko'pchiligi nima qilayotganini tushunaman. Nima uchun ping/pong mavjud (aloqani tirik saqlash va o'lik ulanishlarni aniqlash), nima uchun FIN biti mavjud (katta xabar parchalanishi) va nima uchun masking faqat mijozga tegishliligini tushunaman.
Men production-da hali ham ws ishlataman. Bu kutubxonalarni almashtirish haqida emas edi — ularni tushunish haqida edi.