WebSockets Nasıl Çalışır: Gerçek Zamanlı Bağlantı Süreçleri ve Adım Adım Rehber

Web’in ilk günlerinde tarayıcı basit bir doküman görüntüleyiciydi. Bir sayfa talep ederdiniz, sunucu bunu işlerdi ve bağlantı kapatılırdı. Bu istek-yanıt döngüsü, HTTP (Hypertext Transfer Protocol) protokolünün temelidir.

Ancak, web uygulamaları zamanla gerçek zamanlı sohbetler, canlı finansal grafikler, ortak belge düzenleme ve çok oyunculu oyunlar gibi zengin ve etkileşimli deneyimlere dönüştükçe, geleneksel HTTP modelinin sınırları kendini göstermeye başladı.

Geliştiriciler canlı güncellemeler alabilmek için başlangıçta bazı geçici çözümlere başvurdular:

  • Short Polling (Kısa Yoklama): Tarayıcı, yeni veri olup olmadığını sormak için her birkaç saniyede bir sunucuya sürekli HTTP istekleri gönderir. Bu durum, devasa başlık (header) yükü oluşturur ve sunucu kaynaklarını israf eder.
  • Long Polling (Uzun Yoklama / Comet): Tarayıcı bir istek gönderir ve sunucu yeni veri hazır olana kadar bağlantıyı açık tutar. Veri gönderildikten sonra bağlantı kapatılır ve tarayıcı hemen yeni bir istek açar. Yönetmesi karmaşıktır ve bağlantı kurulum maliyetleri yine de yüksektir.

WebSockets, tek bir TCP bağlantısı üzerinden kalıcı, çift yönlü ve tam çift yönlü (full-duplex) iletişim sağlayan standart bir protokol sunarak bu sınırlamaları kökten çözmüştür.


WebSocket Nedir?

WebSockets (RFC 6455 ile tanımlanmıştır), HTTP ile yan yana çalışır. HTTP, yalnızca istemcinin istek başlatabildiği durumsuz (stateless) bir protokol iken; WebSocket bağlantısı kurulduktan sonra süresiz olarak açık kalır. Bu sayede hem istemci hem de sunucu, minimum gecikmeyle istedikleri an birbirlerine veri gönderebilirler.

WebSocket’in temel kuralı şudur:

Bağlantı bir kez kurulduktan sonra, her iki taraf da yeni bir bağlantı isteği başlatmadan istedikleri an mesaj gönderebilir.


Adım Adım Bağlantı Yaşam Döngüsü

Bir WebSocket bağlantısı üç belirgin aşamadan geçer: El Sıkışma (Handshake), Veri Aktarımı ve Bağlantıyı Kapatma.

WebSocket Connection Lifecycle Diagram

1. HTTP El Sıkışması (Protokol Yükseltme)

Güvenlik duvarları ve yönlendiriciler, genellikle 80 (HTTP) ve 443 (HTTPS) portlarındaki standart web trafiğine izin verecek şekilde yapılandırıldığından; WebSockets bağlantısı standart bir HTTP/1.1 isteği olarak başlar. Buna Upgrade Handshake adı verilir.

İstemci İsteği

İstemci, protokol değişikliği talep eden özel başlıklarla birlikte bir HTTP GET isteği gönderir:

GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Origin: https://example.com
  • Upgrade: websocket ve Connection: Upgrade: Sunucuya istemcinin protokol değiştirmek istediğini bildirir.
  • Sec-WebSocket-Key: Base64 ile kodlanmış rastgele 16 baytlık bir değer. Sunucunun el sıkışmayı aldığını ve WebSocket protokolünü anladığını kanıtlamak için kullanılır.
  • Sec-WebSocket-Version: WebSocket protokol sürümünü belirtir (genellikle 13).
  • Origin: Sunucu tarafından bağlantıya izin verilip verilmeyeceğini belirlemek için kullanılır (yetkisiz sitelerin çapraz site bağlantı kurmasını önleyen güvenlik kontrolü).

Sunucu Yanıtı

Sunucu WebSockets destekliyorsa, isteği doğrular ve bir HTTP durum kodu olan 101 Switching Protocols ile yanıt verir:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
  • Sunucu Sec-WebSocket-Accept Başlığını Nasıl Hesaplar?:
    1. İstemcinin Sec-WebSocket-Key değerini (dGhlIHNhbXBsZSBub25jZQ==) alır.
    2. Bunu standart bir sihirli GUID dizisi ile birleştirir: "258EAFA5-E914-47DA-95CA-C5AB0DC85B11".
    3. Birleştirilen dizinin SHA-1 özetini (hash) hesaplar.
    4. Çıkan özet değerini Base64 ile kodlar.
    5. İstemci bu değerin kendi beklentileriyle uyuştuğunu doğruladığında el sıkışma başarıyla tamamlanır, HTTP bağlantısı ham bir TCP soketine dönüşür ve her iki taraf da WebSocket protokolüne geçer.

2. Veri Çerçeveleme ve Aktarım

Düz metin başlıklarının ardından bir gövde gönderen HTTP’den farklı olarak, WebSockets verileri çerçeve (frame) adı verilen yapılandırılmış ikili paketler halinde iletir.

Bir WebSocket çerçevesi, çok hafif bir başlığa (2 ila 14 bayt arası) ve ardından gelen yüke (payload) sahiptir. Bu başlık şunları içerir:

  • FIN biti (1 bit): Mesajın son çerçevesi olup olmadığını belirtir.
  • Opcode (4 bit): Çerçevenin tipini tanımlar:
    • 0x1: Metin çerçevesi (UTF-8 kodlu)
    • 0x2: İkili (binary) çerçeve
    • 0x8: Bağlantıyı kapatma isteği
    • 0x9: Ping
    • 0xA: Pong
  • Mask biti (1 bit): Yük verisinin maskelenip maskelenmediğini belirtir.
  • Yük Uzunluğu (Payload Length): Verinin boyutu.
  • Maskeleme Anahtarı (4 bayt): Kritik Güvenlik Gereksinimi: İstemciden sunucuya gönderilen tüm çerçeveler, rastgele 4 baytlık bir anahtar kullanılarak maskelenmelidir (XOR yöntemiyle şifrelenmeli). Bu, aracı önbellek sunucularının trafiği okumasını veya önbellek zehirleme saldırıları gerçekleştirmesini engeller. Sunucudan istemciye giden çerçeveler maskelenmemelidir.

Heartbeats (Ping/Pong)

Yönlendiricilerin ve yük dengeleyicilerin boşta kalan bağlantıları kapatmasını önlemek için taraflardan biri Ping çerçevesi gönderebilir. Alıcı taraf, aynı veri yükünü içeren bir Pong çerçevesi ile hemen yanıt vermelidir.

3. Bağlantıyı Kapatma

Bağlantıyı temiz bir şekilde kapatmak için:

  1. Taraflardan biri, bir durum kodu (örneğin normal kapatma için 1000, anormal kapatma için 1006) ve isteğe bağlı bir metin açıklaması içeren bir Close çerçevesi gönderir.
  2. Diğer taraf kendi Close çerçevesiyle yanıt verir.
  3. Alttaki TCP soketi kapatılır.

Kod Örneği: Node.js WebSocket Uygulaması

WebSockets’i çalışırken görmek için basit bir Node.js uygulaması yazalım. Aldığı her mesajı geri yansıtan (echo) yerel bir WebSocket sunucusu ve ona bağlanacak bir istemci betiği oluşturacağız.

WebSocket Sunucusu (server.js)

const { WebSocketServer } = require('ws');
const http = require('http');

// 1. Standart bir HTTP sunucusu oluşturun
const server = http.createServer((req, res) => {
    res.writeHead(200, { 'Content-Type': 'text/plain' });
    res.end('HTTP Server running. Use WebSocket to connect.\n');
});

// 2. HTTP Sunucusuna bir WebSocket Sunucusu ekleyin
const wss = new WebSocketServer({ server });

wss.on('connection', (ws, req) => {
    const clientIp = req.socket.remoteAddress;
    console.log(`[Sunucu] Yeni istemci bağlandı: ${clientIp}`);

    // İstemciye karşılama mesajı gönder
    ws.send(JSON.stringify({ type: 'welcome', message: 'Ghaznix WebSocket sunucusuna bağlandınız!' }));

    // İstemciden gelen mesajları dinle
    ws.on('message', (message) => {
        console.log(`[Sunucu] Alındı: ${message}`);
        
        try {
            const data = JSON.parse(message);
            ws.send(JSON.stringify({
                type: 'echo',
                message: `Sunucu yankısı: ${data.text.toUpperCase()}`,
                timestamp: new Date().toISOString()
            }));
        } catch (e) {
            ws.send(JSON.stringify({ type: 'error', message: 'Geçersiz JSON formatı' }));
        }
    });

    // İstemci bağlantısının kesilmesini ele al
    ws.on('close', (code, reason) => {
        console.log(`[Sunucu] İstemci ayrıldı (Kod: ${code}, Neden: ${reason.toString() || 'Yok'})`);
    });

    ws.on('error', (error) => {
        console.error(`[Sunucu] Soket hatası: ${error.message}`);
    });
});

server.listen(8080, () => {
    console.log('WebSocket sunucusu ws://localhost:8080 adresinde dinliyor');
});

Tarayıcı İstemcisi (İstemci Tarafı JavaScript)

Bu istemciyi doğrudan tarayıcınızın konsolunda çalıştırabilirsiniz:

// 1. Sunucu ile bağlantı kurun
const socket = new WebSocket('ws://localhost:8080');

// 2. Bağlantı açıldığında tetiklenecek olay
socket.addEventListener('open', (event) => {
    console.log('[İstemci] Sunucuya bağlanıldı.');
    
    const payload = JSON.stringify({ text: 'merhaba sunucu!' });
    socket.send(payload);
    console.log(`[İstemci] Gönderildi: ${payload}`);
});

// 3. Sunucudan gelen mesajları dinle
socket.addEventListener('message', (event) => {
    const response = JSON.parse(event.data);
    console.log('[İstemci] Sunucudan mesaj alındı:', response);
});

// 4. Bağlantı kapatıldığında tetiklenecek olay
socket.addEventListener('close', (event) => {
    console.log(`[İstemci] Bağlantı kapatıldı (Kod: ${event.code})`);
});

// 5. Hata durumunda tetiklenecek olay
socket.addEventListener('error', (error) => {
    console.error('[İstemci] WebSocket Hatası:', error);
});

HTTP vs. WebSockets: Detaylı Karşılaştırma

Özellik HTTP/1.1 WebSockets
İletişim Tek Yönlü (İstemci başlatır) Çift Yönlü (Hem istemci hem sunucu)
Bağlantı Modeli İstek-Yanıt (kısa ömürlü) Kalıcı (uzun ömürlü)
Ek Yük (Overhead) Yüksek (her istekte başlıklar gönderilir) Çok Düşük (minimum çerçeve yükü)
Durum Bilgisi Durumsuz (Stateless) Durumlu (Stateful - bağlantı bağlamı korunur)
Protokol http:// veya https:// ws:// veya wss://
Kullanım Alanı Doküman çekme, REST API’ler Sohbet uygulamaları, anlık panolar, canlı yayınlar

WebSockets İçin Güvenlik Önerileri

WebSockets, el sıkışmasından sonra standart HTTP yönlendirmesini baypas ettiği için benzersiz güvenlik açıkları getirebilir:

  1. Güvenli WebSocket (wss://) Kullanın: WebSockets’i her zaman TLS/SSL üzerinden çalıştırın (Port 443). WSS çerçeve içeriğini şifreleyerek araya girme (MITM) ve dinleme saldırılarını engeller.
  2. Origin Doğrulaması: WebSockets, Aynı Köken Politikası (Same-Origin Policy) ile kısıtlanmamıştır. Yetkisiz sitelerin çapraz istek atmasını önlemek için el sıkışma sırasında sunucu tarafında Origin başlığını mutlaka doğrulayın.
  3. El Sıkışmada Kimlik Doğrulama: Bağlantı kurulmadan önce kullanıcıları doğrulayın. Bu işlem genellikle sorgu parametrelerinde bir token (JWT gibi) ileterek veya oturum çerezlerini doğrulayarak yapılır.
  4. Girdi Temizleme (Sanitization): WebSockets aracılığıyla alınan her mesajı güvenilmeyen girdi olarak değerlendirin. XSS saldırılarını önlemek için gelen verileri doğrulayın ve temizleyin.

Özet

WebSockets, geleneksel HTTP yoklamasının (polling) getirdiği ek yükü ortadan kaldırarak gerçek zamanlı web uygulamalarında bir devrim yarattı. Tek bir kalıcı TCP bağlantısını koruyarak anında çift yönlü mesajlaşmayı mümkün kılıyor ve günümüzün canlı grafik panolarına, çok oyunculu oyunlarına ve sohbet uygulamalarına hayat veriyor. HTTP yükseltmesini, çerçeveleme mimarisini ve kritik güvenlik uygulamalarını anlamak, hızlı ve güvenli gerçek zamanlı hizmetler oluşturmanızı sağlar.


Ghaznix Blog’unda daha fazla geliştirici kılavuzu ve eğitim içeriği keşfedin →