WebSocket — HTTP থেকে full-duplex upgrade
WebSockets
HTTP request-gate ভেঙে দুই-দিক-চালিত channel; Sec-WebSocket-Key-র GUID magic থেকে masked frame-এর proxy-poisoning history — আর scale-এ গেলে stateless-এর comfort কীভাবে হাতছাড়া।
আগে এটা বুঝি
আজ পর্যন্ত আমাদের web-এর মূল ছন্দ একটাই: client জিজ্ঞেস করে, server উত্তর দেয়। Request-response। কিন্তু chat message কে deliver করবে? Game-এ opponent-এর move কে জানাবে? Collaborative editor-এ co-author type করলে সেটা আপনার screen-এ আসবে কীভাবে?
Server অপেক্ষা করতে পারে না — তার হাতে কোনো খোলা লাইনই নেই, যেটা দিয়ে সে ইচ্ছা হলেই কথা বলবে। প্রতিটা HTTP response-ই তার শেষ সুযোগ। এই একমুখী জগৎ ভেঙে দুই-দিক-চালিত (full-duplex) channel তৈরির protocol-ই WebSocket (RFC 6455, 2011)।
সবচেয়ে সুন্দর অংশটা: WebSocket শুরু হয় হুবহু একটা HTTP request দিয়ে — তারপর একটা চুক্তির মধ্যে দুই পক্ষ সম্মত হয় এই connection আর HTTP থাকবে না — এবং সেই একই TCP pipe হঠাৎ bidirectional message-channel হয়ে যায়। এই রূপান্তরটাকে বলে the upgrade dance — আজ আমরা তা step-by-step শিখব, frame-level পর্যন্ত, তারপর নিজের হাতে একটা server বানাব stdlib দিয়ে।
মূল ধারণা
Upgrade dance — চার header-এর হাত-মেলানো
Client পাঠায় একটা বৈধ HTTP GET, কিন্তু বিশেষ headers-সহ:
GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket ← 'আমি protocol বদলাতে চাই'
Connection: Upgrade ← hop-by-hop: এই লক্ষ্যেই এই conn
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== ← random 16-byte, base64
Sec-WebSocket-Version: 13Server রাজি হলে ফেরত দেয়:
HTTP/1.1 101 Switching Protocols ← নতুন status! 'protocol বদলালাম'
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=এরপর ওই TCP connection-এ আর কোনো HTTP নেই — চলব WebSocket frames, দুই দিকেই, যখন তখন।
Accept value-টার গণনা deterministic — এটাই dance-এর proof-of-work-lite:
Sec-WebSocket-Accept = base64( SHA1( key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" ) )ওই অদ্ভুত GUID-টা spec-এ fixed constant। কেন এই নাটক? Client যে key পাঠায় সেটা তো সে-ই জানে — server ‘আমি ঠিক আছি’ বলতে তার কী দরকার?
Frame format — wire-এর সূক্ষ্ম ব্যাকরণ
Connection upgrade হওয়ার পর সব data চলে frame-এ — ছোট binary unit:
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 length (optional) |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
| masking key, length 4 (যদি MASK=1) |
+ - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - +
| payload data ... |
+---------------------------------------------------------------+
| Field | মানে |
|---|---|
| FIN | এই frame message-এর শেষ fragment কি না (বড় message ভাঙে) |
| opcode | 1=text, 2=binary, 8=close, 9=ping, 10=pong, 0=continuation |
| MASK | payload XOR-masked কি না |
| len | 7-bit; 126→পরের ২ byte, 127→পরের ৮ byte |
Opcode-গুলোই control-plane: ping/pong হলো built-in keepalive —
client/server idle-connection জীবিত রাখতে PING পাঠায়, peer তৎক্ষণাৎ
PONG ফেরত দেয়; NAT/firewall-রা নিঃশব্দে idle UDP/TCP mapping মুছে
ফেলে — ping-ই সেই ঘুম-ভাঙা। CLOSE (8) হলো graceful goodbye —
status-code বহন করে (1000=normal, 1001=going away…)।
Masking — client MUST mask, server MUST NOT
Rule-টা asymmetric আর অদ্ভুত লাগে: browser-client প্রতিটা frame random 4-byte mask দিয়ে XOR করে পাঠাবেই, server unmasked পাঠাবে।
masked byte[i] = payload[i] XOR mask_key[i mod 4]কারণটা ঐতিহাসিক নিরাপত্তা-ঘটনা — proxy cache poisoning: মনে করুন corporate network-এ একটা caching HTTP proxy। Malicious webpage browser-কে বলল attacker-server-এ WS connection open করতে; পুরনো proxy-রা এটাকে সাধারণ POST ভেবে ভেতরের content নিজের cache-এ বসিয়ে দিতে পারত — যাতে থাকত crafted HTTP request! পরে কোনো user ওই proxy দিয়ে victim-site request করলে cache থেকে attacker-এর response উঠে আসত। Masking এই attack-এর মূল অস্ত্র (attacker-chosen wire-bytes) নিষ্ক্রিয় করে: random mask-এর কারণে attacker wire-এ ঠিক কী bytes যাবে তা আগে থেকে predict করতে পারে না — cache-able request ‘forge’ করা অসম্ভব। Performance cost টুকু সেই নিরাপত্তার বীমা-premium।
ভেতরে কী ঘটছে
চার পথ, এক সমস্যা — realtime-এর menu
Server-push চাইলে পথ চারটা — খরচ-তুলনা করলে design-sense তৈরি হয়:
| Approach | Latency | Overhead per update | Direction | State on server |
|---|---|---|---|---|
| Polling (৫ সে) | ≤5s + RTT | পূর্ণ HTTP request/response | c→s | নেই (stateless!) |
| Long-polling | ~RTT | reconnect + full headers | c→s trigger | hanging request |
| SSE | ~RTT | ১টা persistent stream | s→c only | এক open stream |
| WebSocket | ~RTT | ছোট frame (~৬ byte) | both | পূর্ণ duplex session |
SSE (Server-Sent Events) মানে: plain HTTP response যেটা কখনো শেষ
হয় না (Content-Type: text/event-stream, data: lines), auto-
reconnect built-in, HTTP-infra friendly — এক-directional feed
(news ticker, dashboard push, AI-token streaming!) এ প্রায় সবসময়
সঠিক choice। WebSocket জেতে শুধু তখনই যখন client-ও frequent
পাঠায়: chat, game-state, collab-cursor, signaling। Polling জেতে
update-rate খুব কম হলে (৫ মিনিট পর পর?) — complexity সবচেয়ে কম।
Scaling pain — stateless comfort হারানো
HTTP-জগতে load-balancer যে-কোনো request যে-কোনো replica-তে পাঠাতে পারে — সব replica সমান, session-state বাইরে (Redis/db)। WebSocket ভাঙে এই স্বাচ্ছন্দ্য:
┌── LB ──▶ replica-A ── ws-conn(user1)
user1 ──────────┤ ▲
user2 ──────────┼──────────────┼── ws-conn(user2) ← এটা A-তেই থাকতে হবে!
└────────────▶ replica-Bতিনটা ব্যথা: (১) Sticky sessions — একই user-এর connection সবসময় same-replica-তে (mid-air migrate হয় না); replica মরলে সব connection একসাথে drop। (২) Fan-out — user1-এর message user2-কে (ভিন্ন replica-তে) পৌঁছাতে pub/sub backbone (Redis/Kafka) লাগবেই। (৩) Memory-per-connection — open socket = fd + buffers + stack; ১০০k concurrent WS ≈ OS-tuning problem। HTTP-র ‘request-শেষে-সব-মুছে- যাওয়া’ আরামটাই এখানে মূল শত্রু।
উদাহরণ
পূর্ণ trace — handshake থেকে echo পর্যন্ত, byte হিসাব-সহ
# ১. Handshake (HTTP phase)
C→ GET /echo HTTP/1.1
Upgrade: websocket / Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
S→ HTTP/1.1 101 Switching Protocols
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
# যাচাই: SHA1(key + GUID) → base64 — আমরা build-এ নিজেই compute করব
# ২. Message phase (frame level)
C→ 81 82 7c 9e 4a 53 34 f7 ← masked TEXT 'Hi'
│ │ └─ mask: 7c 9e 4a 53
│ └─ MASK=1, len=2
└─ FIN=1, op=1(text)
unmask: 34^7c=48('H'), f7^9e=69('i')
S→ 81 02 48 69 ← unmasked TEXT 'Hi'
│ └─ MASK=0, len=2 ← server কখনো mask করে না!
└─ FIN=1, op=1
# ৩. Keepalive
C→ 89 80 <mask> ← PING, len=0 (masked — client!)
S→ 8a 80 ← PONG, len=0 (unmasked)
# ৪. Close
C→ 88 82 <mask> 03 e8 ← CLOSE, code 1000 (03e8) normal
S→ 88 02 03 e8 ← CLOSE echo — TCP close followsFrame overhead-টা মেপে দেখুন: ‘Hi’ পাঠাতে ৮ byte (masked) — polling- এ একই কাজে যেত ২০০+ byte HTTP envelope। এই ব্যবধান-ই high-frequency app-এ WebSocket-এর আসল currency।
ছবিতে
Protocol-switch timeline — HTTP থেকে WS-তে রূপান্তরের মানচিত্র
time ─────────────────────────────────────────────────────────────▶
│◄────────────── HTTP phase ──────────────►│◄────── WS phase ─────────────────►│
C│ GET /chat HTTP/1.1 │ [msg]→ [msg]→ │
│ Upgrade: websocket │ ◄[msg] ◄[ping] │
S│ │ [pong]→ │
│ 101 Switching Protocols ◄── THE MOMENT │ (idle NAT-timeout এড়াতে) │
│ Accept: SHA1(key+GUID) │ ◄[msg] │
│ │ [close]→ │
│ │ ◄[close-echo] TCP FIN │
দুই দিকেই যেকোনো মুহূর্তে যেকোনো সংখ্যক frame — কোনো request-gate নেই।
LB-র দৃষ্টিতে: 101-এর পরে এটা opaque byte-pipe — path/header আর দেখা যায় না
(WSS-এ তো encryption-ও — L7 routing তখন শুধু handshake-মুহূর্তেই সম্ভব)।নিজে চালিয়ে দেখুন
Echo-server-এ live WS session — client লিখে পাঠান
ধাপ ১ — client install আর minimal script:
pip install websockets # asyncio-based client/server library# ws_client.py
import asyncio, websockets
async def main():
async with websockets.connect('wss://echo.websocket.events') as ws:
greet = await ws.recv() # server প্রথমেই welcome-line পাঠায়
print(f'welcome: {greet}')
await ws.send('hello from the tutorial')
reply = await asyncio.wait_for(ws.recv(), timeout=5)
print(f'server echoed: {reply}')
await ws.send('one'); await ws.send('two') # full-duplex অনুভূতি
print(await ws.recv(), '|', await ws.recv())
asyncio.run(main())python3 ws_client.py
# server echoed: hello from the tutorial
# one | twowss:// prefix মানে TLS-wrapped WS — handshake-টা হয় TLS-record-এর
ভেতরে, observer কিছুই দেখে না।
ধাপ ২ — masked-frame নিজের চোখে: public endpoint-এর উপর packet- capture করলে WSS-এ encrypted দেখাবে; masked-frame দেখতে local plaintext server দরকার (build section-এর server চালিয়ে tcpdump/wireshark দিয়ে port 8765 capture করুন)। প্রত্যাশিত দৃশ্য:
$ tcpdump -i lo0 -nn -X port 8765 -c 3
0x0000: 81 82 7c 9e 4a 53 34 f7 ← client frame: MASK=1, len=2
masked payload — 'Hi' পড়া যায় না!
0x0010: 81 02 48 69 ← server frame: MASK=0 — plaintext 'Hi'Masking-এর asymmetric rule — ঠিক এই জোড়া লাইনে।
ধাপ ৩ — ভাবুন: উপরের client-এ দুই send back-to-back করলে server দুটা আলাদা frame পায়, নাকি এক merged? Library-রা buffering করে কি না যাচাই করুন — frame-boundary বনাম message-boundary-র পার্থক্যটা TCP-programming-এর সেই পুরনো পাঠের পুনরাবৃত্তি।
Public echo endpoint-এ connect, send, receive করে full-duplex behavior live অনুভব করা যায় — আর library-র debug-log বা hex-dump দেখলেই masked-frame theory-র হাতে-নাগাল প্রমাণ।
নিজে বানান
ws_server.py — handshake + frame codec, stdlib-only echo server
- Raw TCP listen; প্রথম bytes থেকে HTTP request parse করে Sec-WebSocket-Key বের করুন
- Accept = base64(SHA1(key+GUID)) হিসাব করে 101 response পাঠান
- Frame reader: 2-byte header, extended-length branches, mask-key, XOR-unmask
- Opcode dispatch: text/binary echo, ping→pong, close→close-echo
- Threading দিয়ে multiple client — আর experiment-এর client snippet দিয়ে test
~110 লাইনে পুরো protocol-এর core — কোনো dependency নেই:
#!/usr/bin/env python3
"""ws_server.py — minimal WebSocket echo server (stdlib only, RFC 6455 subset)"""
import base64, hashlib, socket, threading
GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
def recv_exact(conn, n):
buf = b''
while len(buf) < n:
part = conn.recv(n - len(buf))
if not part:
raise ConnectionError('closed')
buf += part
return buf
def read_http_request(conn):
buf = b''
while b'\r\n\r\n' not in buf:
buf += conn.recv(4096)
head = buf.split(b'\r\n\r\n')[0].decode('latin1')
lines = head.split('\r\n')
hdrs = {}
for line in lines[1:]:
k, _, v = line.partition(':')
hdrs[k.strip().lower()] = v.strip()
return hdrs
def handshake_response(hdrs):
if 'websocket' not in hdrs.get('upgrade', '').lower():
raise ValueError('not a websocket upgrade')
key = hdrs['sec-websocket-key']
accept = base64.b64encode(
hashlib.sha1((key + GUID).encode()).digest()).decode()
return ('HTTP/1.1 101 Switching Protocols\r\n'
'Upgrade: websocket\r\n'
'Connection: Upgrade\r\n'
f'Sec-WebSocket-Accept: {accept}\r\n\r\n').encode()
def read_frame(conn):
b1, b2 = recv_exact(conn, 2)
fin, opcode = b1 >> 7, b1 & 0x0F
masked, length = b2 >> 7, b2 & 0x7F
if length == 126:
length = int.from_bytes(recv_exact(conn, 2), 'big')
elif length == 127:
length = int.from_bytes(recv_exact(conn, 8), 'big')
mask = recv_exact(conn, 4) if masked else None
payload = bytearray(recv_exact(conn, length)) if length else bytearray()
if mask:
for i in range(len(payload)):
payload[i] ^= mask[i % 4]
return fin, opcode, bytes(payload)
def encode_frame(opcode, payload):
head = bytes([0x80 | opcode])
n = len(payload)
if n < 126:
head += bytes([n])
elif n < 65536:
head += bytes([126]) + n.to_bytes(2, 'big')
else:
head += bytes([127]) + n.to_bytes(8, 'big')
return head + payload
def serve_conn(conn, addr):
print(f'[+] {addr} connected')
try:
conn.sendall(handshake_response(read_http_request(conn)))
print(f'[+] {addr}: handshake ok — frames now')
while True:
fin, op, payload = read_frame(conn)
if op == 8: # CLOSE
conn.sendall(encode_frame(8, payload))
break
if op == 9: # PING → PONG
conn.sendall(encode_frame(10, payload))
continue
if op == 10: # PONG (unsolicited)
continue
if op in (1, 2): # TEXT/BINARY echo
text = payload.decode(errors='replace')
print(f'[{addr}] ← {text!r}')
conn.sendall(encode_frame(1, f'echo: {text}'.encode()))
except (ConnectionError, ValueError) as exc:
print(f'[-] {addr}: {exc}')
finally:
conn.close()
print(f'[-] {addr} closed')
srv = socket.socket()
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
srv.bind(('127.0.0.1', 8765))
srv.listen(8)
print('[ws] listening on ws://127.0.0.1:8765')
while True:
conn, addr = srv.accept()
threading.Thread(target=serve_conn, args=(conn, addr), daemon=True).start()Test — experiment-এর client-টা localhost-এ ঘুরিয়ে:
import asyncio, websockets
async def main():
async with websockets.connect('ws://127.0.0.1:8765') as ws:
await ws.send('local test')
print(await ws.recv()) # echo: local test
asyncio.run(main())নিজে বাড়ান: (১) fragmentation support (FIN=0 continuation frames জমান); (২) broadcast mode — সব connected client-কে relay (chat!); (৩) close-frame-এর status-code parse করে log করুন; (৪) একটা malicious frame পাঠান যেখানে declared-length আসল payload-এর চেয়ে বড় — আপনার reader কীভাবে hang/crash করে দেখুন, তারপর timeout-guard বসান (protocol hardening-এর প্রথম হাতে-কলমে পাঠ)।
বাস্তব সিস্টেমে
WebSocket বাস্তব জগতে
Chat আর collaboration। Slack, Discord, Figma, Google Docs — সবই persistent WS (+ fallbacks): cursor-position হাজার বার মিনিটে — polling-এ অসম্ভব, frame-এ তুচ্ছ। Figma-র case study পড়ার মতো: binary protocol over WS, delta-sync — bandwidth-এ বিস্ময়কর কম।
Trading আর dashboards। Exchange feed (Binance WS API!), Grafana-live, sports-score push — server-initiated updates-এর ক্লাসিক। Latency arbitrage-এ polling-এর 5-second gap মানেই টাকা হারানো।
Scaling stories। Discord: single Python process এক million concurrent WS — async event-loop + OS-fd tuning-এর textbook। Slack-এর fan-out architecture: connection-gateway layer + pub/sub backbone — আজকের scaling-section-এর তিন pain-point-এর production-grade সমাধান।
Infra reality। Modern LB/proxy WS-aware (101 pass-through + long-idle-timeout) — কিন্তু default idle-timeout (60s!) WS kill করে; production-এ ping-interval config mandatory hygiene। WSS termination edge-এ হলে end-to-end app-layer visibility হারায় — security-review-তে আলাদা topic।
WS-এর পরের অধ্যায়। h2/h3-জগতে WS-over-h2 (RFC 8441 extended CONNECT) আর WebTransport — QUIC-র streams-এর উপরে same use-case, multiplexing bonus সহ। WS-র সরল মডেল তবু টিকে আছে: এটা বুঝলে বাকি সব abstraction transparent।
যে ভুলগুলো সবাই করে
“WebSocket "HTTP replace" করে — এখন সব কিছু WS দিয়েই করা উচিত।”
WS একটা specialized tool — REST/HTTP-র বিকল্প নয়। CRUD APIs, page loads, file uploads — request-response world এখনো HTTP-র; সেখানে WS যোগ করবে stateful-session burden, custom framing, আর infra-complexity — লাভ শূন্য। Rule of thumb: interaction-pattern দেখুন — client-driven, discrete operations → HTTP; continuous, both-direction, low-latency → WS। বেশিরভাগ product-এ দুটোই পাশাপাশি চলে: HTTP-তে auth+bootstrap, WS-তে live-layer।
“Masking একটা encryption — নিরাপত্তা দেয়।”
XOR-মাস্ক 4-byte key, যেটা একই frame-এই plaintext-এ যায়! এটা confidentiality মুহূর্তেই শূন্য — কেউ wire পড়লে সঙ্গে সঙ্গে unmask। Masking-এর একমাত্র কাজ: attacker-কে predictable wire-bytes choose করতে না দেওয়া — proxy-cache-poisoning era-র defense। Confidentiality চাইলে সেটা দেয় WSS (TLS layer) — আলাদা স্তর, আলাদা দায়িত্ব। Masking = anti-tampering-at-transit-tools, encryption নয়।
“WS connection-এর ভেতরের messages guaranteed ordered আর reliable — TCP-র মতোই ভরসা।”
Transport হিসেবে TCP reliable-ordered — কিন্তু application-level guarantee নয়! Connection drop হলে in-flight messages হারায়; client reconnect করলে মিস-হওয়া window কে fill করবে? কেউই না — যদি না আপনি sequence-numbers + resume-logic নিজে build করেন। Production chat-system সবাই তাই ack/redelivery/idempotency app-layer-এ রাখে। TCP-র promise শুধু ‘যা পৌঁছাল, order-এ পৌঁছাল’ — ‘সব পৌঁছাবেই’ নয়। Distributed-systems module-এ এই gap-টাই মূল পাঠ হবে।
“SSE পুরনো/দুর্বল — WebSocket-ই modern solution।”
Use-case mismatch-ই এখানে মূল ভুল। One-directional server-feed-এ (token-by-token AI responses!, news, metrics) SSE simpler: plain HTTP (auto-proxy-friendly), auto-reconnect + last-event-ID built-in, no custom server-lib needed। WS তখনই দরকার যখন upstream traffic বিশাল। AI-chat-app-গুলোর বেশিরভাগই আজ SSE-ই চালায় — নতুনতম workload-এ পুরনো ‘দুর্বল’ tool-এর জয়। Right-sizing-ই জেতে, novelty নয়।
বুঝেছেন কি না দেখুন
1Sec-WebSocket-Key/Accept exchange-টা challenge-response authentication
নয় — কারণ কী? তাহলে এর প্রকৃত কাজটা এক বাক্যে কী?
যুক্তি
Authentication-এ secret দরকার যা একপক্ষ জানে অন্যপক্ষ নয়; এখানে key client-ই generate করে, algorithm public, GUID spec-এ printed — কোনো secret নেই, তাই verify-ই করে না কেউ। প্রকৃত কাজ: capability/ awareness proof — server প্রমাণ করে সে RFC 6455-বোঝা active implementation (arbitrary computation perform করতে পারে), আর একই সাথে cross-protocol/speech-act confusion বন্ধ করে। এটা handshake-as- handshake-recognition — identity-proof নয়, protocol-capability-proof।
2Client পাঠাল frame-header: 81 FE 01 F4 + 4-byte mask + payload।
Opcode কী, payload কত বড়? একই payload server-এর পাঠালে header কত byte?
প্রয়োগ
81 FE 01 F4 + 4-byte mask + payload।
Opcode কী, payload কত বড়? একই payload server-এর পাঠালে header কত byte?81: FIN=1, opcode=1 (text)। FE: MASK=1, 7-bit-len=126 → extended
16-bit length follows = 01 F4 = 500 byte। Total client-header:
2+2+4 = 8 byte। Server-এর পাঠালে (MASK=0): 81 7E 01 F4 — 4 byte
(126 marker + 2-byte length)। Asymmetric overhead — masking-tax
শুধু client-direction-এ।
3Proxy-cache-poisoning attack-টায় masking না থাকলে ঠিক কোন step-টা
সম্ভব হতো? একটা আধুনিক HTTPS-only world-এ এই attack কি আজও possible?
যুক্তি
Attack-chain-এর মূল ধাপ: attacker চাইত wire-এ ঠিক যে bytes যাবে সেগুলো control করা — যাতে মাঝের naive proxy ওই bytes-কে বৈধ HTTP request হিসেবে cache-এ বসায়। Masking ছাড়া attacker app-payload choose করলেই wire-bytes deterministic — forge সম্ভব। Random mask মানে wire-bytes = payload XOR random — attacker predict করতে পারে না যে proxy কী দেখবে। আজকের HTTPS-world-এ proxy তো ciphertext দেখে (cache-poisoning impossible at that layer) — কিন্তু WS plaintext (ws://) deployment আজও আছে intranet-এ, আর defense-in-depth principle: layer-assumption-এর উপর security দাঁড় করানো বিপজ্জনক। তাই rule টিকে আছে।
4একটা collaborative whiteboard-app design করছেন: 50-user room, প্রতি
user সেকেন্ডে ~10 cursor-update, drawing-strokes occasional, undo/history
critical। Transport + delivery-design sketch করুন — কোন data কোন
mechanism-এ, fan-out কীভাবে, কোন guarantee কোথায় লাগবে।
ডিজাইন
Transport: এক WSS connection per user (bidirectional দরকার-ই — cursor upstream-এ যায়)। Cursor-updates: unreliable/fast-path — coalesce করে (latest-wins; পুরনো position replay অর্থহীন), binary frames, no-ack — loss টিকে যাবে পরের update-এ (10Hz-এ self-healing)। Drawing strokes: reliable ordered stream — stroke-loss মানে artifact; idempotent stroke-id সহ। Undo/history: reliable + persisted event-log (append-only ops, sequence-numbered) — reconnect- এ last-seq দিয়ে catch-up query; এটাই CRDT/event-sourcing-এর প্রথম ধাপ (distributed-systems module-এ পূর্ণ form)। Fan-out: room-scoped pub/sub — gateway replicas আলাদা হলে Redis/Kafka backbone। Design-moral: এক connection, traffic-class-wise ভিন্ন guarantee — QUIC-lesson-এর WS-flavored echo।
5Opcode 0, 1, 2, 8, 9, 10 — প্রতিটার নাম আর কাজ এক লাইনে।
স্মরণ
0 continuation — আগের fragmented message-এর ধারাবাহিক অংশ;
1 text-message (UTF-8); 2 binary-message; 8 close-handshake
(status-code বহন); 9 ping — keepalive probe; 10 pong — ping-এর
জবাব। Control-frames (8,9,10) যেকোনো মুহূর্তে interleaved হতে পারে,
max 125-byte payload — data-frame-এর সাথে সম-অবস্থানে।
এরপর কী
এরপর কী
Application-টা এখন real-time — কিন্তু যখন million-user-এ scale হবে, এক origin-server যথেষ্ট নয়: দরকার edge-এ caches, পেছনে load-balancer, আর সবার সামনে reverse-proxy। এই তিন ভূমিকার স্থাপত্য — forward/reverse confusion-এর চির-নিবৃত্তি, round-robin থেকে consistent-hashing, anycast- এ BGP-magic, আর CDN-এর hit-ratio economics — পরের লেসনে।
আরও পড়ুন
- RFC 6455 — The WebSocket Protocol — I. Fette, A. Melnikov · Handshake, framing, masking requirement-এর rationale — protocol-এর মূল specification
- RFC 9110 — HTTP Semantics (Upgrade mechanism context) — R. Fielding et al. · 101 Switching Protocols আর Connection/Upgrade header-এর HTTP-side contract
- RFC 8441 — Bootstrapping WebSockets with HTTP/2 — P. McManus · WS-over-h2 extended CONNECT — multiplexed transport-এ WebSocket tunneling