WebSocket Nedir? Gerçek Zamanlı Uygulamaların Arkasındaki Teknoloji
WhatsApp'ta mesaj gönderiyorsunuz, karşı taraf anında görüyor. Bir borsa uygulamasında fiyatlar sürekli güncelleniyor. Çok oyunculu bir oyunda rakibinizin her hareketi anında ekranınıza yansıyor.
Bunların hepsinin arkasında aynı soru var: sunucu, istemciye bir şey olduğunda nasıl haber veriyor? HTTP ile bu soruyu çözmek beklenenden çok daha zor.
HTTP'nin Gerçek Zamanlı Problemi
HTTP istek-yanıt modelinde çalışır. İstemci sorar, sunucu cevaplar. Sunucu istemciye kendi başına bir şey gönderemez — her zaman istemcinin sormayı beklemek zorunda.
Gerçek zamanlı veri ihtiyacı için geliştirilen ilk çözüm polling'di: istemci sunucuya düzenli aralıklarla "yeni bir şey var mı?" diye sorardı.
// Kötü çözüm: Polling setInterval(async () => { const response = await fetch("/api/messages/new"); const messages = await response.json(); if (messages.length > 0) displayMessages(messages); }, 1000); // Her saniye sor
Sorun açık: kullanıcı 10.000 olduğunda saniyede 10.000 istek. Çoğu boş yanıt. Sunucu yük altında, gecikme yüksek, bant genişliği israf.
Long polling biraz daha akıllıcaydı: istemci istek gönderir, sunucu yeni veri gelene kadar bağlantıyı açık tutar, veri gelince yanıt verir, istemci hemen yeni istek gönderir.
// Daha iyi ama hâlâ ideal değil: Long Polling async function longPoll() { const response = await fetch("/api/messages/wait"); const messages = await response.json(); displayMessages(messages); longPoll(); // Hemen yeni istek gönder }
Daha az boş yanıt ama hâlâ her mesaj için yeni bir HTTP bağlantısı. TCP handshake maliyeti her seferinde tekrar ödeniyor.
WebSocket: Kalıcı Bağlantı
WebSocket, istemci ile sunucu arasında tek bir kalıcı bağlantı kurar. Bağlantı bir kez kurulduktan sonra her iki taraf da istediği zaman veri gönderebilir. Ekstra overhead yok, polling yok, tekrar tekrar bağlantı kurma yok.
WebSocket bağlantısı HTTP upgrade mekanizmasıyla başlar:
GET /chat HTTP/1.1 Host: example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== Sec-WebSocket-Version: 13
Sunucu kabul ederse:
HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
101 — Switching Protocols. Artık bu bağlantı HTTP değil, WebSocket protokolü üzerinde çalışıyor. İkisi de istediği zaman mesaj gönderebilir.
Tarayıcıda WebSocket Kullanımı
// Bağlantı kur const ws = new WebSocket("wss://example.com/chat"); // Bağlantı açıldığında ws.addEventListener("open", () => { console.log("Bağlantı kuruldu"); ws.send(JSON.stringify({ type: "join", room: "general", username: "ali" })); }); // Mesaj geldiğinde ws.addEventListener("message", (event) => { const data = JSON.parse(event.data); switch (data.type) { case "message": displayMessage(data); break; case "user_joined": showNotification(`${data.username} odaya katıldı`); break; case "typing": showTypingIndicator(data.username); break; } }); // Bağlantı kapandığında ws.addEventListener("close", (event) => { console.log(`Bağlantı kapandı: ${event.code} - ${event.reason}`); // Otomatik yeniden bağlan setTimeout(reconnect, 3000); }); // Hata durumunda ws.addEventListener("error", (error) => { console.error("WebSocket hatası:", error); }); // Mesaj gönder function sendMessage(text) { if (ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify({ type: "message", text, timestamp: Date.now() })); } }
Node.js ile WebSocket Sunucusu
import { WebSocketServer } from "ws"; const wss = new WebSocketServer({ port: 8080 }); // Oda yönetimi const rooms = new Map(); wss.on("connection", (ws, request) => { console.log("Yeni bağlantı"); ws.isAlive = true; ws.on("message", (rawData) => { const data = JSON.parse(rawData); switch (data.type) { case "join": handleJoin(ws, data); break; case "message": handleMessage(ws, data); break; case "typing": handleTyping(ws, data); break; } }); ws.on("close", () => handleDisconnect(ws)); ws.on("pong", () => { ws.isAlive = true; }); }); function handleJoin(ws, data) { ws.username = data.username; ws.room = data.room; if (!rooms.has(data.room)) { rooms.set(data.room, new Set()); } rooms.get(data.room).add(ws); // Odadaki herkese bildir broadcast(data.room, { type: "user_joined", username: data.username }, ws); // ws = bu mesajı göndereni hariç tut } function handleMessage(ws, data) { broadcast(ws.room, { type: "message", username: ws.username, text: data.text, timestamp: Date.now() }); } function broadcast(room, data, exclude = null) { const clients = rooms.get(room) || new Set(); const message = JSON.stringify(data); clients.forEach(client => { if (client !== exclude && client.readyState === 1) { client.send(message); } }); } // Heartbeat: ölü bağlantıları temizle setInterval(() => { wss.clients.forEach(ws => { if (!ws.isAlive) return ws.terminate(); ws.isAlive = false; ws.ping(); }); }, 30000);
Heartbeat: Ölü Bağlantıları Tespit Etmek
WebSocket bağlantıları sessiz kaldığında ara network cihazları (proxy, load balancer) bağlantıyı kesebilir. İstemci ve sunucu bundan haberdar olmayabilir — her ikisi de bağlantının hâlâ açık olduğunu düşünür.
Heartbeat mekanizması bu sorunu çözer: sunucu belirli aralıklarla ping gönderir, istemci pong ile yanıtlar. Yanıt gelmezse bağlantı ölmüştür.
Ölçeklendirme: Birden Fazla Sunucu
Tek sunucu yeterli olmadığında sorun çıkar: sunucu A'ya bağlı kullanıcı, sunucu B'ye bağlı kullanıcıya mesaj gönderemez. Çözüm: pub/sub sistemi.
import { createClient } from "redis"; const publisher = createClient(); const subscriber = createClient(); await publisher.connect(); await subscriber.connect(); // Mesaj geldiğinde Redis'e yayınla function handleMessage(ws, data) { publisher.publish("chat:general", JSON.stringify({ type: "message", username: ws.username, text: data.text })); } // Redis'ten gelen mesajları dinle ve WebSocket'e ilet subscriber.subscribe("chat:general", (message) => { const data = JSON.parse(message); // Bu sunucuya bağlı tüm istemcilere gönder localClients.forEach(client => { if (client.readyState === 1) client.send(message); }); });
Her sunucu Redis kanalını dinler. Hangi sunucuya bağlı olursa olsun, tüm kullanıcılar mesajı alır.
WebSocket vs Server-Sent Events
WebSocket her zaman doğru araç değildir. Server-Sent Events (SSE), sunucudan istemciye tek yönlü veri akışı için daha basit bir alternatiftir.
SSE standart HTTP üzerinden çalışır, otomatik reconnect destekler, tarayıcı desteği mükemmeldir. Ama istemciden sunucuya veri göndermek için ayrı HTTP isteği gerekir.
WebSocket kullanın: Çift yönlü gerçek zamanlı iletişim. Chat, oyun, işbirliği araçları.
SSE kullanın: Tek yönlü akış. Canlı bildirimler, haber feed'i, progress bar, log streaming.
WebSocket'i anlamak, gerçek zamanlı sistemlerin nasıl çalıştığını anlamaktır. Chat'ten canlı işbirliği araçlarına, oyunlardan finansal platformlara — kalıcı bağlantının gücü her yerde.