WebSockets vs SSE vs Long Polling: realtime технологиясын кантип тандоо керек
Маалыматтарды реалдуу убакытта көрсөтүү керек болгондо - чат, заказ статусу, онлайн эсептегич, кабарлар - кайсы технологияны тандоо маселеси туулат. Ар бири өзүнүн сценарийлери, чектөөлөрү жана компромисстери бар.
Кысканын кыскасы
| Критерий | WebSocket | SSE | Long Polling |
|---|---|---|---|
| Багыт | Эки тараптуу | Сервер → Клиент | Сервер → Клиент |
| Протокол | ws:// wss:// | HTTP | HTTP |
| Автоматтык кайра туташуу | Жок (кол менен) | Ооба (браузер) | Реализацияга жараша |
| Прокси/балансировщик | Жөндөшүү керек | Нативдик | Нативдик |
| Серверге жүктөм | Төмөн (туруктуу туташуу) | Төмөн | Жогору |
| Аткаруунун татаалдыгы | Жогору | Төмөн | Орто |
Long Polling - эң жөнөкөй вариант
Клиент HTTP суроо жибергет. Сервер маалымат пайда болгонго же таймаут болгонго чейин туташууну ачык кармайт, андан соң жооп берет.
// Сервер (Express)
const pendingClients = new Map<string, Response>();
app.get('/api/notifications/poll', authenticate, async (req, res) => {
const userId = req.user.id;
res.setTimeout(30_000);
pendingClients.set(userId, res);
req.on('close', () => {
pendingClients.delete(userId);
});
});
// Кабар келгенде - күтүп жаткан клиентке жибербиз
async function sendNotification(userId: string, data: object) {
const client = pendingClients.get(userId);
if (client) {
client.json(data);
pendingClients.delete(userId);
}
}
// Клиент
async function startPolling() {
while (true) {
try {
const response = await fetch('/api/notifications/poll', {
signal: AbortSignal.timeout(35_000),
});
if (response.ok) {
const data = await response.json();
handleNotification(data);
}
} catch {
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
}
Качан колдонуу: Сейрек жаңыртуулар, жөнөкөйлүк маанилүү, прокси жөндөшүү мүмкүн эмес.
Server-Sent Events (SSE)
Браузер HTTP туташуусун ачат жана окуяларды агым катары алат. Автоматтык кайра туташуу браузерде камтылган.
// Сервер
const sseClients = new Map<string, Response[]>();
app.get('/api/events', authenticate, (req, res) => {
const userId = req.user.id;
// SSE headers
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
res.setHeader('X-Accel-Buffering', 'no'); // Nginx үчүн
res.flushHeaders();
const clients = sseClients.get(userId) ?? [];
clients.push(res);
sseClients.set(userId, clients);
// Прокси таймаутун алдын алуу үчүн heartbeat
const heartbeat = setInterval(() => {
res.write(':ping\n\n');
}, 25_000);
req.on('close', () => {
clearInterval(heartbeat);
const remaining = (sseClients.get(userId) ?? []).filter(c => c !== res);
remaining.length === 0
? sseClients.delete(userId)
: sseClients.set(userId, remaining);
});
});
// Клиентке окуя жөнөтүү
function sendEvent(userId: string, eventType: string, data: object) {
const clients = sseClients.get(userId) ?? [];
const msg = `event: ${eventType}\ndata: ${JSON.stringify(data)}\n\n`;
clients.forEach(c => c.write(msg));
}
// Колдонуу:
sendEvent(userId, 'order_status', { orderId: '123', status: 'shipped' });
sendEvent(userId, 'notification', { text: 'Заказыңыз жөнөтүлдү!' });
// Клиент - браузер автоматтык кайра туташат
const eventSource = new EventSource('/api/events', { withCredentials: true });
eventSource.addEventListener('order_status', (e) => {
const data = JSON.parse(e.data);
updateOrderStatus(data.orderId, data.status);
});
eventSource.addEventListener('notification', (e) => {
const data = JSON.parse(e.data);
showNotification(data.text);
});
// Туташуу үзүлсө - браузер автоматтык кайра туташат
Качан колдонуу: Серверден клиентке гана жаңыртуулар, автоматтык кайра туташуу керек, HTTP балансировщик аркылуу иштейт.
WebSocket - эки тараптуу канал
WebSocket эки тараптуу маалымат алмашуу үчүн туруктуу туташуу түзөт. Чат, кооперативдик өзгөртүү, онлайн оюндар үчүн идеал.
import { WebSocketServer, WebSocket } from 'ws';
const wss = new WebSocketServer({ server });
interface AuthenticatedWS extends WebSocket {
userId?: string;
isAlive?: boolean;
}
const connectedUsers = new Map<string, Set<AuthenticatedWS>>();
wss.on('connection', async (ws: AuthenticatedWS, req) => {
// URL же cookie аркылуу аутентификация
const token = new URL(req.url!, 'ws://localhost').searchParams.get('token');
const userId = await verifyToken(token);
if (!userId) {
ws.close(4001, 'Уруксат жок');
return;
}
ws.userId = userId;
ws.isAlive = true;
const userConns = connectedUsers.get(userId) ?? new Set();
userConns.add(ws);
connectedUsers.set(userId, userConns);
ws.on('message', async (data) => {
try {
const message = JSON.parse(data.toString());
await handleMessage(ws, message);
} catch {
ws.send(JSON.stringify({ type: 'error', message: 'Жарамсыз формат' }));
}
});
ws.on('pong', () => { ws.isAlive = true; });
ws.on('close', () => {
const conns = connectedUsers.get(userId!);
conns?.delete(ws);
if (conns?.size === 0) connectedUsers.delete(userId!);
});
});
// Өлгөн туташууларды аныктоо
setInterval(() => {
wss.clients.forEach((ws: AuthenticatedWS) => {
if (!ws.isAlive) return ws.terminate();
ws.isAlive = false;
ws.ping();
});
}, 30_000);
// Колдонуучуга жөнөтүү (бардык табулатурлары/түзмөктөрүнө)
function sendToUser(userId: string, data: object) {
connectedUsers.get(userId)?.forEach(ws => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify(data));
}
});
}
Socket.io - ишенимдүүлүк керек болгондо
import { Server } from 'socket.io';
const io = new Server(server, {
cors: { origin: process.env.FRONTEND_URL, credentials: true },
transports: ['websocket', 'polling'], // polling запас вариант катары
});
io.use(async (socket, next) => {
try {
socket.data.userId = await verifyToken(socket.handshake.auth.token);
next();
} catch {
next(new Error('Уруксат жок'));
}
});
io.on('connection', (socket) => {
const { userId } = socket.data;
socket.join(`user:${userId}`);
socket.on('chat:message', async (data: { roomId: string; text: string }) => {
const message = await db.message.create({
data: { userId, roomId: data.roomId, text: data.text },
});
io.to(`room:${data.roomId}`).emit('chat:message', message);
});
});
// Конкреттүү колдонуучуга кабар
function notifyUser(userId: string, event: string, data: object) {
io.to(`user:${userId}`).emit(event, data);
}
Конкреттүү тапшырмалар үчүн тандоо
| Тапшырма | Сунуш |
|---|---|
| Push кабарлар | SSE |
| Заказ/жеткирүү статусу | SSE |
| Чат | WebSocket |
| Биргелешип өзгөртүү | WebSocket |
| Онлайн эсептегичтер | SSE |
| Онлайн оюндар | WebSocket (нативдик) |
| Сейрек жаңыртуулар | Long Polling |
Aunimeda бизнес тиркемелер үчүн realtime функционалды жүзөгө ашырат.
Ошондой эле: Telegram Bot FSM жана middleware, Docker Node.js production deploy