WebSocket API, tarayıcı ile sunucu arasında iki yönlü ve sürekli açık kalan bir iletişim oturumu kurar. Bağlantı açıldıktan sonra iki taraf da istediği an mesaj gönderebilir; sayfanın yanıt almak için sunucuyu tekrar tekrar yoklaması (polling) gerekmez. Tarayıcıda bağlantıyı new WebSocket("wss://...") ile açar, open, message, error ve close olaylarını dinler, veri göndermek için send(), kapatmak için close() çağırırsınız.

Bu yazı istemci tarafındaki WebSocket nesnesine odaklanır: bağlantı kurma, durumlar, mesaj gönderme ve alma, JSON ve ikili veri, kapatma kodları, geri ve ileri önbelleği (bfcache) ile uyum ve güvenlik. WebSocket ile Server-Sent Events arasında seçim yapmak, Socket.io ve Nginx üzerinden vekil yapılandırması gibi konular WebSocket ile SSE karşılaştırması yazısında ele alınıyor. Tarayıcı örnekleri MDN belgelerine göre yazıldı; ağ gerektirmeyen yardımcı örnekler Node.js 24 ile denendi.

Bağlantı nasıl kurulur: el sıkışma

Bir WebSocket bağlantısı sıradan bir HTTP isteğiyle başlar. İstemci, Upgrade: websocket başlığı taşıyan bir GET isteği gönderir ve sunucu bağlantıyı WebSocket protokolüne yükseltmeyi kabul ederse 101 Switching Protocols durum koduyla cevap verir. Bu andan sonra aynı TCP bağlantısı üzerinden çerçeveler (frame) halinde veri akar. MDN'nin sunucu rehberindeki el sıkışma örneği şöyledir:

http
GET /chat HTTP/1.1
Host: example.com:8000
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

Sec-WebSocket-Key başlığını tarayıcı otomatik ekler; siz yazmazsınız. Sunucu bu değerin sonuna sabit bir GUID metni ekler, SHA-1 özetini alır ve Base64 ile kodlayarak Sec-WebSocket-Accept başlığında geri gönderir. Böylece istemci, karşı tarafın gerçekten WebSocket konuşan bir sunucu olduğundan emin olur. Bu hesabı Web Crypto API ile yaparak MDN'deki değeri doğrulayabilirsiniz:

javascript
const key = "dGhlIHNhbXBsZSBub25jZQ==";
const GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
const ozet = await crypto.subtle.digest("SHA-1", new TextEncoder().encode(key + GUID));
const accept = btoa(String.fromCharCode(...new Uint8Array(ozet)));
console.log(accept); // s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

Bu hesap sunucunun işidir; istemci kodunda yapmanız gerekmez. Ama el sıkışmanın neden bir HTTP isteği olduğunu ve vekil sunucuların neden Upgrade başlığını iletecek şekilde ayarlanması gerektiğini anlamak için faydalıdır. crypto.subtle hakkında daha fazlası Web Crypto API yazısında.

WebSocket nesnesini oluşturmak

new WebSocket(url) çağrısı nesneyi oluşturur ve hemen bağlanmayı denemeye başlar. URL'nin şeması ws, wss, http veya https olabilir; https verirseniz tarayıcı bunu wss'ye çevirir. URL bir parça (# ile başlayan kısım) içeremez. Göreli bir URL, çağıran betiğin temel adresine göre çözülür. URL ayrıştırılamazsa, şema bu dördü dışında kalırsa veya parça içeriyorsa kurucu SyntaxError fırlatır.

javascript
const wssWebSocket = new WebSocket("wss://websocket.example.org");
console.log(wssWebSocket.url); // 'wss://websocket.example.org'

const httpsWebSocket = new WebSocket("https://websocket.example.org");
console.log(httpsWebSocket.url); // 'wss://websocket.example.org

İkinci argüman protocols, tercih sırasına göre bir alt protokol adı veya adlar dizisidir. Tek bir sunucu birden çok alt protokol uygulayabilir ama her bağlantıda yalnızca biri seçilir. Sunucunun seçtiği alt protokol bağlantı kurulduktan sonra socket.protocol özelliğinden okunur; bağlantı kurulamazsa bu değer boş metindir. Aynı adı listede iki kez yazmak da SyntaxError nedenidir.

Güvenli olmayan bir bağlantı denerseniz kurucu SecurityError fırlatabilir. MDN, çoğu tarayıcının WebSocket bağlantılarında güvenli bağlantı istediğini, yalnızca aynı cihaz veya muhtemelen aynı ağ için istisna yapabildiğini belirtir. Gerçek uygulamada sayfa HTTPS ile sunulmalı ve bağlantı wss:// kullanmalıdır; ayrıntılar HTTPS ve TLS rehberinde, hata adları ise DOMException ve güvenli bağlam hataları yazısında.

Bağlantı durumları: readyState

Bağlantı durumları: readyState: WebSocket.CONNECTING, WebSocket.OPEN, WebSocket.CLOSING, WebSocket.CLOSED
SabitDeğerAnlamı
WebSocket.CONNECTING0Nesne oluşturuldu, bağlantı henüz açık değil
WebSocket.OPEN1Bağlantı açık, iletişime hazır
WebSocket.CLOSING2Bağlantı kapanma sürecinde
WebSocket.CLOSED3Bağlantı kapalı veya hiç açılamadı

Bağlantı eşzamansız kurulduğu için nesneyi oluşturduğunuz satırın hemen altında send() çağıramazsınız; o anda readyState hâlâ CONNECTING'dir. MDN'ye göre her bağlantı denemesi ya bir open ya da bir close olayıyla sonuçlanır: bağlantı kurulamazsa önce error, ardından wasClean değeri false olan bir close olayı gelir.

Olayları dinlemek

Olayları dinlemek

WebSocket dört olay yayar ve her biri addEventListener() veya onopen, onmessage, onerror, onclose özellikleriyle dinlenebilir. Özellik atamasının, aynı özelliğe daha önce atanmış dinleyicinin yerini aldığını unutmayın; birden çok dinleyici gerekiyorsa addEventListener() kullanın. Olay dinleyicileri hakkında genel bilgi addEventListener yazısında.

javascript
const socket = new WebSocket("wss://websocket.example.org");

socket.addEventListener("open", (event) => {
  socket.send("Hello Server!");
});

socket.addEventListener("message", (event) => {
  console.log("Message from server:", event.data);
});

socket.addEventListener("error", (event) => {
  console.error("WebSocket error:", event);
});

socket.addEventListener("close", (event) => {
  if (event.wasClean) {
    console.log(`Closed cleanly, code=${event.code}, reason=${event.reason}`);
  } else {
    console.log("Connection died");
  }
});

message olayı bir MessageEvent'tir ve gelen veri event.data özelliğindedir. error olayı ek özellik taşımayan genel bir Event'tir; hatanın ayrıntısını bu nesneden öğrenemezsiniz. Hata sonrasında bağlantı kapanır ve close olayı tetiklenir. close olayı bir CloseEvent'tir: code kapatma kodunu, reason sunucunun bildirdiği nedeni, wasClean ise bağlantının düzgün kapanıp kapanmadığını verir.

Mesaj göndermek: send()

send() metin (string), ArrayBuffer, Blob, typed array veya DataView kabul eder. Metin UTF-8 olarak kodlanır; Blob gönderildiğinde ikili çerçeve kullanılır ve Blob.type yok sayılır. Bağlantı CONNECTING durumundayken send() çağırmak InvalidStateError fırlatır. Bağlantı CLOSING veya CLOSED durumundaysa ise tarayıcı veriyi hata vermeden sessizce atar; bu yüzden göndermeden önce durumu kontrol etmek iyi bir alışkanlıktır.

JavaScript nesnelerini göndermenin yaygın yolu, onları JSON metnine çevirmektir. Sunucu da aynı biçimde JSON gönderebilir ve istemci JSON.parse() ile çözer. Birden fazla mesaj türü olan uygulamalarda her mesaja bir tür alanı eklemek ve gelen mesajı türüne göre ilgili işleyiciye yönlendirmek kodu düzenli tutar. Aşağıdaki yönlendirici, gerçek bir bağlantı olmadan event.data taşıyan nesnelerle denendi:

javascript
const isleyiciler = {
  sohbet: (veri) => `${veri.kimden}: ${veri.metin}`,
  durum: (veri) => `çevrim içi: ${veri.sayi}`,
};

function mesajIsle(event) {
  let mesaj;
  try {
    mesaj = JSON.parse(event.data);
  } catch {
    return "JSON olmayan mesaj atlandı";
  }
  const isleyici = isleyiciler[mesaj.tur];
  return isleyici ? isleyici(mesaj.veri) : `bilinmeyen tür: ${mesaj.tur}`;
}

const gelenler = [
  { data: JSON.stringify({ tur: "sohbet", veri: { kimden: "Ayşe", metin: "Merhaba" } }) },
  { data: JSON.stringify({ tur: "durum", veri: { sayi: 3 } }) },
  { data: JSON.stringify({ tur: "oyun", veri: {} }) },
  { data: "pong" },
];
for (const e of gelenler) console.log(mesajIsle(e));
// Ayşe: Merhaba
// çevrim içi: 3
// bilinmeyen tür: oyun
// JSON olmayan mesaj atlandı

// Tarayıcıda:
// socket.addEventListener("message", (e) => console.log(mesajIsle(e)));

Sunucudan gelen veriye hiçbir zaman körü körüne güvenmeyin: JSON.parse() çağrısını try bloğuna almak, bozuk bir mesajın tüm dinleyiciyi çökertmesini önler. Gelen metni sayfaya eklerken de innerHTML yerine textContent kullanmak güvenlidir; nedenini innerHTML ve textContent farkı yazısında bulabilirsiniz.

bufferedAmount ve hız kontrolü

send() verinin gönderilmesini beklemez; veriyi iç tampona ekler ve hemen döner. bufferedAmount özelliği, send() ile kuyruğa alınmış ama henüz ağa aktarılmamış bayt sayısını gösterir. Tüm veri gönderildiğinde sıfıra iner; ancak bağlantı kapandığında sıfırlanmaz ve send() çağırmaya devam ederseniz artmayı sürdürür. Metin UTF-8 ile kodlandığı için sayım bayt cinsindendir; Türkçe karakterler bir karakterden fazla yer tutar:

javascript
const kodlayici = new TextEncoder();
const mesaj = JSON.stringify({ iteration: 1, content: "çğış" });
console.log(mesaj.length, kodlayici.encode(mesaj).length); // 32 36

Tampon dolduğu için veri gönderilemezse soket otomatik olarak kapatılır. Çok sık veri gönderen uygulamalarda yeni mesaj eklemeden önce bufferedAmount değerine bakmak, tamponun kontrolsüz büyümesini önlemenin basit bir yoludur.

MDN, WebSocket arayüzünün geri basıncı (backpressure) desteklemediğini özellikle belirtir: mesajlar uygulamanın işleyebileceğinden hızlı gelirse tarayıcı ya mesajları tamponlayarak belleği doldurur, ya işlemci yüzde yüz kullanımda yanıt vermez hale gelir, ya da ikisi birden olur. Bu sorunu çözmek için akış (stream) tabanlı WebSocketStream arayüzü vardır; opened sözü bir ReadableStream ve bir WritableStream verir ve geri basınç otomatik uygulanır. MDN bu arayüzü standart dışı ve sınırlı destekli olarak tanımlar, geri basınç gerekiyorsa WebTransport API'yi önerir. Tarayıcı desteği için MDN uyumluluk tablosuna bakın.

javascript
if ("WebSocketStream" in self) {
  const wss = new WebSocketStream("wss://example.com/wss");
  const { readable, writable } = await wss.opened;
  const reader = readable.getReader();
  const writer = writable.getWriter();
  writer.write("ping");
  const { value, done } = await reader.read();
}

İkili veri ve binaryType

Sunucu ikili veri gönderdiğinde istemci bunu binaryType değerine göre Blob veya ArrayBuffer olarak alır. Varsayılan değer "blob"'dur. Veriyi bayt düzeyinde işleyecekseniz "arraybuffer" daha pratiktir; metin çerçeveleri ise her zaman metin olarak gelir:

javascript
const socket = new WebSocket("ws://localhost:8080");
socket.binaryType = "arraybuffer";

socket.addEventListener("message", (event) => {
  if (event.data instanceof ArrayBuffer) {
    const view = new DataView(event.data);
    console.log(view.getInt32(0));
  } else {
    console.log(event.data);
  }
});

Gönderme tarafında aynı veriyi hazırlamak için bir ArrayBuffer ve DataView kullanırsınız. Aşağıdaki parça, dört baytlık bir tamsayıyı yazıp okur:

javascript
const tampon = new ArrayBuffer(4);
new DataView(tampon).setInt32(0, 2026);
console.log(new DataView(tampon).getInt32(0)); // 2026
console.log(tampon.byteLength); // 4
// socket.send(tampon);

Bağlantıyı kapatmak: close(code, reason)

close() bağlantıyı veya süren bağlantı denemesini kapatır; bağlantı zaten CLOSED ise hiçbir şey yapmaz. Kapatma, bir kapanış el sıkışmasıyla başlar ve close() daha önce gönderilmiş mesajları atmaz; el sıkışma onlar gönderildikten sonra başlar.

Kod belirtmezseniz normal kapanış için 1000, diğer durumlarda gerçek nedeni gösteren 1001 ile 1015 arasındaki standart değerlerden biri otomatik atanır. Kendiniz bir kod verecekseniz değer ya 1000 ya da 3000 ile 4999 arasında bir tamsayı olmalıdır; aksi halde InvalidAccessError fırlatılır. reason metni UTF-8 ile kodlandığında 123 baytı aşmamalıdır, aşarsa SyntaxError alırsınız. Kod verirseniz neden de, neden verirseniz kod da vermeniz önerilir.

javascript
function kapatmaKoduGecerliMi(code) {
  return Number.isInteger(code) && (code === 1000 || (code >= 3000 && code <= 4999));
}
for (const k of [1000, 1001, 2999, 3000, 4999, 5000]) {
  console.log(k, kapatmaKoduGecerliMi(k));
}
// 1000 true
// 1001 false
// 2999 false
// 3000 true
// 4999 true
// 5000 false

const kodlayici = new TextEncoder();
const turkce = "Oturum kapatıldı, görüşmek üzere";
console.log(turkce.length, kodlayici.encode(turkce).length); // 32 38
const uzun = "ğ".repeat(62);
console.log(uzun.length, kodlayici.encode(uzun).length); // 62 124

Son satır, MDN'deki uyarının somut bir örneğidir: yalnızca 62 karakterlik bir Türkçe metin bile 124 bayt tuttuğu için 123 bayt sınırını aşar. Kullanıcıya gösterilecek uzun açıklamaları reason içine koymak yerine kısa bir kod kullanmak daha güvenlidir. Tarayıcıda kapatma şöyle görünür:

javascript
socket.close(4000, "oturum sona erdi");

Sayfa geçmişi önbelleği (bfcache) ile uyum

Geri ve ileri önbelleği, kullanıcının yakın zamanda ziyaret ettiği sayfaya geri döndüğünde sayfanın JavaScript yığını dahil anlık görüntüsünü saklayarak hızlı açılmasını sağlar. MDN, açık bir WebSocket bağlantısının tarayıcının sayfayı bu önbelleğe almasını engelleyebileceğini belirtir. İyi uygulama, kullanıcı sayfadan ayrılırken bağlantıyı pagehide olayında kapatmak, sayfa önbellekten geri geldiğinde ise pageshow olayında event.persisted değerine bakarak yeniden bağlanmaktır:

javascript
let websocket = new WebSocket(wsUri);
initializeWebSocketListeners(websocket);

window.addEventListener("pagehide", () => {
  if (websocket) {
    websocket.close();
    websocket = null;
  }
});

window.addEventListener("pageshow", (event) => {
  if (event.persisted) {
    websocket = new WebSocket(wsUri);
    initializeWebSocketListeners(websocket);
  }
});

Burada initializeWebSocketListeners(), yukarıdaki dört olay dinleyicisini yeni nesneye bağlayan kendi fonksiyonunuzdur. Sayfa yaşam döngüsü olaylarının ayrıntıları sayfa yaşam döngüsü yazısında.

Güvenlik

MDN iki temel kural verir. Birincisi, WebSocket karma içerik ortamında kullanılmamalıdır: HTTPS ile yüklenen bir sayfadan güvenli olmayan ws:// bağlantısı açmayın (ve tersini de yapmayın). Çoğu tarayıcı artık yalnızca güvenli WebSocket bağlantılarına izin verir ve güvensiz bağlamlarda desteği kaldırmıştır (karma içerik hatası). İkincisi, sunucu tarafında Origin başlığını kontrol edin. Tüm tarayıcılar el sıkışmada Origin gönderir; sunucu beklemediği bir köken gördüğünde 403 ile reddederek siteler arası WebSocket ele geçirme (Cross Site WebSocket Hijacking) saldırısına karşı korunabilir. Tarayıcı dışı istemcilerin sahte Origin gönderebileceğini de unutmayın; kimlik doğrulamayı yine sunucuda yapın.

Sık yapılan hatalar

Sık yapılan hatalar: open olayını beklemeden send() çağırmak, Kapalı bağlantıya yazmaya devam etmek, Geçersiz kapatma kodu kullanmak, reason metnini karakter sayısıyla ölçmek
  • open olayını beklemeden send() çağırmak: CONNECTING durumunda InvalidStateError alırsınız.
  • Kapalı bağlantıya yazmaya devam etmek: veri sessizce atılır ve bufferedAmount artmaya devam eder.
  • Geçersiz kapatma kodu kullanmak: 1001 veya 2000 gibi değerler close() içinde InvalidAccessError verir; kendi kodlarınız için 3000 ile 4999 aralığını kullanın.
  • reason metnini karakter sayısıyla ölçmek: sınır bayt cinsindendir; Türkçe karakterler iki bayt tutar.
  • HTTPS sayfada ws:// kullanmak: karma içerik nedeniyle bağlantı engellenir.
  • error olayından ayrıntı beklemek: olay ek bilgi taşımaz; kapanış nedeni için close olayındaki code ve wasClean değerlerine bakın.
  • URL'ye # parçası eklemek: kurucu SyntaxError fırlatır.

Sık Sorulan Sorular

WebSocket bağlantısı koptuğunda otomatik yeniden bağlanır mı?

Hayır. WebSocket arayüzünde kapanmış bir bağlantıyı yeniden açan bir yöntem yoktur; yeni bir bağlantı için yeni bir nesne oluşturmanız gerekir. Yeniden bağlanma mantığını close olayında kendiniz kurarsınız; bfcache örneğindeki pageshow kalıbı bunun basit bir biçimidir.

ws:// ile wss:// arasındaki fark nedir?

wss:// bağlantıyı TLS ile şifreler, ws:// şifrelemez. MDN gerçek uygulamalarda sayfanın HTTPS ile sunulmasını ve bağlantının wss kullanmasını önerir; ws:// yalnızca yerel geliştirme gibi durumlar için uygundur.

WebSocket ile fetch arasında ne zaman hangisini seçmeliyim?

Tek seferlik istek ve yanıt için fetch() yeterlidir. Sunucunun herhangi bir anda istemciye veri göndermesi gereken sohbet, canlı bildirim veya çok oyunculu oyun gibi senaryolarda WebSocket uygundur. Yalnızca sunucudan istemciye tek yönlü akış gerekiyorsa SSE de bir seçenektir; karşılaştırma WebSocket ve SSE yazısında.

WebSocket bağlantısında CORS geçerli mi?

WebSocket kurucusu yalnızca URL ve alt protokol listesi alır; fetch()'teki mode veya credentials gibi CORS seçenekleri yoktur. MDN'nin sunucu rehberine göre tarayıcılar el sıkışmada Origin başlığını gönderir ve bu başlığa bakarak bağlantıyı kabul etmek ya da reddetmek sunucunun işidir. Bu yüzden köken kontrolünü sunucuda yapmanız önemlidir.

Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.

İlgili içerikler: Web API öğrenme rehberi · WebSocket ve SSE karşılaştırması · Fetch API kullanımı · postMessage ile sekme iletişimi · Sayfa yaşam döngüsü · HTTPS ve TLS 1.3