WebSocket — ws moduli bilan, handshake, event lar

~20 daq · Express · Real-time

Muammodan boshlaylik

Chat ilovasi yasayapsiz. Foydalanuvchi xabar yuborganda — boshqalar darhol ko'rishi kerak.

HTTP bilan bu qanday ishlaydi?

Ali:  "Salom" yubordi
Vali: Yangi xabar bormi? (so'rov)  → "yo'q"
Vali: Yangi xabar bormi? (so'rov)  → "yo'q"
Vali: Yangi xabar bormi? (so'rov)  → "ha, Ali salom dedi"

Vali har soniyada server dan so'rab turadi. 1000 foydalanuvchi bo'lsa — sekundiga 1000 ta so'rov. Ko'pchiligi "yo'q" javob qaytaradi. Server befoyda ishlarni qiladi.

Asosiy muammo: HTTP — bir tomonlama. Siz so'raysiz, server javob beradi. Server o'zi xabar yubora olmaydi.

WebSocket bu muammoni tubdan hal qiladi.


WebSocket nima?

WebSocket — server va mijoz o'rtasida doimiy, ikki tomonlama ulanish.

Hayotiy o'xshatma

HTTP va WebSocket ni telefon qo'ng'irog'i bilan tushuntiraylik:

HTTP — SMS. Har xabar uchun alohida "qo'ng'iroq":

Ali → Server: "Salom"   (ulanish → so'rov → javob → ulanish yopiladi)
Ali → Server: "Qalaysiz?" (yana ulanish → so'rov → javob → yopiladi)

WebSocket — telefon qo'ng'irog'i. Bir marta bog'lanasiz, keyin ikkalangiz xohlagan vaqtda gaplashasiz:

Ali ←→ Server: ulanish o'rnatildi
Ali → Server: "Salom"
Server → Ali: "Yaxshi"
Server → Ali: "Vali ham kirdi"   ← server o'zi xabar yubordi!
Ali → Server: "Privet Vali"

HTTP vs WebSocket — texnik farq

HTTP:
Mijoz ──(so'rov)──► Server
Mijoz ◄──(javob)── Server
[ulanish yopiladi]

WebSocket:
Mijoz ◄──────────────────► Server
      [doimiy ochiq kanal]
      ikkalasi ham istalgan
      vaqtda xabar yuboradi

Handshake — qanday boshlanadi?

WebSocket HTTP dan boshlanadi — bu ajablanarli. Lekin mantiqli sababi bor: HTTP ni barcha brauzerlar, serverlar, proxylar tushunadi. WebSocket shu yo'l orqali "upgrade" so'raydi.

1. Mijoz HTTP so'rov yuboradi:
   GET /chat HTTP/1.1
   Upgrade: websocket          ← "HTTP dan WebSocket ga o'tmoqchiman"
   Connection: Upgrade
   Sec-WebSocket-Key: abc123   ← tasodifiy kalit

2. Server rozi bo'lsa:
   HTTP/1.1 101 Switching Protocols   ← "kelishdik"
   Upgrade: websocket
   Sec-WebSocket-Accept: xyz789       ← kalitdan hosil qilingan javob

3. Endi HTTP emas — WebSocket ulanishi boshlanadi

101 Switching Protocols — bu maxsus HTTP status kodi. "Men HTTP dan boshqa protokolga o'tyapman" degani. Shundan keyin HTTP tugadi, WebSocket boshlandi.


ws moduli — eng oddiy misol

npm install ws
// server.js
const WebSocket = require('ws');

const wss = new WebSocket.Server({ port: 8080 });

// Yangi ulanish kelganda
wss.on('connection', (socket) => {
  console.log('Yangi mijoz ulandi');

  // Mijozdan xabar kelganda
  socket.on('message', (data) => {
    console.log('Keldi:', data.toString());

    // Javob qaytarish
    socket.send('Xabaringizni oldim!');
  });

  // Ulanish uzilganda
  socket.on('close', () => {
    console.log('Mijoz chiqib ketdi');
  });
});
// client.js (Node.js yoki brauzer)
const ws = new WebSocket('ws://localhost:8080');

ws.on('open', () => {
  console.log('Ulandi!');
  ws.send('Salom server!');
});

ws.on('message', (data) => {
  console.log('Serverdan:', data.toString());
});

ws.on('close', () => {
  console.log('Ulanish uzildi');
});

Express bilan birga ishlatish

Ko'pincha WebSocket va HTTP server bir portda ishlashi kerak:

// app.js
const express    = require('express');
const http       = require('http');
const WebSocket  = require('ws');

const app    = express();
const server = http.createServer(app); // ← Express ni HTTP serverga o'rash
const wss    = new WebSocket.Server({ server }); // ← Xuddi shu serverda WS

// HTTP route (oddiy)
app.get('/', (req, res) => {
  res.send('Salom!');
});

// WebSocket ulanishlari
wss.on('connection', (socket) => {
  socket.send('Xush kelibsiz!');

  socket.on('message', (data) => {
    console.log('Xabar:', data.toString());
  });
});

// Bitta server — ikkala protokol
server.listen(3000, () => {
  console.log('Server 3000 portda');
  // HTTP:      http://localhost:3000
  // WebSocket: ws://localhost:3000
});

Real misol: oddiy chat

Har bir kelgan xabarni barcha ulangan mijozlarga yuborish — broadcast:

// server.js
const express   = require('express');
const http      = require('http');
const WebSocket = require('ws');

const app    = express();
const server = http.createServer(app);
const wss    = new WebSocket.Server({ server });

// Yordamchi: barcha ulanishli mijozlarga yuborish
const broadcast = (message, senderSocket = null) => {
  wss.clients.forEach((client) => {
    // Faqat ulanish ochiq bo'lsa va yuborgandan boshqasiga
    if (client.readyState === WebSocket.OPEN && client !== senderSocket) {
      client.send(message);
    }
  });
};

wss.on('connection', (socket) => {
  console.log('Ulandi. Jami:', wss.clients.size);

  // Yangi odam kirdi — hammaga xabar
  broadcast(JSON.stringify({ type: 'info', text: 'Yangi foydalanuvchi kirdi' }));

  socket.on('message', (data) => {
    const message = data.toString();

    // Barcha boshqalarga yuborish
    broadcast(JSON.stringify({ type: 'message', text: message }), socket);
  });

  socket.on('close', () => {
    console.log('Chiqdi. Qoldi:', wss.clients.size);
    broadcast(JSON.stringify({ type: 'info', text: 'Foydalanuvchi chiqdi' }));
  });
});

server.listen(3000);

Brauzerda:

// Frontend (brauzer)
const ws = new WebSocket('ws://localhost:3000');

ws.onopen = () => {
  console.log('Ulandi!');
};

ws.onmessage = (event) => {
  const data = JSON.parse(event.data);

  if (data.type === 'message') {
    console.log('Xabar:', data.text);
  } else if (data.type === 'info') {
    console.log('Ma\'lumot:', data.text);
  }
};

// Xabar yuborish
document.getElementById('send').onclick = () => {
  const text = document.getElementById('input').value;
  ws.send(text);
};

socket.readyState — ulanish holati

// Xabar yuborishdan oldin ulanish ochiqligini tekshiring
if (socket.readyState === WebSocket.OPEN) {
  socket.send('Xabar');
}

// To'rtta holat:
// WebSocket.CONNECTING  (0) — ulanilmoqda
// WebSocket.OPEN        (1) — ochiq, yuborish mumkin
// WebSocket.CLOSING     (2) — yopilmoqda
// WebSocket.CLOSED      (3) — yopilgan

Arxitektura — katta loyiha uchun

project/
├── server.js              ← HTTP + WS server
├── websocket/
│   ├── index.js           ← wss sozlamasi, connection handler
│   └── handlers/
│       ├── chatHandler.js ← chat xabarlari
│       └── infoHandler.js ← tizim xabarlari
└── app.js                 ← Express routelar
// websocket/index.js
const WebSocket = require('ws');
const chatHandler = require('./handlers/chatHandler');

const initWebSocket = (server) => {
  const wss = new WebSocket.Server({ server });

  wss.on('connection', (socket) => {
    chatHandler(socket, wss); // ← logika alohida faylda
  });

  return wss;
};

module.exports = initWebSocket;
// websocket/handlers/chatHandler.js
const WebSocket = require('ws');

module.exports = (socket, wss) => {
  socket.on('message', (data) => {
    let parsed;

    try {
      parsed = JSON.parse(data);
    } catch {
      socket.send(JSON.stringify({ error: 'Noto\'g\'ri format' }));
      return;
    }

    if (parsed.type === 'chat') {
      // Barcha ochiq ulanishlarga yuborish
      wss.clients.forEach((client) => {
        if (client.readyState === WebSocket.OPEN) {
          client.send(JSON.stringify({
            type: 'chat',
            text: parsed.text,
            time: new Date().toISOString(),
          }));
        }
      });
    }
  });

  socket.on('close', () => {
    console.log('Foydalanuvchi chiqdi');
  });
};
// server.js
const http         = require('http');
const app          = require('./app');
const initWebSocket = require('./websocket');

const server = http.createServer(app);
initWebSocket(server); // ← WebSocket ulanadi

server.listen(3000);

Tez-tez uchraydigan xatolar

1. readyState tekshirmasdan yuborish

// ❌ — ulanish yopiq bo'lsa xato chiqadi
socket.send('Xabar');
// ✅
if (socket.readyState === WebSocket.OPEN) {
  socket.send('Xabar');
}

Sababi: ulanish to'satdan uzilishi mumkin. Yopiq socketga send() — Error: WebSocket is not open xatosi.

2. Xomdan matn yuborish — JSON ishlatmay

// ❌ — tur aniqlanmagan, kengaytirilmaydi
socket.send('salom');
socket.send('yangi_foydalanuvchi:Ali');  // ← o'z formatini ixtiro qilish
// ✅ — JSON: har xabar turga ega, kengaytiriladi
socket.send(JSON.stringify({ type: 'chat', text: 'salom', from: 'Ali' }));
socket.send(JSON.stringify({ type: 'join', username: 'Ali' }));
socket.send(JSON.stringify({ type: 'leave', username: 'Ali' }));

Sababi: JSON bilan turga qarab ishlash mumkin. Matn bilan murakkablashganda parse qilish qiyin.

3. try/catch siz JSON.parse

// ❌ — noto'g'ri JSON kelsa server crash
socket.on('message', (data) => {
  const parsed = JSON.parse(data); // ← xato chiqsa to'xtaydi
});
// ✅
socket.on('message', (data) => {
  try {
    const parsed = JSON.parse(data);
    // ...
  } catch {
    socket.send(JSON.stringify({ error: 'JSON format noto\'g\'ri' }));
  }
});

4. Xabar yuborganning o'ziga qaytarish

// ❌ — chat da o'z xabaringiz ikki marta ko'rinadi
wss.clients.forEach((client) => {
  client.send(message); // ← yuborganga ham qaytadi
});
// ✅ — yuborgandan boshqasiga
wss.clients.forEach((client) => {
  if (client !== socket && client.readyState === WebSocket.OPEN) {
    client.send(message);
  }
});

Xulosa

  • WebSocket — doimiy, ikki tomonlama kanal: server ham, mijoz ham istagan vaqtda xabar yuboradi
  • Handshake — HTTP orqali boshlanadi (101 Switching Protocols), keyin WebSocket
  • wss.clients — barcha ulangan mijozilar, forEach bilan broadcast
  • Har doim readyState === OPEN tekshiring
  • Xabarlarni JSON formatida yuboring — tur (type) bilan
  • Katta loyihada: handler larni alohida fayllarga ajrating

Keyingi dars: 43-dars: Socket.io — o'rnatish, room, namespace, broadcast