00 WebSocket nedir
WebSocket, tek bir TCP bağlantısı üzerinde tarayıcı ile sunucu arasında full-duplex iletişim sağlayan, IETF tarafından RFC 6455 ile 2011'de standartlaştırılmış bir protokoldür.
HTTP, istek-yanıt modeli üzerine kurulmuştur: client bir şey ister, server yanıt verir, bağlantı kapanır. Bu model statik web sayfaları için mükemmeldir; ancak gerçek zamanlı veri gerektiren uygulamalarda ciddi sorunlar yaratır. Sunucunun kendi inisiyatifiyle client'a mesaj gönderemeyişi, uzun yıllar boyunca çeşitli geçici çözümlerin doğmasına yol açtı.
WebSocket bu sorunu kökten çözdü: HTTP üzerinde başlayan ancak hemen TCP stream'e dönüşen, her iki tarafın da istediği zaman mesaj gönderebildiği kalıcı bir kanal sağlar. Tek bir handshake, sürekli bir bağlantı.
Hangi uygulamalar WebSocket ister
HTTP polling vs WebSocket karşılaştırması
| Yöntem | Nasıl çalışır | Gecikme | Sunucu yükü | Sunucudan push |
|---|---|---|---|---|
| HTTP Polling | Client düzenli aralıklarla GET atar | Yüksek (interval kadar) | Çok yüksek (boş yanıtlar) | Hayır |
| HTTP Long-Polling | Client bekler, server veri olunca yanıt verir; client tekrar ister | Orta | Yüksek (yarı açık bağlantılar) | Dolaylı |
| SSE (EventSource) | Server tek yönlü event stream açar | Düşük | Orta | Evet — tek yön |
| WebSocket | Tek TCP bağlantısı, iki yönlü | Çok düşük | Düşük (kalıcı bağlantı) | Evet — iki yön |
URL şeması
ws://example.com/socket → TCP:80, şifresiz wss://example.com/socket → TCP:443, TLS ile şifreli ws://localhost:8765/ → yerel geliştirme wss://api.example.com:8443/ws → özel port, TLS
WebSocket, RFC 6455 ile 2011'de standartlaştırıldı. Tüm modern tarayıcılar ve popüler sunucu framework'leri destekler. HTTP/1.1 üzerinde kurulur; HTTP/2 üzerinden WebSocket için RFC 8441 ayrı bir mekanizma tanımlar ancak nadiren kullanılır.
Bu bölümde
- HTTP request-response modelinin gerçek zamanlı senaryolardaki yetersizliği
- Polling, long-polling, SSE ve WebSocket arasındaki farklar
- ws:// ve wss:// URL şemaları
- RFC 6455, 2011
01 HTTP Upgrade handshake
WebSocket bağlantısı, sıradan bir HTTP/1.1 isteğiyle başlar; sunucu 101 Switching Protocols yanıtını verdiği andan itibaren TCP soket WebSocket protokolüne devredilir.
WebSocket, sıfırdan yeni bir ağ protokolü olarak tasarlanmadı. Mevcut HTTP altyapısından — proxy'ler, firewall'lar, 80/443 portları — yararlanmak için HTTP üzerinde yükseltme (upgrade) mekanizmasını kullanır. Bu sayede bir WebSocket bağlantısı, standart bir HTTP isteğiyle başlayarak ağ cihazlarında herhangi bir özel konfigürasyon gerektirmez.
Client'tan gelen Upgrade isteği
GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Origin: http://example.com
Sunucudan gelen 101 yanıtı
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Accept hesabı
Sunucu, Sec-WebSocket-Key değerini RFC 6455'te tanımlı sabit bir GUID ile birleştirir, SHA-1 hash alır ve base64'e dönüştürür. Bu mekanizma, sunucunun gerçekten WebSocket'i desteklediğini kanıtlar.
import hashlib
import base64
# RFC 6455 Section 1.3 — sabit magic UUID
GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
def compute_accept(sec_websocket_key: str) -> str:
combined = sec_websocket_key + GUID
sha1_hash = hashlib.sha1(combined.encode("utf-8")).digest()
return base64.b64encode(sha1_hash).decode("utf-8")
# Örnek: tarayıcının gönderdiği key
key = "dGhlIHNhbXBsZSBub25jZQ=="
accept = compute_accept(key)
print(f"Sec-WebSocket-Accept: {accept}")
# → s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
client → GET /ws HTTP/1.1 + Upgrade: websocket + Sec-WebSocket-Key server → 101 Switching Protocols + Sec-WebSocket-Accept ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ HTTP bitti ───▶ TCP soket artık WebSocket protocol taşıyor
Handshake tamamlandıktan sonra HTTP protokolü sona erer. Aynı TCP bağlantısı artık WebSocket frame'leri taşır. HTTP cookie'leri ve header'lar yalnızca handshake sırasında geçerlidir — kimlik doğrulama bu aşamada yapılmalıdır.
Bu bölümde
- Upgrade ve Connection header'larının rolü
- Sec-WebSocket-Key → SHA-1 → base64 → Sec-WebSocket-Accept akışı
- 101 Switching Protocols sonrası HTTP'nin sona ermesi
02 Frame formatı
WebSocket verisi, her biri kontrol bitleri, opcode, uzunluk alanı ve isteğe bağlı maskeleme anahtarı içeren frame'lere bölünür.
Handshake tamamlanınca TCP bağlantısı WebSocket frame'leri taşımaya başlar. Her frame, ilk iki zorunlu byte ile başlar; kalan alanlar frame içeriğine göre değişir. Wire üzerinde gereksiz byte bulunmaz: küçük mesajlar 2-6 byte header ile gönderilir.
Frame bit diyagramı
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len | Extended payload length |
|I|S|S|S| (4) |A| (7) | (16/64) |
|N|V|V|V| |S| | (if payload len==126/127) |
| |1|2|3| |K| | |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - -+
| Extended payload length continued, if payload len == 127 |
+ - - - - - - - - - - - - - - -+-------------------------------+
| Masking-key (4 bytes, if MASK=1) |
+---------------------------------------------------------------+
| Payload Data |
+---------------------------------------------------------------+
Alan açıklamaları
Opcode tablosu
| Opcode | İsim | Açıklama |
|---|---|---|
0x0 | Continuation | Önceki fragmente edilmiş mesajın devamı |
0x1 | Text | UTF-8 metin mesajı |
0x2 | Binary | Ham binary veri |
0x3–0x7 | — | Gelecekteki data frame'ler için rezerve |
0x8 | Close | Bağlantı kapatma isteği |
0x9 | Ping | Heartbeat / canlılık kontrolü |
0xA | Pong | Ping'e yanıt |
0xB–0xF | — | Gelecekteki control frame'ler için rezerve |
Maskeleme: XOR uygulaması
def apply_mask(payload: bytes, masking_key: bytes) -> bytes:
"""RFC 6455 Section 5.3 — XOR masking/unmasking (aynı işlem)."""
return bytes(
b ^ masking_key[i % 4]
for i, b in enumerate(payload)
)
# Client "Hello" metnini sunucuya gönderecek
payload = b"Hello"
mask_key = b"\x37\xfa\x21\x3d" # rastgele 4 byte
masked = apply_mask(payload, mask_key)
unmasked = apply_mask(masked, mask_key) # aynı fonksiyon ile geri al
print(unmasked) # b'Hello'
Maskeleme, güvenlik amacıyla değil, önbellek zehirlenmesi saldırılarına (cache poisoning) karşı tasarlanmıştır. Proxy'lerin WebSocket frame'lerini HTTP yanıtı sanıp önbelleğe almasını engeller. Server-to-client mesajlar maskelenmez.
Bu bölümde
- FIN, RSV, MASK bitlerinin anlamı
- 7-bit payload length ve extended length kodlamaları
- Opcode tablosu: 0x0–0xA
- XOR maskeleme mekanizması ve amacı
03 Control frames: ping, pong, close
Control frame'ler (ping, pong, close) bağlantı yönetimine hizmet eder; veri frame'lerinin aksine fragmente edilemez ve payload'ları 125 byte ile sınırlıdır.
Ping / Pong — heartbeat
Ping frame'i (opcode 0x9), bir tarafın karşı tarafın hâlâ bağlı olup olmadığını sorgulamak için gönderdiği kontrol mesajıdır. Ping alan taraf, aynı payload ile bir pong frame'i (opcode 0xA) göndermek zorundadır.
import asyncio
import websockets
async def server_with_ping(websocket):
try:
async for message in websocket:
await websocket.send(f"echo: {message}")
except websockets.ConnectionClosed:
print("Bağlantı kapandı")
# ping_interval ve ping_timeout websockets.serve() parametresi olarak verilir
async def main():
async with websockets.serve(
server_with_ping,
"0.0.0.0",
8765,
ping_interval=30, # her 30 saniyede ping gönder
ping_timeout=10, # 10 saniye içinde pong gelmezse bağlantıyı kes
):
await asyncio.get_event_loop().run_until_complete(asyncio.Future())
asyncio.run(main())
Close frame ve kapanış el sıkışması
Bağlantıyı düzgün şekilde kapatmak için her iki tarafın da close frame göndermesi gerekir. İlk close frame'i gönderen taraf kapanışı başlatır; diğer taraf aynı status code ile yanıt vermelidir. Ardından TCP FIN ile bağlantı sonlandırılır.
Client → Close frame (code: 1000, reason: "Normal closure") Server ← Close frame (code: 1000) ← server echo Server → TCP FIN Client ← TCP FIN ACK
Close status kodları
| Kod | İsim | Kullanım |
|---|---|---|
| 1000 | Normal Closure | Bağlantı amacına ulaştı, normal kapanış |
| 1001 | Going Away | Sunucu kapatılıyor veya tarayıcı sayfadan ayrılıyor |
| 1002 | Protocol Error | Protokol ihlali tespit edildi |
| 1003 | Unsupported Data | Desteklenmeyen veri türü alındı |
| 1008 | Policy Violation | Mesaj politikayı ihlal etti |
| 1009 | Message Too Big | Mesaj izin verilen boyutu aştı |
| 1011 | Internal Error | Sunucu tarafında beklenmedik hata |
| 1012 | Service Restart | Sunucu yeniden başlıyor, tekrar bağlanabilirsiniz |
Aniden TCP bağlantısını kesmek (FIN göndermeden) yerine close frame ile düzgün kapanış yapın. Özellikle sunucu yeniden başlarken 1012 kodu göndermek, client'ların akıllıca reconnect yapmasını sağlar.
Bu bölümde
- Ping (0x9) ve pong (0xA) opcode'larının heartbeat işlevi
- 30–60 saniye ping interval best practice
- Close frame (0x8) ile iki taraflı kapanış el sıkışması
- 1000, 1001, 1011, 1012 close status kodları
04 Python websockets kütüphanesi — server
websockets kütüphanesi, asyncio tabanlı WebSocket sunucusu ve client'ı yazmak için Python'ın en olgun ve RFC uyumlu seçeneğidir.
Kurulum
pip install websockets # en güncel stabil sürüm
python -c "import websockets; print(websockets.__version__)"
Handler fonksiyonu imzası
Her WebSocket bağlantısı için bir coroutine handler çağrılır. Handler tamamlandığında bağlantı kapatılır.
import asyncio
import websockets
from websockets.server import WebSocketServerProtocol
async def handler(websocket: WebSocketServerProtocol) -> None:
# websocket.remote_address → (ip, port) tuple
# websocket.path → "/chat" gibi URL path
# websocket.request_headers → HTTP upgrade header'ları
remote = websocket.remote_address
print(f"Yeni bağlantı: {remote[0]}:{remote[1]} — path: {websocket.path}")
try:
async for message in websocket:
print(f"[{remote}] aldı: {message}")
await websocket.send(f"echo: {message}")
except websockets.ConnectionClosedOK:
print(f"[{remote}] normal kapandı")
except websockets.ConnectionClosedError as e:
print(f"[{remote}] hata: {e.code} {e.reason}")
Echo server — tam örnek
import asyncio
import websockets
async def echo(websocket):
async for message in websocket:
await websocket.send(message)
async def main():
async with websockets.serve(echo, "0.0.0.0", 8765) as server:
print("Echo server başlatıldı → ws://localhost:8765")
await server.wait_closed()
asyncio.run(main())
Broadcast: tüm client'lara gönderme
import asyncio
import websockets
# Bağlı tüm client'ları takip eden set
CONNECTED: set = set()
async def handler(websocket):
CONNECTED.add(websocket)
try:
async for message in websocket:
# Gönderici hariç herkese ilet
recipients = CONNECTED - {websocket}
if recipients:
await asyncio.gather(
*[r.send(message) for r in recipients]
)
finally:
CONNECTED.discard(websocket)
async def main():
async with websockets.serve(handler, "0.0.0.0", 8765):
await asyncio.Future() # sonsuza kadar çalış
asyncio.run(main())
Path-based routing
async def handler(websocket):
path = websocket.path
if path == "/chat":
await chat_handler(websocket)
elif path == "/metrics":
await metrics_handler(websocket)
else:
await websocket.close(1008, "Unknown endpoint")
Bu bölümde
- async def handler(websocket) imzası ve yaşam döngüsü
- recv(), send(), close() metodları
- async with websockets.serve() ile server başlatma
- CONNECTED set pattern ile broadcast
- websocket.path ile URL tabanlı routing
05 asyncio ile client
websockets client'ı, asyncio context manager sözdizimi ile bağlanır; hem tek seferlik sorgular hem de sürekli mesaj akışı için kullanılabilir.
Temel bağlantı
import asyncio
import websockets
async def main():
uri = "ws://localhost:8765"
async with websockets.connect(uri) as ws:
await ws.send("Merhaba sunucu!")
response = await ws.recv()
print(f"Yanıt: {response}")
# context manager bloğu sonunda bağlantı kapatılır
asyncio.run(main())
Async generator ile mesaj stream
import asyncio
import websockets
async def listen():
uri = "ws://localhost:8765"
async with websockets.connect(uri) as ws:
# async for: sunucu close frame gönderene kadar döngü sürer
async for message in ws:
print(f"Gelen: {message}")
# binary veriyse: isinstance(message, bytes)
asyncio.run(listen())
Ping/pong ve bağlantı durumu
import asyncio
import websockets
async def check_connection():
async with websockets.connect("ws://localhost:8765") as ws:
# Manuel ping — latency ölçümü
latency = await ws.ping()
print(f"Round-trip latency: {latency:.3f}s")
# Bağlantı durumu
print(f"Kapalı mı: {ws.closed}") # False
print(f"Close code: {ws.close_code}") # None (henüz kapalı değil)
await ws.send("test")
msg = await ws.recv()
# Context manager'dan çıkıldıktan sonra
print(f"Close code: {ws.close_code}") # 1000
asyncio.run(check_connection())
Bu bölümde
- async with websockets.connect() context manager kullanımı
- async for message in ws ile sürekli okuma
- ws.ping() ile manuel latency ölçümü
- ws.closed, ws.close_code durum alanları
06 JSON mesajlaşma ve routing
WebSocket, ham byte akışı sağlar; uygulama katmanında mesaj türü ve yük ayrımını sağlamak için JSON tabanlı envelope pattern kullanılır.
Mesaj zarfı (envelope) şeması
Ham string mesajlar yönetilemez hale gelince her mesajı bir JSON zarfı içinde gönderin. type alanı mesajı doğru handler'a yönlendirir; payload ise uygulama verisini taşır.
// Komut mesajı
{
"type": "command",
"payload": { "action": "start", "target": "motor_1" }
}
// Sensör verisi
{
"type": "sensor",
"payload": { "temp": 42.5, "unit": "C", "ts": 1712345678 }
}
// Hata bildirimi
{
"type": "error",
"payload": { "code": 404, "message": "Unknown command" }
}
Dispatch pattern: type → handler
import asyncio
import json
import websockets
async def handle_command(websocket, payload: dict):
action = payload.get("action")
target = payload.get("target")
print(f"Komut: {action} → {target}")
await websocket.send(json.dumps({
"type": "ack",
"payload": {"action": action, "status": "ok"}
}))
async def handle_subscribe(websocket, payload: dict):
topic = payload.get("topic", "all")
print(f"Subscribe: {topic}")
await websocket.send(json.dumps({
"type": "subscribed",
"payload": {"topic": topic}
}))
# Tip → coroutine eşlemesi
DISPATCH = {
"command": handle_command,
"subscribe": handle_subscribe,
}
async def handler(websocket):
async for raw in websocket:
try:
msg = json.loads(raw)
msg_type = msg.get("type")
payload = msg.get("payload", {})
except json.JSONDecodeError:
await websocket.send(json.dumps({
"type": "error",
"payload": {"message": "Invalid JSON"}
}))
continue
handler_fn = DISPATCH.get(msg_type)
if handler_fn:
await handler_fn(websocket, payload)
else:
await websocket.send(json.dumps({
"type": "error",
"payload": {"message": f"Unknown type: {msg_type}"}
}))
Binary mesajlar: bytes gönderme
import struct
# Sensör verisi: 4 byte float (sıcaklık) + 4 byte float (nem) + 8 byte int (timestamp)
packet = struct.pack("!ff q", 42.5, 68.3, 1712345678901)
# Binary websocket mesajı olarak gönder
await websocket.send(packet)
# Alıcı tarafta
raw = await websocket.recv()
if isinstance(raw, bytes):
temp, humidity, ts = struct.unpack("!ff q", raw)
print(f"Sıcaklık: {temp:.1f}°C, Nem: {humidity:.1f}%")
Rate limiting: per-client sayaç
import asyncio
import time
async def handler(websocket):
window_start = time.monotonic()
msg_count = 0
MAX_PER_SEC = 20
async for raw in websocket:
now = time.monotonic()
if now - window_start >= 1.0:
window_start = now
msg_count = 0
msg_count += 1
if msg_count > MAX_PER_SEC:
await websocket.close(1008, "Rate limit exceeded")
return
# normal işleme...
Bu bölümde
- JSON envelope: type + payload şeması
- DISPATCH dict ile type-based routing
- JSON parse hatası yönetimi ve hata mesajı gönderme
- struct.pack ile binary sensör verisi gönderme
- Per-client rate limiting (sliding window)
07 Reconnect ve exponential backoff
Gerçek dünyada bağlantılar kopar; iyi bir client, akıllı bir bekleme stratejisiyle sunucuyu bunaltmadan yeniden bağlanır.
Bağlantı kopma senaryoları
websockets.connect() parametreleri
| Parametre | Tip | Açıklama |
|---|---|---|
additional_headers | dict | Handshake sırasında eklenecek özel HTTP header'ları (ör. Authorization) |
open_timeout | float | Handshake tamamlanma zaman aşımı (saniye). None = sınırsız. |
close_timeout | float | Close frame el sıkışması için beklenecek süre. |
ping_interval | float | Otomatik ping gönderme aralığı (saniye). None = devre dışı. |
ping_timeout | float | Ping'e yanıt bekleme süresi. Aşılırsa bağlantı kesilir. |
Exponential backoff with jitter
Her başarısız denemeden sonra bekleme süresini ikiye katla; üzerine rastgele bir değer ekle (jitter). Bu yöntem, birden fazla client'ın aynı anda yeniden bağlanmasını (thundering herd) önler.
import asyncio
import random
import websockets
URI = "ws://localhost:8765"
MAX_DELAY = 60.0 # saniye — maksimum bekleme
async def connect_with_backoff():
attempt = 0
while True:
try:
async with websockets.connect(
URI,
open_timeout=10,
ping_interval=20,
ping_timeout=10,
) as ws:
attempt = 0 # başarıyla bağlandık, sayacı sıfırla
print("Bağlandı")
await on_connected(ws) # subscribe yenile, işleme başla
except (
websockets.ConnectionClosed,
OSError,
asyncio.TimeoutError,
) as exc:
delay = min(2 ** attempt + random.uniform(0, 1), MAX_DELAY)
print(f"Bağlantı kesildi ({exc}). {delay:.1f}s sonra tekrar dene.")
await asyncio.sleep(delay)
attempt += 1
async def on_connected(ws):
# Bağlanıldığında subscribe mesajı gönder
import json
await ws.send(json.dumps({"type": "subscribe", "payload": {"topic": "sensors"}}))
async for msg in ws:
print(f"Mesaj: {msg}")
asyncio.run(connect_with_backoff())
Backoff değerleri (örnek)
| Deneme | 2^attempt | Jitter (0–1) | Toplam bekleme |
|---|---|---|---|
| 0 | 1s | ~0.4s | ~1.4s |
| 1 | 2s | ~0.7s | ~2.7s |
| 2 | 4s | ~0.2s | ~4.2s |
| 3 | 8s | ~0.9s | ~8.9s |
| 5 | 32s | ~0.5s | ~32.5s |
| 6+ | 60s (max) | ~0.x s | ≤60s |
Circuit breaker pattern: belirli sayıda ardışık başarısız denemeden sonra bağlantıyı "açık" (open) duruma al ve yeniden denemeden önce daha uzun süre bekle. Bu, tamamen erişilmez bir sunucuya sürekli bağlanma girişimini önler. Üretim sistemlerinde tenacity veya stamina gibi kütüphaneler bu pattern'i hazır sunar.
Bu bölümde
- Server restart, network glitch, ping timeout senaryoları
- open_timeout, ping_interval, ping_timeout parametreleri
min(2^attempt + random(0,1), max_delay)formülü- Reconnect sonrası subscribe yenileme
- Circuit breaker pattern özeti
08 Pratik: minimal chat server
Bu bölümde WebSocket ile gerçek bir chat sistemi inşa ediyoruz: broadcast server, nickname yönetimi ve asyncio ile eşzamanlı gönderme/alma.
Mesaj formatı
// Kullanıcı odaya katıldı
{ "type": "join", "nick": "ahmet", "text": "" }
// Normal mesaj
{ "type": "chat", "nick": "ahmet", "text": "Merhaba!" }
// Kullanıcı ayrıldı (server üretir)
{ "type": "leave", "nick": "ahmet", "text": "" }
Server
import asyncio
import json
import websockets
# nick → websocket eşlemesi
USERS: dict = {}
async def broadcast(msg: dict, exclude=None):
"""Tüm bağlı kullanıcılara mesaj gönder."""
data = json.dumps(msg, ensure_ascii=False)
targets = [ws for ws in USERS.values() if ws is not exclude]
if targets:
await asyncio.gather(*[ws.send(data) for ws in targets])
async def handler(websocket):
nick = None
try:
# İlk mesaj: join frame
raw = await websocket.recv()
data = json.loads(raw)
nick = data.get("nick", "anonim").strip()[:20]
if nick in USERS:
await websocket.send(json.dumps({
"type": "error", "nick": "server",
"text": f"'{nick}' takma adı zaten kullanımda."
}))
await websocket.close(1008, "Nick taken")
return
USERS[nick] = websocket
print(f"[+] {nick} katıldı ({len(USERS)} kullanıcı)")
await broadcast({"type": "join", "nick": nick, "text": ""}, exclude=websocket)
await websocket.send(json.dumps({
"type": "info", "nick": "server",
"text": f"Hoşgeldin {nick}! Odada {len(USERS)} kişi var."
}))
async for raw in websocket:
msg = json.loads(raw)
if msg.get("type") == "chat":
text = str(msg.get("text", ""))[:500]
await broadcast({"type": "chat", "nick": nick, "text": text})
except (websockets.ConnectionClosed, json.JSONDecodeError):
pass
finally:
if nick and nick in USERS:
del USERS[nick]
print(f"[-] {nick} ayrıldı ({len(USERS)} kullanıcı)")
await broadcast({"type": "leave", "nick": nick, "text": ""})
async def main():
async with websockets.serve(handler, "0.0.0.0", 8765):
print("Chat server → ws://localhost:8765")
await asyncio.Future()
asyncio.run(main())
Client — eşzamanlı gönderme ve alma
import asyncio
import json
import sys
import websockets
URI = "ws://localhost:8765"
async def receive_messages(ws):
async for raw in ws:
msg = json.loads(raw)
t, nick, text = msg["type"], msg["nick"], msg.get("text", "")
if t == "chat":
print(f"\r[{nick}] {text}")
elif t == "join":
print(f"\r*** {nick} odaya katıldı ***")
elif t == "leave":
print(f"\r*** {nick} ayrıldı ***")
elif t in ("info", "error"):
print(f"\r[server] {text}")
async def send_messages(ws, nick: str):
loop = asyncio.get_event_loop()
while True:
# stdin'i asyncio ile oku (blocking olmadan)
line = await loop.run_in_executor(None, sys.stdin.readline)
line = line.rstrip("\n")
if line.lower() == "/quit":
await ws.close()
break
await ws.send(json.dumps({
"type": "chat", "nick": nick, "text": line
}))
async def main():
nick = input("Nick: ").strip()
async with websockets.connect(URI) as ws:
# Join mesajı gönder
await ws.send(json.dumps({"type": "join", "nick": nick, "text": ""}))
# Gönderme ve alma'yı eşzamanlı çalıştır
await asyncio.gather(
receive_messages(ws),
send_messages(ws, nick),
)
asyncio.run(main())
Çalıştırma
# Terminal 1: server
python chat_server.py
# Terminal 2: birinci client
python chat_client.py
# Nick: ahmet
# Terminal 3: ikinci client
python chat_client.py
# Nick: fatma
# Mesaj gönder:
# ahmet terminali: Merhaba!
# fatma terminali: *** ahmet odaya katıldı *** → [ahmet] Merhaba!
TLS ile wss://: SSLContext
import asyncio
import ssl
import websockets
def create_ssl_context() -> ssl.SSLContext:
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
ctx.load_cert_chain(
certfile="/etc/ssl/certs/server.crt",
keyfile="/etc/ssl/private/server.key",
)
return ctx
async def main():
ssl_ctx = create_ssl_context()
async with websockets.serve(
handler, "0.0.0.0", 8443, ssl=ssl_ctx
):
print("TLS chat server → wss://localhost:8443")
await asyncio.Future()
asyncio.run(main())
Self-signed sertifika için: openssl req -x509 -newkey rsa:4096 -keyout server.key -out server.crt -days 365 -nodes. Client'ta ssl=True (sistem CA kullan) veya özel CA için ssl.create_default_context() ile custom CA yükleyin.
Bu bölümde
- Chat server: USERS dict, broadcast, join/leave bildirimleri
- Nick çakışması kontrolü ve 1008 ile bağlantı reddi
- asyncio.gather ile eşzamanlı gönderme + alma
- loop.run_in_executor ile async stdin okuma
- ssl.SSLContext ile wss:// TLS desteği