Course-এর সূচি

2.6

Real-Time ২: WebSocket, WebRTC আর এদের Scale করা

দুই দিকে একসঙ্গে কথা বলার connection, peer-to-peer media, পাঁচটা উপায়ের মধ্যে বেছে নেওয়া, আর লাখো socket-এ message ছড়িয়ে দেওয়া।

55 মিনিট পড়া + Animation

টিপস: ডটেড-আন্ডারলাইন করা যেকোনো শব্দে ট্যাপ বা Hover করলে তার অর্থ দেখবে।

এই Lesson শেষে তুমি পারবে

  • WebSocket-এর Upgrade Handshake একটা একটা Header ধরে বোঝাতে, আর Sec-WebSocket-Key ও Sec-WebSocket-Accept কী প্রমাণ করে আর কী করে না, তা বলতে।
  • একটা WebSocket Frame পড়তে (FIN, Opcode, Mask bit, Length, Masking Key), এর Header কেন ২–১৪ Byte আর Client-কে কেন Mask করতেই হয়, তা বলতে।
  • Ping/Pong Heartbeat, Close Code, Subprotocol আর wss:// ঠিকভাবে ব্যবহার করতে, আর Log-এ Token ফাঁস না করে বা Cross-site Hijack-এর দরজা না খুলে একটা Socket Authenticate করতে।
  • WebSocket Scale করতে: একটা Server-এ কত Connection, Load Balancer আর Idle Timeout, Pub/Sub Backplane সহ Stateless Gateway, Presence, Ack আর Sequence Number দিয়ে পৌঁছানোর নিশ্চয়তা, Reconnect-এর ঝড়, Deploy-এর সময় Connection সরানো, আর ১০ লাখ User-এর Fan-out হিসাব।
  • একটা WebRTC Call কীভাবে তৈরি হয় (Signaling, SDP Offer/Answer, ICE, STUN, TURN), কীভাবে Encrypted হয় (DTLS-SRTP), Data Channel কী, আর কখন Group Call-এ SFU বা MCU লাগে, তা বোঝাতে।
  • Short Polling, Long Polling, Server-Sent Events, WebSocket আর WebRTC-এর মধ্যে বাছতে, আর WebTransport, MQTT ও gRPC Streaming কোথায় বসে তা জানতে।

#এই Lesson কোথায় বসে

Lesson ২.৫-এ আমরা সেই কৌশলগুলো দেখেছি যেগুলো সাধারণ HTTP-র ভেতরেই থাকে: Short Polling, Long Polling আর Server-Sent Events। এদের সবার একটা সীমা আছে: একই পথে Client উল্টো দিকে কথা বলতে পারে না। এই Lesson-এ সেই দুটো প্রযুক্তি, যেগুলো এই সীমা ভেঙে দেয়; তারপর Real-time System-এর আসল কঠিন অংশ: লাখো User-এর জন্য এদের চালানো।

কয়েকটা টুকরো Chapter ১-এ আগেই এসেছে; এখানে সেগুলো আবার না পড়িয়ে তার ওপর দাঁড়াব:

  • Lesson ১.৩ দেখিয়েছে WebSocket Server কেন ইচ্ছে করেই Stateful, আর Pub/Sub-এর ধারণাটা ছুঁয়ে গেছে।
  • Lesson ১.৪ WebRTC, Signaling, STUN, TURN আর SFU-র পরিচয় দিয়েছে; Lesson ১.৭ দেখিয়েছে NAT, Hole Punching আর Symmetric NAT।
  • Lesson ১.৯ আর Lesson ১.১০-এ ছিল TCP, TLS, Keepalive আর NAT-এর Idle Timeout; Lesson ১.১৩ দেখিয়েছে WebSocket Proxy-র জন্য Nginx-এ কী Setting লাগে।

#WebSocket: একটাই Connection, দুই দিকে

WebSocket হলো Browser (বা যেকোনো Client) আর Server-এর মধ্যে একটা Full-duplex, দ্বিমুখী, স্থায়ী Connection, যার Standard RFC 6455 (২০১১)। Full-duplex মানে দুই পাশ একসাথে পাঠাতে পারে; স্থায়ী মানে Connection মিনিট বা ঘণ্টার পর ঘণ্টা খোলা থাকে, আর যেকোনো পাশ যখন খুশি Message পাঠাতে পারে, আগে কোনো Request লাগে না। URL হয় ws:// বা, বাস্তবে সবসময়, wss://।

Client                                    Server
  |--- HTTP GET  (Upgrade: websocket) ----->|   Handshake পর্ব (সাধারণ HTTP/1.1)
  |<-- 101 Switching Protocols -------------|
  |=========================================|
  |          [ WebSocket Connection ]       |   একই TCP Connection, আর HTTP না
  |<======== Frame (Text বা Binary) =======>|
  |<======== Frame (Text বা Binary) =======>|

এটা চলে তিন ধাপে:

  1. Handshake: Client একটা সাধারণ HTTP Request পাঠায়, তাতে থাকে Upgrade: websocket Header।
  2. Protocol বদল: Server রাজি হলে উত্তরে দেয় Status 101 Switching Protocols।
  3. Data আদান-প্রদান: Connection তখন HTTP ছেড়ে WebSocket Frame Protocol-এ কথা বলে। এই একটা Connection দিয়েই Client আর Server যেকোনো মুহূর্তে একে অপরকে Message পাঠাতে পারে।

এতে লাভটা কী? Polling-এর তুলনায় ফাঁকা আসা-যাওয়া নেই, প্রতিবার HTTP Header (Request-প্রতি শত শত Byte) নেই, পরের Poll-এর অপেক্ষাও নেই: একটা Message-এর খরচ কয়েক Byte-এর Frame, আর পাঠানোর পর এক Network যাত্রাতেই পৌঁছে যায়। Text (UTF-8, সাধারণত JSON) আর Binary Data (Browser-এ ArrayBuffer বা Blob, মানে Protobuf বা কাঁচা Audio) দুটোই সরাসরি যায়।

#Upgrade Handshake

শুরুর Request একটা সাধারণ HTTP/1.1 GET (GET-ই হতে হবে, আর অন্তত HTTP/1.1):

GET /chat HTTP/1.1
Host: chat.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Origin: https://chat.example.com
Sec-WebSocket-Protocol: chat.v2, chat.v1
Sec-WebSocket-Extensions: permessage-deflate
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat.v2
Headerকে পাঠায়কেন
Upgrade: websocket + Connection: Upgradeদুই পাশইProtocol বদলানোর অনুরোধ আর সম্মতি। দুটোই Hop-by-hop Header, তাই পথের প্রতিটা Proxy-কে এগুলো সামনে পাঠানোর মতো করে সাজাতে হয়
Sec-WebSocket-KeyClient১৬টা এলোমেলো Byte, base64 করা, প্রতিটা Connection-এ নতুন
Sec-WebSocket-AcceptServerbase64(SHA-1(Key + নির্দিষ্ট String 258EAFA5-E914-47DA-95CA-C5AB0DC85B11))
Sec-WebSocket-Version: 13Clientচালু একমাত্র Version
OriginBrowserযে Page Socket খুলেছে; Server-কে এটা যাচাই করতেই হবে (নিচে Authentication অংশ দেখো)
Sec-WebSocket-ProtocolClient প্রস্তাব দেয়, Server একটা বাছেSocket-এর ভেতরে কোন Application Protocol চলবে (Subprotocol)
Sec-WebSocket-ExtensionsClient প্রস্তাব দেয়, Server মেনে নেয়permessage-deflate Compression-এর মতো Extension (RFC 7692)

Key আর Accept-এর জোড়া প্রমাণ করে Server WebSocket Handshake বুঝেছে; ফলে সাধারণ কোনো HTTP Server বা পুরনো Response ফেরত দেওয়া কোনো Cache-কে ভুল করে WebSocket Endpoint ভাবা যায় না। এটা Security না: যে কেউ এটা হিসাব করতে পারে। Status ১০১ না হলে, বা Accept-এর মান ভুল হলে, Browser Connection বাতিল করে।

WebSocket Upgrade: একটা HTTP Request থেকে দুই দিকের পাইপ

WebSocket শুরু হয় একদম সাধারণ একটা HTTP/1.1 GET দিয়ে। কোন Header-গুলো একে স্থায়ী, দুই দিকে একসাথে চলা Connection বানায়, আর কী করলে সেটা ভাঙে, দেখো।

পরিস্থিতি

Port ৪৪৩-এ wss://: HTTP-র প্রথম Byte যাওয়ার আগেই TCP আর TLS খোলা হয়ে গেছে।

Browserchat.example.com
Proxy / LBLayer 7
Chat Server
GET /chat · Upgrade: websocket
ধাপ 1 / 8
1

Browser একটা সাধারণ HTTP/1.1 GET পাঠায়: Upgrade: websocket, Connection: Upgrade, Sec-WebSocket-Version: 13, এলোমেলো ১৬ Byte-এর একটা Sec-WebSocket-Key (base64), আর Page-এর Origin। এটা এখনো সাদামাটা HTTP, তাই Port ৪৪৩-এ সাধারণ Proxy আর Firewall পার হয়ে যায়।

#ws:// আর wss://

ws:// চলে Port ৮০-তে, খোলা লেখায়; wss:// চলে TLS-এর ভেতরে, সাধারণত Port ৪৪৩-এ, ঠিক HTTPS-এর মতো। সবসময় wss:// ব্যবহার করো:

  • এতে Message Encrypted থাকে আর Server-এর পরিচয় যাচাই হয়।
  • Internet-এ অনেক বেশি নির্ভরযোগ্যভাবে পার হয়। যেসব Corporate Proxy আর Mobile যন্ত্র খোলা HTTP ঘেঁটে দেখে, তারা প্রায়ই Upgrade Header মুছে দেয় বা লম্বা Connection কেটে দেয়; TLS-এর ভেতরে তারা শুধু একটা Encrypted Stream দেখে।
  • HTTPS Page থেকে ws:// খুলতে দেয় না Browser (Mixed Content)।

#Frame: Wire-এ আসলে কী যায়

Handshake-এর পরে প্রতিটা Message যায় এক বা একাধিক Frame হিসেবে। Header খুবই ছোট:

 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)     |   (len = 126 হলে 16 bit,      |
|N|V|V|V|       |S|             |    len = 127 হলে 64 bit)      |
| |1|2|3|       |K|             |                               |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
|       ... extended payload length-এর বাকিটা (64 bit হলে)        |
+-------------------------------+-------------------------------+
|      Masking key (4 Byte, শুধু Client → Server Frame-এ)        |
+---------------------------------------------------------------+
|                         Payload data                          |
+---------------------------------------------------------------+
Fieldআকারমানে
FIN১ bit১ = এই Message-এর শেষ Frame; ০ = আরও টুকরো আসছে
RSV1–3৩ bitকোনো Extension ব্যবহার না করলে ০ (Compression RSV1 চালু করে)
Opcode৪ bit0x0 Continuation, 0x1 Text (বৈধ UTF-8), 0x2 Binary, 0x8 Close, 0x9 Ping, 0xA Pong
Mask bit১ bit১ = পেছনে Masking Key আছে; Client-এর প্রতিটা Frame-এ ১, Server-এর প্রতিটা Frame-এ ০ হতেই হবে
Payload Length৭ bit০–১২৫ = এটাই Length; ১২৬ = পরের ২ Byte-এ Length; ১২৭ = পরের ৮ Byte-এ Length
Masking Key০ বা ৪ Byteপ্রতিটা Frame-এ এলোমেলো; Payload-এর প্রতিটা Byte এর সাথে XOR হয়

তাই Header ২ থেকে ১৪ Byte: Server-এর ছোট Frame-এ ২ Byte, ১৬ bit Length হলে ৪, ৬৪ bit Length হলে ১০, আর Client-এর Frame-এ Mask-এর জন্য আরও ৪। Server থেকে Client-এর Frame ২–১০ Byte; Client থেকে Server-এর Frame ৬–১৪ Byte।

Client-কে কেন Mask করতেই হয়। Masking Encryption না: Key তো একই Frame-এ যাচ্ছে। এটা রক্ষা করে মাঝের যন্ত্রগুলোকে। WebSocket Standard হওয়ার আগে গবেষকেরা দেখিয়েছিলেন, একটা Script Browser দিয়ে এমন Byte পাঠাতে পারে, যা কোনো বাজেভাবে লেখা Transparent Proxy অন্য কোনো Site-এর নতুন HTTP Request হিসেবে পড়বে, তারপর আক্রমণকারীর বানানো “Response” ওই Proxy-র পেছনের সবার জন্য Cache করে রাখবে (Cache Poisoning)। Browser যখন নিজে এলোমেলো একটা Mask বাছে, Script আর Wire-এ যাওয়া Byte নিয়ন্ত্রণ করতে পারে না। Server-কে Mask করতে হয় না, কারণ তার Byte অবিশ্বস্ত Page Script বাছে না; আর Client-এর কাছ থেকে Mask ছাড়া Frame পেলে Server-কে Connection বন্ধ করতেই হবে।

Message বনাম Frame। একটা Message টুকরো করে পাঠানো যায়: প্রথম Frame-এ থাকে Opcode (Text বা Binary) আর FIN = ০, পরের Frame-গুলো Opcode Continuation নেয়, আর শেষটায় FIN = ১। এতে অজানা আকারের Message-ও Stream করে পাঠানো যায়। Control Frame (Close, Ping, Pong) সর্বোচ্চ ১২৫ Byte বয়, কখনো টুকরো হয় না, আর বড় একটা Message-এর টুকরোগুলোর ফাঁকে ঢুকে যেতে পারে; তাই ৫০ MB Upload চললেও Heartbeat তার পেছনে আটকে থাকে না। কাঁচা TCP-র মতো না, WebSocket Message-এর সীমানা রাখে: Receiver পায় গোটা Message, Byte Stream না।

একটা WebSocket Frame, একটা একটা Field ধরে

সবচেয়ে লম্বা অবস্থাটা আঁকা, প্রতি সারিতে ৩২ bit। ছোট Frame-এ Extended Length-এর সারি থাকে না, আর Server-এর Frame-এ Masking Key নেই; তাই Header ২ থেকে ১৪ Byte।

পরিস্থিতি

Standard-এর Mask করা Text Frame: 81 85 37 fa 21 3d 7f 9f 4d 51 58।

08162431

FIN · RSV1–3 · 1+3

1 000

Opcode · 4

…

Mask bit · Length · 1+7

…

Extended Length · 0/16

…

Extended Length (৬৪ bit হলে, বাকিটা) · ০/৪৮

…

Masking Key (শুধু Client → Server) · ০/৩২

…

Payload Data · বাকিটা

…

ধাপ 1 / 5
1

FIN = 1: এটা Message-এর শেষ (এখানে একমাত্র) Frame। তিনটা RSV bit শূন্য থাকে, যদি না permessage-deflate-এর মতো কোনো Extension এগুলো ব্যবহার করে (Compression RSV1 চালু করে)।

#Subprotocol আর Extension

WebSocket শুধু Message বয়ে নেয়; তাদের মানে কী, সে বলে না। Sec-WebSocket-Protocol দিয়ে ঠিক হওয়া Subprotocol ওপরের Application Protocol-এর নাম বলে: যেমন GraphQL Subscription-এর জন্য graphql-transport-ws, WebSocket-এর ওপর MQTT-র জন্য mqtt, বা Message Broker-এর জন্য STOMP। নিজের Protocol-এর Version-ও এভাবেই রাখো (chat.v2), তাহলে Migration-এর সময় পুরনো আর নতুন Client পাশাপাশি চলতে পারে। Client যেটা প্রস্তাব করেনি, Server সেরকম Subprotocol বাছলে Client Connection বাতিল করে।

চালু Extension একটাই, permessage-deflate (Compression)। এটা JSON-কে ৭০–৯০% ছোট করতে পারে, কিন্তু প্রতি Connection-এ Memory খায় (দুই পাশে একটা করে Compression Context, দশ থেকে শত Kilobyte, যদি না Context Takeover বন্ধ করো), CPU-ও খায়; আর আক্রমণকারীর বাছা লেখার সাথে গোপন জিনিস একসাথে Compress করলে সেটা ফাঁস হতে পারে (HTTPS-এর CRIME/BREACH-এর মতো একই ধরনের আক্রমণ)। অনেক বড় Deployment এটা শুধু বড় Message-এ চালু করে, নয়তো একদমই না।

#বন্ধ করা: Close Frame আর Status Code

পরিষ্কারভাবে বন্ধ করা মানে একটা Closing Handshake: এক পাশ ২ Byte-এর Status Code আর চাইলে একটা কারণ (সর্বোচ্চ ১২৩ Byte) সহ Close Frame পাঠায়, অন্য পাশ Close Frame দিয়ে উত্তর দেয়, তারপর TCP Connection বন্ধ হয় (সাধারণত আগে Server)। যে Code-গুলো জানা দরকার:

Codeনামকখন
1000Normal Closureকাজ শেষ (User Chat ছেড়ে গেছে)
1001Going AwayServer বন্ধ হচ্ছে, বা Browser Page ছেড়ে চলে যাচ্ছে
1002Protocol Errorভুল গড়নের Frame, Mask ছাড়া Client Frame
1003Unsupported Dataযেমন শুধু Text নেয় এমন Endpoint-এ Binary পাঠানো
1006Abnormal ClosureWire-এ কখনো যায় না: Close Frame ছাড়াই Connection কেটে গেলে নিজের দিকে জানানো হয় (Network, Proxy, Crash)
1007Invalid Payload Dataবৈধ UTF-8 না, এমন Text Frame
1008Policy Violationসাধারণ “অনুমতি নেই” (খোলার পর Auth ব্যর্থ, নিয়ম ভাঙা)
1009Message Too BigReceiver-এর সীমার চেয়ে বড়
1011Internal ErrorServer অপ্রত্যাশিত সমস্যায় পড়েছে
1012Service Restartপরিকল্পিত Restart: এখনই Reconnect করো
1013Try Again Laterচাপে আছে: পরে Reconnect করো
4000–4999নিজস্ব ব্যবহারতোমার Application-এর নিজের Code (যেমন 4001 “Token-এর মেয়াদ শেষ, Refresh করে আবার Connect করো”)

Client-এর উচিত Code দেখে সিদ্ধান্ত নেওয়া: ১০০০ মানে Reconnect না, ১০১২ মানে এখনই Reconnect (Jitter সহ), ১০১৩ মানে পিছিয়ে যাও, ১০০৬ মানে Network ভেঙেছে, তাই Backoff দিয়ে Reconnect করে Resume।

#Heartbeat: Ping আর Pong

Data না বইলে একটা WebSocket কিছুই পাঠায় না, আর এতে দুটো সমস্যা হয়:

  1. মাঝের যন্ত্র চুপ Connection কেটে দেয়। Load Balancer, Proxy, NAT Gateway আর Mobile Carrier কিছুক্ষণ অলস থাকা Connection ফেলে দেয়: AWS ALB ডিফল্টে ৬০ সেকেন্ডে (৪,০০০ সেকেন্ড পর্যন্ত বাড়ানো যায়), NLB ৩৫০ সেকেন্ডে, Nginx-এর proxy_read_timeout ৬০ সেকেন্ডে, আর বাসার Router ও Carrier কয়েক মিনিটে।
  2. মৃত অন্য পাশকে জীবিত মনে হয়। যে Phone Lift-এ ঢুকল বা Network হারাল, সে FIN বা Close Frame কিছুই পাঠায় না। Server হাতে রাখে একটা Half-open Connection: Write সফল হয় (এটা শুধু Kernel Buffer ভরে), Presence বলে “Online”, আর Socket Memory আটকে রাখে। TCP Keepalive ডিফল্টে বন্ধ, আর চালু থাকলেও টের পেতে ২ ঘণ্টার বেশি লাগে (Lesson ১.১০)।

সমাধান একটা Heartbeat: Server প্রতি ২০–৩০ সেকেন্ডে একটা Ping Frame পাঠায়; অন্য পাশকে একই Payload সহ Pong দিয়ে উত্তর দিতে হয়। দুটো Pong না এলে Server Socket বন্ধ করে সব পরিষ্কার করে। Interval-টা পথের সবচেয়ে ছোট Idle Timeout-এর চেয়েও ছোট হতে হবে। Browser Ping-এর উত্তর আপনা-আপনি দেয়, কিন্তু JavaScript Ping Frame পাঠাতে পারে না; তাই যে Client মৃত Server ধরতে চায়, সে ছোট একটা Application Message ({"t":"ping"}) পাঠায়, আর প্রায় ১০ সেকেন্ডে উত্তর না এলে Reconnect করে।

Heartbeat: Idle Timeout আর নিঃশব্দে হারিয়ে যাওয়া অন্য পাশ

দীর্ঘস্থায়ী Socket বিদায় না জানিয়েই মারা যেতে পারে। Ping দিয়েই দুই পাশ সেটা টের পায়, আর পথের যন্ত্রগুলো চুপচাপ Connection কেটে দেয় না।

পরিস্থিতি

Phone
Load BalancerIdle Timeout ৬০ সে.
Gateway
শেষ Message ১০:০০:০০
ধাপ 1 / 4
1

একটা Support Chat: একটা Message, তারপর কিছুক্ষণ কেউ কিছু লেখে না। কোনো Heartbeat চালু নেই।

#একটা WebSocket Authenticate করা

Browser-এর WebSocket API Authorization-এর মতো নিজের Header বসাতে পারে না। চালু উপায়গুলো:

উপায়কীভাবেসাবধান
Session CookieUpgrade Request-এর সাথে Browser নিজেই Cookie পাঠায়WebSocket CORS-এর আওতায় পড়ে না: Origin যাচাই করো একটা অনুমোদিত তালিকার সাথে, নইলে যেকোনো Site তোমার User হিসেবে Socket খুলতে পারে (Cross-Site WebSocket Hijacking, CSWSH)। দ্বিতীয় স্তর হিসেবে SameSite Cookie রাখো
প্রথম Message-এ TokenSocket খোলো, তারপর পাঠাও {"type":"auth","token":"…"}Authenticate হওয়ার আগেই Socket তৈরি: আর কিছু নিও না, আর কয়েক সেকেন্ডে ঠিক Token না এলে বন্ধ করো (1008)
স্বল্পমেয়াদি Ticketসাধারণ Authenticated Request দিয়ে একবারের একটা Ticket আনো, তারপর Connect করো wss://…/chat?ticket=…-এTicket এলোমেলো, কয়েক সেকেন্ডের মেয়াদি আর একবারের হতে হবে। দীর্ঘমেয়াদি Token কখনো URL-এ রেখো না: URL লেখা হয়ে যায় Load Balancer, Proxy আর Server-এর Access Log-এ
Sec-WebSocket-Protocol-এ Tokenভুয়া একটা Subprotocol সেজে Token পাচার (Kubernetes এটা করে)একটা কৌশলমাত্র: কিছু Proxy এই Header Log করে, আর Server-কে একটা Subprotocol ফেরত বলতে হয়

Browser ছাড়া অন্য Client (Mobile App, Service) Upgrade-এর সাথে সরাসরি Authorization Header পাঠাতে পারে। যে উপায়েই করো, মনে রাখো: যে Token দিয়ে Socket খুলেছে, Socket তার চেয়ে বেশিদিন বাঁচতে পারে। মেয়াদ যাচাই করো, আর মেয়াদ ফুরোলে একটা Application Code (যেমন 4001) দিয়ে বন্ধ করো, বা Socket দিয়েই নতুন Token নাও। শুধু Connection না, প্রতিটা Message-এর Authorization যাচাই করো (এই User কি এই Room-এ লিখতে পারে?)।

#HTTP/2 আর HTTP/3-এর ওপর WebSocket

পুরনো Upgrade-এর জন্য লাগে HTTP/1.1 আর নিজস্ব একটা TCP Connection। RFC 8441 (২০১৮) সংজ্ঞা দেয় HTTP/2-এর ওপর WebSocket: Client নতুন একটা Stream-এ :protocol: websocket সহ একটা বাড়তি CONNECT Request পাঠায়, Server ১০১-এর বদলে 200 দেয়, আর WebSocket তখন চলে আগে থেকে খোলা HTTP/2 Connection-এর একটা Stream হিসেবে; ফলে একই Server-এর অনেক Socket একটাই TCP আর TLS Connection ভাগ করে নেয়। RFC 9220 (২০২২) HTTP/3-এর জন্য একই কাজ করে। Chrome আর Firefox RFC 8441 সমর্থন করে, কিন্তু অনেক Proxy আর Server এখনো শুধু HTTP/1.1 Upgrade-ই নেয়; তাই ২০২৬-এও বাস্তবে বেশিরভাগ WebSocket Traffic নিজের TCP Connection-এই চলে। Frame-এর গড়ন দুই ক্ষেত্রেই এক।

#WebSocket Scale করা

Request-Response API Scale করা সহজ, কারণ উত্তর দেওয়ার পর Server Request-টা ভুলে যায়। WebSocket Server ধরে রাখে দীর্ঘস্থায়ী, Stateful Connection: কোন User কোন Socket-এ, কে কোন Room-এ যোগ দিয়েছে। এতে Design-এর প্রায় সব অংশ বদলে যায়।

#একটা Server কত Connection রাখতে পারে?

প্রতিটা Connection-এর খরচ:

  • একটা File Descriptor। Linux Process প্রায়ই শুরু হয় ১,০২৪টা খোলা File-এর Soft Limit নিয়ে (ulimit -n)। এটা দশ লাখ বা তার বেশিতে তোলো (systemd-এ LimitNOFILE, limits.conf-এ nofile, আর পুরো System-এর জন্য fs.nr_open/fs.file-max)।
  • Memory। অলস একটা TCP Socket-এর জন্য Kernel-এর লাগে কয়েক Kilobyte (Data চললেই শুধু Buffer বাড়ে); TLS State, Read আর Write Buffer, আর তোমার Runtime-এর প্রতি-Connection Object মিলে সাধারণত আরও ১০–৫০ KB। হিসাব রক্ষণশীল রাখো: ১,০০,০০০ Socket × ৫০ KB = ৫ GB।
  • CPU, মূলত কিনারায়। অলস Socket প্রায় কিছুই খায় না। CPU খায় TLS Handshake, Heartbeat, JSON Encode, আর সবচেয়ে বেশি তুমি যে Message পাঠাও সেগুলো।

বিশাল সংখ্যা সম্ভব হয় Event Loop-এর জন্য: প্রতি Connection-এ একটা আটকে থাকা Thread না রেখে, একটা Thread Kernel-কে জিজ্ঞেস করে (Linux-এ epoll, BSD-তে kqueue দিয়ে) “আমার ১,০০,০০০ Socket-এর কোনগুলোতে Data এসেছে?”, আর শুধু সেগুলোই সামলায়। Nginx, Node.js, Netty, Go-র Runtime আর Erlang সবাই এভাবে চলে (I/O Model আছে Chapter ৩-এ)। রেকর্ডগুলো চমকে দেওয়ার মতো: ২০১২ সালেই WhatsApp Erlang আর FreeBSD-তে প্রতি Server-এ প্রায় ২০ লাখ Connection চালাত, আর Phoenix Framework এক মেশিনে ২০ লাখ দেখিয়েছে। Production-এ ইচ্ছে করেই অনেক কম রাখা হয়, সাধারণত Node-প্রতি ১০,০০০–১,০০,০০০, কারণ একটা Node মরলে তার সব Socket সাথে যায় (Blast Radius), আর সেই সব Client একসাথে Reconnect করে।

#Load Balancer আর দীর্ঘস্থায়ী Connection

  • L4 (NLB, বা TCP Mode-এ HAProxy) TCP Connection না পড়েই এগিয়ে দেয়। সস্তা, বিপুল Connection সামলায়, কিন্তু Path দেখে Route করতে বা Upgrade যাচাই করতে পারে না।
  • L7 (ALB, Nginx, Envoy, HTTP Mode-এ HAProxy) Upgrade Request দেখতে পায়, তাই TLS শেষ করতে, /ws-কে Socket Server-এ পাঠাতে আর খারাপ Request আগেভাগে ফেরাতে পারে। ১০১-এর পরে সে শুধু দুই দিকে Byte কপি করে। প্রতি Client-এর জন্য সে দুটো Connection রাখে (একটা Client-এর সাথে, একটা Backend-এর সাথে), তাই তার নিজের File Descriptor আর Memory-ও হিসাবে ধরতে হয়।
  • Proxy-র Port-এর সীমা। Proxy থেকে Backend-এর প্রতিটা Connection একটা Ephemeral Port নেয়: Linux-এর ডিফল্ট পরিসর ৩২৭৬৮–৬০৯৯৯, মানে একটা Proxy IP থেকে একটা Backend IP আর Port-এ প্রায় ২৮,০০০ Connection। Socket অনেক হলে Backend-কে কয়েকটা IP বা Port দাও, অথবা Proxy-কে কয়েকটা Source IP।
  • প্রতিটা ধাপের Idle Timeout তোমার Heartbeat Interval-এর চেয়ে লম্বা হতে হবে (ওপরে দেখো)।
  • Sticky Session? একটা WebSocket সারা জীবন একটাই Backend-এ বাঁধা থাকে, তাই Socket-এর নিজের জন্য Stickiness লাগে না। Stickiness দরকার শুধু আলাদা আলাদা HTTP Request দিয়ে বানানো বিকল্প পথে (Socket.IO-র Long Polling), বা Reconnect-কে একই Server-এ ফিরতেই হলে; ভালো Design এটা এড়ায়।
  • অসমতা। Round Robin নতুন Connection ভাগ করে, পুরনোগুলো না। Scale-out-এর পর নতুন Node ফাঁকা বসে থাকে আর পুরনোগুলো ভরা থাকে, কারণ কেউ Reconnect করে না। Least-connections ব্যবহার করো, আর সবচেয়ে ব্যস্ত Node-গুলোর অল্প কিছু Socket (Code 1012 দিয়ে) ধীরে ধীরে বন্ধ করে ভারসাম্য ফেরাও, যাতে তারা অন্যখানে Reconnect করে।

#Stateless Gateway আর একটা Pub/Sub Backplane

Server একটা হলে User B-কে Message পৌঁছানো মানে নিজের একটা Map-এ খোঁজা। দুটো হলে B হয়তো অন্য Server-এ Connect, আর প্রথমটার সেই Socket-এ পৌঁছানোর কোনো উপায় নেই (Lesson ১.৩ সমস্যাটা দেখিয়েছিল)। চালু Architecture:

  1. Gateway (Connection Server) WebSocket ধরে রাখে, Authenticate করে, Heartbeat চালায় আর Frame ও ভেতরের Message-এর মধ্যে অনুবাদ করে। এরা Socket রাখে কিন্তু কোনো Business State রাখে না: যেকোনো User যেকোনো Gateway-তে Connect করতে পারে, আর একটা Gateway হারালে কোনো Data হারায় না।
  2. পেছনের Service (Chat, Notification, Game Logic) আসল কাজ করে, আর দরকারি সবকিছু পৌঁছানোর আগে Store করে।
  3. দুইয়ের মাঝে একটা Pub/Sub Backplane। প্রতিটা Gateway তার প্রতিটা Connect থাকা User-এর জন্য একটা Channel-এ (user:B) আর তার User-দের যোগ দেওয়া প্রতিটা Room-এর জন্য একটা Channel-এ (room:r7) Subscribe করে। পৌঁছাতে চাইলে Service একবার Channel-এ Publish করে; শুধু সেই Channel-এ Subscribe করা Gateway-রাই একটা কপি পায় আর নিজের Socket-এ লিখে দেয়।
  4. চাইলে একটা Connection Registry (User → Gateway, Redis-এ, Heartbeat-এ নবায়ন হওয়া TTL সহ), সরাসরি Routing আর Presence-এর জন্য।

একটা Group-এর জন্য Backplane পাঠায় সদস্য আছে এমন প্রতিটা Gateway-তে একটা কপি, সদস্যপ্রতি একটা না; প্রতিটা Gateway নিজের ভেতরে ছড়িয়ে দেয়। ১০০টা Gateway-তে ছড়ানো ১০,০০০ সদস্যের Room-এ প্রতি Message-এ Backplane-এর খরচ ১০০টা Delivery।

Scale Out: Stateless Gateway আর একটা Pub/Sub Backplane

প্রতিটা User-এর Socket থাকে কোনো একটা Gateway-তে। A-র Message-কে B-কে খুঁজে নিতে হয়, B যেখানেই Connect থাকুক।

পরিস্থিতি

User A
User B
Gateway 1
Chat Service + Store
“hi B”
ধাপ 1 / 3
1

Server একটা হলে A আর B দুজনেই Gateway 1-এ Connect। সব Socket এর Memory-তে, তাই পৌঁছে দেওয়া মানে নিজের একটা Map-এ খোঁজা।

Backplaneপৌঁছানোশক্তিদুর্বলতা
Redis Pub/Subবড়জোর একবার, Memory-তে, কোনো ইতিহাস নেইখুব দ্রুত, সহজ, সবার কাছেই Redis আছেযে Subscriber বন্ধ, সে Message হারায়। Redis Cluster-এ পুরনো PUBLISH যায় প্রতিটা Node-এ; বড় মাপে Sharded Pub/Sub (SPUBLISH, Redis 7+) ব্যবহার করো
Redis Streamsস্থায়ী Log, Consumer Groupএকটা ID থেকে Replay, তবু সহজMemory-তে আটকানো; ছাঁটাই তোমাকেই সামলাতে হয়
NATSবড়জোর একবার (মূল অংশ), Persistence স্তর দিলে অন্তত একবারখুব হালকা, Subject Wildcard (room.*), লাখো SubjectPersistence একটা বাড়তি অংশ
Kafkaটেকসই, Replay করা যায়, Partition-এ ক্রমমতোবিশাল Throughput, Event-এর আসল খাতালাখো ছোট ছোট User-ভিত্তিক Topic-এর জন্য বানানো না; Conversation ধরে Partition করো আর Gateway-কে বাছাই করতে দাও; Latency বেশি

একটা চালু ভাগাভাগি: Kafka (বা Database) টেকসই আসল উৎস, আর হালকা একটা Pub/Sub (Redis বা NATS) Gateway-গুলোতে দ্রুত, হারাতে-পারে এমন Fan-out করে; Resume-এর সময় ফাঁক ভরে Storage থেকে।

#Presence

“কে Online” দেখতে সহজ, কিন্তু দামি। চালু একটা Design: প্রতিটা Heartbeat-এ Gateway presence:user123 = gw-7-কে ৬০ সেকেন্ডের TTL দিয়ে নবায়ন করে; Key মুছে গেলে User Offline। দুটো সমস্যা:

  • ঝিকমিক করা (Flapping)। Mobile Connection অনবরত কাটে আর ফেরে। “Offline” ঘোষণার আগে কয়েক সেকেন্ড অপেক্ষা করো (Grace Period), নইলে প্রতিটা সুড়ঙ্গ একটা Offline–Online ঝড় হয়ে দাঁড়ায়।
  • Fan-out। ১০ লাখ User-এর প্রত্যেকের ২০০ জন Contact, আর মিনিটে ১,০০,০০০ অবস্থা বদলালে প্রতিটা বদল প্রতিটা Contact-কে পাঠানো মানে মিনিটে ২ কোটি Event। বাস্তব System Presence পাঠায় শুধু যারা দেখছে তাদের (খোলা Chat, চোখের সামনে থাকা Contact List), বাকিটা চাইলে আনে; খুব বড় Group-এ শুধু একটা সংখ্যা দেখায়।

#পৌঁছানোর নিশ্চয়তা আর ক্রম

TCP ক্রম আর পৌঁছানোর নিশ্চয়তা দেয় শুধু Connection যতক্ষণ বেঁচে আছে। ভাঙলে পথে থাকা বা Kernel Buffer-এ থাকা Byte হারায়, আর কোনগুলো পৌঁছেছে কোনো পাশই জানে না। একটা Socket-এর ওপর পৌঁছানো বড়জোর একবার (At Most Once)। Chat-এর মতো নির্ভরযোগ্যতা ওপরে আবার বানাতে হয়:

  1. আগে Store, পরে পৌঁছানো। Message প্রথমে টেকসই Storage-এ লেখো, প্রতিটা Conversation-এ একজন লেখকের (Database, বা যে Partition-এর হাতে Conversation) দেওয়া একটা Sequence Number সহ।
  2. Acknowledge। Client নিজের কাছে Save করা সবচেয়ে বড় Sequence Number-এর Ack দেয় (TCP-র মতো জমিয়ে)।
  3. Resume। আবার Connect করার সময়, যেকোনো Gateway-তে, Client বলে “৪২ পর্যন্ত সব আমার কাছে আছে”, আর Server Storage থেকে ৪৩ থেকে আবার পাঠায়।
  4. Duplicate বাদ দাও। আবার পাঠানোর কারণে পৌঁছানো হয় অন্তত একবার (At Least Once), তাই Client আগে থেকে থাকা Sequence Number বাদ দেয়। নিজের পাঠানো Message-এ Client একটা অনন্য ID জোড়ে, আর Ack না আসা পর্যন্ত একই ID দিয়ে আবার পাঠায়; আগে দেখা ID পেলে Server আগের ফলটাই ফেরত দেয় (একটা Idempotency Key, Chapter ২৬)।
  5. ক্রম ঠিক হয় Sequence Number দিয়ে, পৌঁছানোর সময় দিয়ে না। আলাদা Gateway আর Retry-র কারণে Message একে অন্যকে টপকে যেতে পারে। Client আগে-পরে আসা Message ধরে রাখে, ফাঁক খোঁজে (৪৬, ৪৮: ৪৭ কোথায়?) আর যা নেই সেটা আনিয়ে নেয়।

অন্তত একবার পৌঁছানো আর Idempotent প্রক্রিয়া মিলে User পায় ঠিক একবার-এর অনুভূতি। ক্রমের নিশ্চয়তা থাকে প্রতিটা Conversation-এ; আলাদা Conversation-এর মধ্যে সার্বিক ক্রমের নিশ্চয়তা কেউ দেয় না, আর দরকারও নেই।

Socket-এ পৌঁছানোর নিশ্চয়তা: Ack, Sequence Number, Resume

TCP ক্রমমতো পৌঁছায় শুধু Connection যতক্ষণ বেঁচে আছে। Chat App ওপরে আবার নির্ভরযোগ্যতা বানায়।

পরিস্থিতি

Rina-র Phone
Gateway
Chat Service + Store
“৭টায় দেখা হবে”
ধাপ 1 / 3
1

সরল একটা Design: Service Message-টা Gateway-তে ঠেলে দেয়, পৌঁছানোর কোনো হিসাব রাখে না।

#Reconnect-এর ঝড়, Jitter সহ Backoff, আর খালি করা

৫০,০০০ Socket-ওয়ালা একটা Gateway মরলে ৫০,০০০ Client একই সেকেন্ডে টের পায়। সবাই সাথে সাথে Reconnect করলে বাকি Gateway-গুলোকে এক সেকেন্ডে সামলাতে হয় ৫০,০০০ TLS Handshake, Auth Service-কে ৫০,০০০ Token যাচাই, আর Store-কে ৫০,০০০ Resume Query। অনেকগুলো ব্যর্থ হয়, আর প্রতিটা Client নির্দিষ্ট একই সময় পরে আবার চেষ্টা করলে পরের ঢেউও সমান বড়: একটা Thundering Herd, যা বহরের সুস্থ অংশটাকেও ফেলে দিতে পারে।

  • Full Jitter সহ Exponential Backoff। অপেক্ষা করো random(0, min(cap, base × 2^attempt)), যেমন Base ১ সেকেন্ড আর Cap ৩০ সেকেন্ড, আর একদম প্রথম চেষ্টাটাও এলোমেলো করো। ৩০ সেকেন্ডে ছড়ানো ৫০,০০০ Reconnect মানে সেকেন্ডে প্রায় ১,৭০০।
  • Server-এর দিক থেকে প্রবেশ নিয়ন্ত্রণ। প্রতি Gateway-তে সেকেন্ডে নতুন Handshake-এর একটা সীমা রাখো; তার ওপরে সস্তায় উত্তর দাও: Upgrade-এর আগে Retry-After সহ HTTP 503, বা খোলা Socket-এ Close Code 1013। Client ইঙ্গিতটাকে ন্যূনতম ধরে, তার ওপর Jitter যোগ করে (Retry আর Backoff বিস্তারিত: Chapter ২৮)।
  • Reconnect সস্তা করো। Resume Token, Cache করা Auth সিদ্ধান্ত, আর এমন Resume Query যা শুধু ফাঁকটুকু পড়ে।

Deploy হলো পরিকল্পিত Reconnect-ঝড়, তাই আস্তে করো। Gateway-কে Draining হিসেবে চিহ্নিত করো (Load Balancer নতুন Connection পাঠানো বন্ধ করে; ALB-এর Deregistration Delay ডিফল্ট ৩০০ সেকেন্ড), কয়েক মিনিট ধরে ধাপে ধাপে Code 1012 দিয়ে এর Socket বন্ধ করো, আর খালি হলে তবেই Process থামাও। Kubernetes-এ ডিফল্ট ৩০ সেকেন্ডের Termination Grace Period এর জন্য খুবই ছোট: Drain-এর পুরো সময় ধরে এটা বাড়াও, preStop Hook-এ Drain করো, আর একবারে একটা Gateway Roll করো।

Reconnect-এর ঝড়, Jitter দেওয়া Backoff আর Gateway খালি করা

একটা Gateway-তে ৫০,০০০ Socket। সেটা চলে গেলে কী হবে, তা নির্ভর করে Client-রা কীভাবে ফেরে তার ওপর।

পরিস্থিতি

৫০,০০০ Client
Load Balancer
বাকি Gateway
Auth + Store
Gateway 7 Crash: ৫০,০০০ Socket বন্ধ (1006)
ধাপ 1 / 5
1

একটা Gateway Crash করল। এর সব Client একই সেকেন্ডে অস্বাভাবিক Close পায়।

#Fan-out-এর হিসাব: একসাথে ১০ লাখ User

একটা Chat Service-এর হাতে-কলমে হিসাব:

  1. Connection। একসাথে ১০,০০,০০০ User, Gateway-প্রতি ১০,০০০ Socket-এর নীতি = ১০০টা Gateway; বিকল আর Deploy-এর জন্য ৩০% বাড়তি রাখলে প্রায় ১৩০টা। (প্রতিটা আরও বেশি রাখতে পারত; ১০,০০০ রাখলে একটা বিকলের Blast Radius আর Reconnect-ঝড় ছোট থাকে।)
  2. Heartbeat। প্রতি ৩০ সেকেন্ডে একটা Ping = ১০,০০,০০০ / ৩০ ≈ পুরো বহরে সেকেন্ডে ৩৩,০০০ Ping, Gateway-প্রতি প্রায় ৩৩০: নগণ্য।
  3. Message। গড়ে একজন User প্রতি ১০ সেকেন্ডে একটা Message পেলে সেকেন্ডে ১,০০,০০০ Delivery, Gateway-প্রতি ১,০০০। প্রতিটা ৩০০ Byte হলে মোট ৩০ MB/s, মানে প্রায় ২৪০ Mbps বাইরে যাওয়া Traffic। ৫ গুণ চূড়ায় (বড় একটা খেলা শেষ হলো) সেকেন্ডে ৫,০০,০০০।
  4. Group। ১,০০,০০০ সদস্যের একটা Live Event Room-এ সেকেন্ডে ২০টা Message মানে শুধু ওই Room থেকেই সেকেন্ডে ২০ লাখ Delivery। Backplane বয় শুধু ২০ × ১০০ Gateway = সেকেন্ডে ২,০০০; গুণটা Gateway-গুলো নিজের ভেতরে করে। এই মাপে Sample বা Batch-ও করা হয় (“আর ৩০০টা Message” দেখাও), কারণ কোনো মানুষ সেকেন্ডে ২০টা Message পড়ে না।
  5. Reconnect। একটা Gateway হারানো = ১০,০০০ Reconnect, ৩০ সেকেন্ডের Jitter-এ সেকেন্ডে প্রায় ৩৩০। পুরো একটা Zone (বহরের এক-তৃতীয়াংশ) হারালে ৩,৩০,০০০; এজন্যই প্রায়ই স্থির সময়ের চাপ না, Handshake সামলানোর ক্ষমতাই বহরের মাপ ঠিক করে।

#Managed আর Mobile বিকল্প

  • Managed WebSocket। Amazon API Gateway-এর WebSocket API তোমার হয়ে Connection ধরে রাখে আর প্রতিটা Message-এ তোমার Backend (যেমন Lambda) ডাকে; উত্তর পাঠাও একটা Callback URL দিয়ে (@connections)। জানা দরকার এমন সীমা: একটা Connection সর্বোচ্চ ২ ঘণ্টা বাঁচে, আর ১০ মিনিট অলস থাকলে বন্ধ হয়। Pusher আর Ably-র মতো Hosted Service পুরো Fan-out স্তরটাই Product হিসেবে দেয়।
  • Background-এ থাকা Mobile App Socket খোলা রাখতে পারে না: Operating System তাদের থামিয়ে দেয়। Chat App সামনে থাকলে Socket রাখে, নইলে User-কে জাগাতে Push Notification (APNs, FCM) ব্যবহার করে।

নিজেকে যাচাই করোপ্রশ্ন

একটা Chat Service-এর ৩টা Gateway একটা Round-robin Load Balancer-এর পেছনে। User-রা বলছে, Message মাঝে মাঝে শুধু Page Reload করলে দেখা যায়। সবচেয়ে সম্ভাব্য কারণ কী?

#WebRTC: Peer-এর মধ্যে Real-time Media

WebRTC (Web Real-Time Communication) হলো কিছু Browser API (২০২১ থেকে একটা W3C Standard) আর IETF Protocol-এর একটা সেট, যা দিয়ে দুটো Browser বা App-এর মধ্যে সরাসরি Audio, Video আর যেকোনো Data পাঠানো যায়, Peer থেকে Peer-এ। কোনো Plug-in ছাড়াই Browser Tab-এ Google Meet Call এটা দিয়েই চলে।

“সরাসরি” কথাটায় একটু শোধরানো দরকার: WebRTC Media Peer থেকে Peer-এ পাঠায়, কিন্তু Server ছাড়া একটা Call শুরুই করতে পারে না। Peer-দের পরিচয় করাতে লাগে Signaling Server, Public Address জানতে STUN, আর প্রায় সবসময় শেষ ভরসা হিসেবে Relay করতে TURN; আর Group Call-এর Media-ও Server দিয়েই যায়।

               +-------------------+
               |  Signaling server |   (শুধু Setup: SDP আর ICE Candidate)
               +-------------------+
                  /             \
       1. SDP / ICE             2. SDP / ICE
                /                 \
               v                   v
        +----------+           +----------+
        |  Peer A  |<=========>|  Peer B  |
        +----------+           +----------+
          3. সরাসরি Encrypted Media (UDP-র ওপর SRTP), না পারলে TURN দিয়ে

#Signaling: SDP Offer আর Answer

WebRTC ইচ্ছে করেই Signaling-এর Standard বানায়নি: দুই Peer কীভাবে Setup-এর বার্তা বিনিময় করবে, সেটা Application-এর ব্যাপার। বেশিরভাগ App নিজের WebSocket ব্যবহার করে (এজন্যই এই দুই প্রযুক্তিকে এত বেশি একসাথে দেখা যায়); HTTP-ও চলে। Live Stream ঢোকানোর জন্য এখন একটা Standard আছে, WHIP (RFC 9725, ২০২৫): Media Server-এ Offer-এর একটা HTTP POST।

যা বিনিময় হয় তা SDP (Session Description Protocol), Session-এর একটা লেখা বর্ণনা:

  1. যে Call করছে সে একটা Offer বানায়: তার Media Track, সে যেসব Codec পারে (Audio-তে Opus; Video-তে VP8, H.264, আর প্রায়ই VP9 বা AV1), ICE Username আর Password, আর নিজের DTLS Certificate-এর Fingerprint।
  2. যাকে Call করা হলো সে এটা বসিয়ে একটা Answer বানায়, দুজনেই পারে এমন Codec বেছে, নিজের ICE Credential আর Fingerprint যোগ করে।
  3. তারপর প্রতিটা পাশ ICE Candidate (সম্ভাব্য Address) খুঁজে পাওয়ামাত্র পাঠায় (Trickle ICE), আবারও Signaling দিয়ে।

Call চলার পুরো সময় Signaling Channel খোলা রাখতে হয়, কারণ Network বদলালে নতুন Offer আর Answer লাগে (একটা ICE Restart)।

#ICE, STUN আর TURN

বেশিরভাগ Device NAT-এর পেছনে থাকে, তাদের কোনো Public Address নেই (Lesson ১.৭)। ICE (Interactive Connectivity Establishment) এমন একটা পথ খোঁজে যেটা কাজ করে:

Candidate-এর ধরনকোথা থেকে আসেউদাহরণ
HostDevice-এর নিজের Interface Address (গোপনীয়তার জন্য Browser প্রায়ই এটা একটা এলোমেলো .local নামের পেছনে লুকায়)192.168.1.20:50000
Server-reflexiveSTUN Server জানায় NAT তোমাকে কোন Public Address আর Port দিয়েছে203.0.113.7:51000
RelayTURN Server নিজের ওপর একটা Address দেয় আর সবকিছু এগিয়ে দেয়192.0.2.50:49152

দুই Peer একই সময়ে Candidate জোড়াগুলোতে Connectivity Check (ছোট STUN Packet) পাঠায়; প্রতিটা বাইরে যাওয়া Check পাঠানো পাশের নিজের NAT-এ একটা Mapping খোলে, তাই অন্য পাশের Check ঢুকতে পারে (Hole Punching)। ICE কাজ করা সবচেয়ে ভালো জোড়াটা বাছে: আগে Host, তারপর Server-reflexive, তারপর Relay। Call চলার সময় কয়েক সেকেন্ড পরপর Consent Check নিশ্চিত করে পথটা এখনো চলছে; বন্ধ হলে Connection যায় “disconnected”, তারপর “failed” অবস্থায়, আর Application-কে একটা ICE Restart চালাতে হয়।

STUN সস্তা: প্রতি খোঁজে দুটো ছোট্ট Packet, আর Public STUN Server-ও আছে। TURN দামি, কারণ পুরো Media Stream-টাই Relay করে:

  • Symmetric NAT (প্রতিটা Destination-এ নতুন Port) থাকলে Hole Punching ব্যর্থ হয়; এটা Corporate Network আর কিছু Mobile Carrier-এ সাধারণ; UDP পুরো বন্ধ থাকলেও তাই। Operator-রা প্রায়ই বলে মোটামুটি ১০–২০% Session-এ TURN লাগে, Corporate Network-এ অনেক বেশি; অনুপাতটা খুব নির্ভর করে তোমার User কারা তার ওপর।
  • দুই দিকে ১.৫ Mbps-এর Relay করা এক-এক Call Relay-তে প্রায় ৩ Mbps ঢোকায় আর ৩ Mbps বের করে, মোটামুটি Call-ঘণ্টা প্রতি ১.৩৫ GB Egress। Cloud-এর Egress দামে এটা বিলে একটা আসল লাইন।
  • TURN চালাও UDP ৩৪৭৮-এ, আর শুধু Web Traffic ছাড়ে এমন Network-এর জন্য Port ৪৪৩-এ TLS-এর ওপরেও (turns:)। TCP-র ভেতরে Media Head-of-line Blocking-এ ভোগে, তাই এটা শেষ ভরসা।
  • TURN-এর জন্য তোমার App-এর দেওয়া স্বল্পমেয়াদি Credential লাগে (সাধারণত একটা মেয়াদ-সময়ের HMAC), নইলে এটা অচেনা লোকদের জন্য একটা বিনা পয়সার খোলা Relay হয়ে যায়।

একটা WebRTC Call: Signaling, ICE, STUN আর TURN-এর বিকল্প পথ

Peer-to-peer Media-কেও শুরু হতে Server লাগে। দুই Peer-ই NAT-এর পেছনে।

পরিস্থিতি

Peer ANAT-এর পেছনে
SignalingWebSocket
STUN
TURN Relay
Peer BNAT-এর পেছনে
createOffer(): Codec, ICE Credential, DTLS Fingerprint
ধাপ 1 / 12
1

A Camera আর Microphone নেয়, তারপর একটা Offer বানায়: SDP-র একটা লেখা, তাতে Media Track, Codec (Opus, VP8, H.264…), ICE Username আর Password, আর নিজের DTLS Certificate-এর Fingerprint।

#Encryption: DTLS-SRTP

WebRTC-তে Encryption বাধ্যতামূলক; Encrypt ছাড়া কোনো Mode নেই। ICE পথ খুঁজে পেলে Peer-রা সেই পথে একটা DTLS Handshake চালায় (UDP-র জন্য বানানো TLS), প্রত্যেকে নিজের বানানো (Self-signed) Certificate দিয়ে। প্রতিটা পাশ মিলিয়ে দেখে অন্য পাশের Certificate SDP-তে পাওয়া Fingerprint-এর সাথে মেলে কি না, তারপর দুজনেই SRTP (Secure RTP)-র Key বের করে, যেটা প্রতিটা Audio আর Video Packet Encrypt আর যাচাই করে। এটাই DTLS-SRTP (RFC 5764)।

Design-এ এর দুটো ফল: Call ততটাই বিশ্বস্ত, যতটা বিশ্বস্ত Fingerprint বয়ে আনা Signaling Channel (তাই Signaling TLS-এর ওপর আর Authenticated হতে হবে); আর TURN Relay শুধু Encrypted Packet দেখে। তবে একটা SFU DTLS-SRTP শেষ করে, তাই Media দেখতে পায়; SFU পেরিয়ে সত্যিকারের End-to-end Encryption চাইলে SFrame (RFC 9605)-এর মতো আরেকটা স্তর লাগে, যেটা SFU-তে পৌঁছানোর আগেই Media Payload Encrypt করে।

#Data Channel

Media ছাড়াও WebRTC Peer-দের মধ্যে যেকোনো Message-এর জন্য Data Channel খুলতে পারে। এগুলো চলে UDP-র ওপর DTLS-এর ওপর SCTP দিয়ে (RFC 8831): নিরাপত্তা দেয় DTLS, Message আর Stream দেয় SCTP। প্রতিটা Channel সাজানো যায়:

  • নির্ভরযোগ্য আর ক্রমমতো (ডিফল্ট), TCP Stream-এর মতো কিন্তু Channel-প্রতি, তাই একটা ধীর Channel বাকিদের আটকায় না।
  • ক্রমহীন আর/বা অনির্ভরযোগ্য (ordered: false, maxRetransmits: 0 বা maxPacketLifeTime): Game State, Cursor Position বা Sensor-এর পাঠের জন্য আদর্শ, যেখানে নতুন মান এলে হারানো পুরনোটার আর কোনো দাম নেই।

ব্যবহার: Peer-to-peer File পাঠানো (Snapdrop-এর মতো কাছাকাছি Device-এ ভাগাভাগি; Snapdrop নিজে ২০২৩-এ মালিক বদলেছে, আর একটা Community Fork সেটা চালিয়ে যাচ্ছে), Multiplayer Game, Remote Control, আর Call-এর ভেতরে Chat। Message ছোট রাখো (কয়েক দশ Kilobyte), বড় File টুকরো করে পাঠাও।

#Group Call: Mesh, SFU আর MCU

এক-এক Call স্বভাবতই Peer-to-peer। Group Call তা না:

Mesh (খাঁটি P2P)SFU (Selective Forwarding Unit)MCU (Multipoint Control Unit)
কীভাবেসবাই সবার সাথে যুক্তসবাই একটা Stream Server-এ পাঠায়, Server Decode না করেই বাকিদের কাছে এগিয়ে দেয়Server সব Stream Decode করে, একটা ছবিতে মিশিয়ে আবার Encode করে
জনপ্রতি Uploadn − ১টা Stream১টা Stream (বা একটা Simulcast সেট)১টা Stream
জনপ্রতি Downloadn − ১টা Streamn − ১টা পর্যন্ত Stream১টা মেশানো Stream
Server-এর খরচনেই (TURN বাদে)Bandwidth, অল্প CPUপ্রতি Call-এ খুব বেশি CPU বা GPU
বাড়তি Latencyনেইখুবই সামান্যDecode + মেশানো + Encode
কোথায় ভালো২–৪ জনপ্রায় সব আধুনিক Video Product (Meet, Discord, Teams, বেশিরভাগ Platform)Phone Bridge, পুরনো Meeting Room System, একটা মিলিত ছবি Record করা

প্রত্যেকে ১.৫ Mbps-এ ৬ জন হলে Mesh-এ প্রত্যেককে ৭.৫ Mbps Upload করতে আর পাঁচবার পর্যন্ত Encode করতে হয়, অথচ SFU-তে জনপ্রতি Upload মাত্র ১.৫ Mbps (SFU নিজে নেয় ৯ Mbps আর পাঠায় ৪৫ Mbps)।

আলাদা Screen আর Network-এর Receiver-দের সামলাতে SFU দুটো কৌশল ব্যবহার করে:

  • Simulcast: প্রতিটা Sender একই Video দুই-তিনটা Resolution-এ Encode করে (ধরো ৭২০p, ৩৬০p, ১৮০p), আর SFU প্রতিটা Receiver-কে ঠিকটা পাঠায়: যে কথা বলছে তার জন্য পুরো আকার, Grid-এর জন্য ছোট ছবি, দুর্বল Mobile Link-এর জন্য কম মান।
  • SVC (Scalable Video Coding, VP9 আর AV1-এ আছে): স্তরে স্তরে সাজানো একটাই Stream; SFU আলাদা কপির মধ্যে বাছাই না করে Layer ফেলে দেয়।

বড় Meeting কয়েকটা Region জুড়ে একাধিক SFU শিকলের মতো জোড়ে, আর খুব বড় দর্শক (দশ হাজার Viewer) হলে Interactive WebRTC ছেড়ে Broadcast-ধাঁচের Delivery-তে যায় (CDN দিয়ে HLS বা DASH), যদি না এক সেকেন্ডের কম দেরি একেবারে জরুরি হয়।

Group Call: Mesh বনাম SFU বনাম MCU

একটা Video Call-এ ছয়জন। কে কয়টা Stream পাঠায়, আর খরচটা কে দেয়?

পরিস্থিতি

Rina↑ ৫টা Stream
Tanvir
Mitu
Arif
Nadia
Sami
ধাপ 1 / 3
1

Mesh: কোনো Media Server নেই। প্রত্যেকে বাকি প্রত্যেকের সাথে একটা করে Peer Connection খোলে। Rina তার Video পাঁচবার পাঠায়, প্রতি Peer-এ একবার, সাধারণত প্রতিটার জন্য আলাদা করে Encode করে।

বাস্তবের Product: Google Meet, Discord, Microsoft Teams আর বেশিরভাগ Video Platform WebRTC Media SFU দিয়ে চালায়। Zoom-এর Desktop আর Mobile App নিজের Media Protocol ব্যবহার করে, আর তার Browser Client WebRTC; যেভাবেই হোক, Zoom Call চলে তাদের Server দিয়ে, Peer-to-peer না।

#Real-time কৌশল বাছাই

পাঁচটা বিকল্প পাশাপাশি (Note থেকে শোধরানো, নিচে দেখো):

Short PollingLong PollingServer-Sent EventsWebSocketWebRTC
Data-র দিকServer → Client, Client-এর টেনে আনাServer → Client, Client-এর টেনে আনাServer → Client Pushদুই দিকে (Full Duplex)দুই দিকে, Peer থেকে Peer
ProtocolHTTP(S)HTTP(S)HTTP(S), text/event-streamTCP-র ওপর ws:// / wss:// (HTTP/2, HTTP/3-এও)Media: UDP-র ওপর SRTP; Data: UDP-র ওপর DTLS-এর ওপর SCTP
Connection-এর ধরনপ্রতি Interval-এ নতুন Requestধরে রাখা Request, প্রতিটা Response-এর পর আবারএকটা দীর্ঘস্থায়ী HTTP Responseএকটা স্থায়ী Connectionসরাসরি Peer Connection (বা TURN / SFU দিয়ে)
User পর্যন্ত দেরিInterval পর্যন্তমোটামুটি এক যাত্রামোটামুটি এক যাত্রামোটামুটি এক যাত্রাMedia-র জন্য সবচেয়ে কম (UDP, Relay ছাড়া)
Server-এর চাপখুব বেশি (বেশিরভাগ ফাঁকা Request)মাঝারি–বেশি (প্রতি Update-এ একটা Request)প্রতি Update-এ কম, Client-প্রতি একটা খোলা Connectionপ্রতি Message-এ কম, Client-প্রতি একটা খোলা Connection, Statefulএক-এক Call-এ শুধু Signaling; Group আর বন্ধ Network-এ SFU আর TURN-এর আসল খরচ
Binary Dataহ্যাঁহ্যাঁনা (UTF-8 Text; দরকারে base64)হ্যাঁ (Text আর Binary)হ্যাঁ (Media আর Data Channel)
Reconnectপরের Pollপরের RequestBrowser-এ তৈরি করা, Last-Event-ID দিয়ে Resumeনিজে লেখো (বা Library নাও)Application-কে ICE Restart চালাতে হয়
Proxy আর Firewall পার হওয়াসবসময়সাধারণত (Proxy Timeout খেয়াল রাখো)সাধারণত (Proxy Buffering খেয়াল রাখো)wss:// হলে সাধারণতকড়া Network-এ ৪৪৩-এ TURN লাগে

কীভাবে বাছবে:

                 কী যায়, আর কার থেকে কার কাছে?
                                 |
     +---------------------------+---------------------------+
     |                           |                           |
শুধু Server → Client      Client ⇄ Server, দুই দিকে        Peer ⇄ Peer Media
     |                           |                           |
  (SSE নাও)                (WebSocket নাও)              (WebRTC নাও)
  • Live Stock Ticker      • Chat Application            • Video Call (Meet)
  • News Feed Update       • Multiplayer Game            • Voice Chat (Discord)
  • খেলার Scorecard         • যৌথ Canvas                   • P2P File Sharing
  • AI Output Streaming      (Figma, Google Docs)
    (ChatGPT-এর মতো লেখা)  • Live Trading Screen
  1. Short Polling: Update কালেভদ্রে আসে, জরুরিও না, আর বাড়তি ঝামেলা চাও না। উদাহরণ: দিনে এক-দুবার বদলায় এমন Widget, একটা Status Check।
  2. Long Polling: যেখানে WebSocket বা SSE পার হয় না (পুরনো Browser, বৈরী Proxy), সেখানে বিকল্প পথ হিসেবে। উদাহরণ: পুরনো একটা Payment Gateway-এর Notification Check।
  3. Server-Sent Events: Data যায় শুধু Server থেকে Client-এ, আর Client কালেভদ্রে কিছু ফেরত পাঠায়। উদাহরণ: ChatGPT-এর মতো AI Response Streaming, শেয়ারবাজারের Live দাম, Live Scoreboard, System Notification।
  4. WebSocket: যখন দুই পাশই ঘনঘন আর তাৎক্ষণিক পাঠায়। উদাহরণ: Live Chat, Multiplayer Online Game, Live Trading Platform, Figma-র মতো যৌথ Editing।
  5. WebRTC: যখন দুই বা তার বেশি মানুষ সবচেয়ে কম দেরিতে Audio, Video বা কাঁচা Data বিনিময় করে। উদাহরণ: Google Meet, Discord-এর Voice আর Video, Browser থেকে Browser-এ File ভাগাভাগি।

Interview-এর জন্য মোটা দাগের নিয়ম: Latency-র চাহিদা মেটায় এমন সবচেয়ে সহজ জিনিস দিয়ে শুরু করো; “Real-time” Feature-এর অবাক করা বড় একটা অংশ SSE-তেই হয়ে যায়, আর সাধারণ HTTP-র নিয়মকানুন বজায় থাকে; Client সত্যিই ঘনঘন উল্টো দিকে কথা বললে WebSocket নাও; WebRTC শুধু Media বা সত্যিকারের Peer-to-peer Data-র জন্য। অনেক Product এগুলো মেশায়: একটা Chat App Message-এর জন্য WebSocket, Background-এ Push Notification, আর Call-এর জন্য WebRTC ব্যবহার করতে পারে, যেখানে WebSocket-টাই Call-এর Signaling Channel-এর কাজও করে।

একটা Live Update, পৌঁছানোর সাতটা উপায়

১০:০০:০৩-এ একটা Score বদলাল। প্রতিটা কৌশল কীভাবে এটা User-এর কাছে পৌঁছায় আর কত খরচে, তুলনা করো।

পরিস্থিতি

Client
Server
অন্য Peer
GET /score (প্রতি ৫ সে.)
ধাপ 1 / 4
1

Client ঘড়ি ধরে জিজ্ঞেস করে। ১০:০০:০০-এ নতুন কিছু নেই।

#নতুন আর পাশের বিকল্পগুলো

  • WebTransport হলো HTTP/3 (QUIC)-এর ওপর একটা Browser API: অনেক স্বাধীন Stream, যাদের মধ্যে Head-of-line Blocking নেই, সাথে অনির্ভরযোগ্য Datagram, Client থেকে Server-এ। একে ভাবো TCP-র সীমা ছাড়া WebSocket হিসেবে: Game, Live Media (IETF-এর Media over QUIC কাজ এর ওপরেই দাঁড়িয়ে) আর দ্রুত Telemetry-র জন্য। Chromium Browser আর Firefox এটা সমর্থন করে; Safari-র সমর্থন পিছিয়ে আছে, আর এর জন্য UDP খোলা থাকতে হয়, তাই একটা WebSocket বিকল্প রেখো।
  • MQTT হলো IoT দুনিয়ার Publish/Subscribe Protocol: Device একটা Broker-এর সাথে একটা TCP Connection রাখে (Port ১৮৮৩, TLS সহ ৮৮৮৩), Topic-এ Publish করে আর Wildcard দিয়ে Subscribe করে। এতে আছে তিনটা Delivery স্তর (QoS 0, 1, 2), রেখে দেওয়া Message (নতুন Subscriber শেষ মানটা পায়), আর একটা “Last Will”, যা Device হারিয়ে গেলে Publish হয়। এর স্থির Header ২ Byte পর্যন্ত ছোট হতে পারে, যা দুর্বল Link-এ Battery-চালিত Device-এর জন্য মানানসই। Browser Broker-এ পৌঁছায় WebSocket-এর ওপর MQTT দিয়ে। উদাহরণ: Open-source Eclipse Mosquitto Broker আর Managed AWS IoT Core।
  • gRPC Streaming (HTTP/2-এর ওপর Server, Client আর দ্বিমুখী Stream) হলো Service-এর মধ্যে Real-time Stream-এর চালু পছন্দ; Browser-এর লাগে gRPC-Web, যা শুধু Server Streaming পারে। এটা আছে Chapter ২৬-এ।
  • Webhook হলো সাধারণ HTTP Request দিয়ে Server থেকে Server-এ Push (Chapter ২৬), আর Mobile Push (APNs, FCM) পৌঁছায় চালু না থাকা App-এর কাছে।

#System Design-এ Real-time

WebSocket আছে এমন যেকোনো Design-এর জন্য একটা Checklist:

  • Connection সামলানো আর Business Logic আলাদা রাখো: Stateless Gateway, পেছনে Service, মাঝে একটা Pub/Sub Backplane।
  • পৌঁছানোর আগে Store করো, Conversation-প্রতি Message-এ নম্বর দাও, Ack নাও, Resume করো, Duplicate বাদ দাও।
  • প্রতি ২০–৩০ সেকেন্ডে Heartbeat; পথের প্রতিটা Idle Timeout এর চেয়ে লম্বা রাখো।
  • URL-এ দীর্ঘমেয়াদি Token না রেখে Upgrade Authenticate করো; Origin যাচাই করো; প্রতিটা Message-এ আর Token-এর মেয়াদ ফুরোলে আবার Authorization যাচাই করো।
  • প্রতি Connection-এ সবকিছুর সীমা রাখো: সর্বোচ্চ Message আকার, সেকেন্ডে কয়টা Message, Send Buffer-এর আকার। যে ধীর Consumer-এর Buffer বাড়তেই থাকে (Browser-এ bufferedAmount, Server-এ না পাঠানো Byte), তাকে বাদ দাও বা মান নামাও, নইলে সে Server-এর Memory খেয়ে ফেলবে।
  • Reconnect-ঝড়ের জন্য তৈরি থাকো: Client-এ Full Jitter Backoff, Gateway-তে প্রবেশ নিয়ন্ত্রণ, Deploy-এ ধীরে ধীরে খালি করা, আর একটা Zone হারানোর মাপে ক্ষমতা।
  • মাপো: Gateway-প্রতি খোলা Connection, সেকেন্ডে Connect আর Disconnect (Close Code অনুযায়ী), Store থেকে Socket পর্যন্ত Message-এর দেরি, Heartbeat ব্যর্থতা, Send Buffer-এর আকার।
লক্ষণসম্ভাব্য কারণকোথায় দেখবে
চুপ থাকার ঠিক ৬০ সেকেন্ড পর Socket বন্ধ, Code 1006Load Balancer বা Proxy-র Idle TimeoutHeartbeat Interval, ALB Idle Timeout, proxy_read_timeout
শুধু একটা Proxy-র পেছনে Upgrade ব্যর্থ, Local-এ চলেUpgrade/Connection সামনে যাচ্ছে নাProxy Config (proxy_http_version 1.1, Upgrade Header)
Message শুধু একই Server-এর User-রা পায়Gateway-গুলোর মধ্যে Backplane নেইPub/Sub Subscription, Registry
চলে যাওয়ার ঘণ্টার পর ঘণ্টা পরেও User Online দেখায়Half-open Socket, Heartbeat নেইPing/Pong, Presence TTL
একটা Node মরার পর Outage ছড়িয়ে পড়েএকসাথে ReconnectJitter, প্রবেশ নিয়ন্ত্রণ, Handshake-এর ক্ষমতা
Scale-out-এর পর নতুন Node অলসদীর্ঘস্থায়ী Connection কখনো ভারসাম্য ফেরায় নাLeast-connections, ধীরে ধীরে ভারসাম্য ফেরানো
কিছু Call জোড়ে, কিছু কখনো নাSymmetric NAT বা বন্ধ UDP, TURN নেইUDP আর TLS ৪৪৩-এ TURN, chrome://webrtc-internals-এ ICE Candidate-এর ধরন
৩ জনে Group Call ঠিক, ৮ জনে ভাঙাMesh TopologySFU, Simulcast

Interview-এর যেসব কোণ প্রস্তুত রাখবে: “WhatsApp Design করো” বা “একটা Notification System Design করো” (Gateway, Backplane, Store-and-forward, Delivery Receipt, Presence, Offline User-এর জন্য Push; পুরো Case Study আছে Chapter ৫২-এ), “১ কোটি Connection-এ Scale করবে কীভাবে?” (Connection-এর হিসাব, Blast Radius, ঝড়), “একটা LLM-এর উত্তর Stream করতে SSE না WebSocket?” (SSE: একদিকে, সাধারণ HTTP, আপনা-আপনি Resume), আর “Group Video Call কেন Peer-to-peer না?” (Upload Bandwidth, SFU)।

#নিজে করে দেখো

# ১. আসল একটা Handshake দেখো: হাতে Upgrade Request পাঠাও (তোমার নিজের যেকোনো WebSocket Endpoint)
curl -i -N --http1.1 \
  -H "Connection: Upgrade" -H "Upgrade: websocket" \
  -H "Sec-WebSocket-Version: 13" \
  -H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" \
  https://chat.example.com/ws
# আশা করো: HTTP/1.1 101 Switching Protocols ... Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

# ২. Accept-এর মান নিজে হিসাব করো
printf '%s' 'dGhlIHNhbXBsZSBub25jZQ==258EAFA5-E914-47DA-95CA-C5AB0DC85B11' | openssl sha1 -binary | base64

# ৩. একটা Socket-এর সাথে হাতে কথা বলো (Node.js লাগবে)
npx wscat -c wss://chat.example.com/ws

# ৪. একটা Linux Server-এর Connection ক্ষমতা
ulimit -n                         # Process-প্রতি খোলা File-এর সীমা (প্রায়ই 1024)
sysctl fs.file-max fs.nr_open     # পুরো System-এর সীমা
ss -s                             # Socket-এর মোট হিসাব
ss -tn state established '( sport = :443 )' | wc -l   # 443-এ চালু Connection

Heartbeat সহ ছোট একটা Server, Node.js-এর জনপ্রিয় ws Library দিয়ে:

import { WebSocketServer } from 'ws';

const wss = new WebSocketServer({ port: 8080, maxPayload: 64 * 1024 }); // 64 KB-র বেশি হলে 1009

wss.on('connection', (ws, req) => {
  if (req.headers.origin !== 'https://chat.example.com') return ws.close(1008, 'bad origin');
  ws.isAlive = true;
  ws.on('pong', () => { ws.isAlive = true; });
  ws.on('message', (data, isBinary) => { /* Authorise, Store, তারপর Backplane-এ Publish */ });
});

setInterval(() => {               // Heartbeat: প্রতি 30 সে. Ping, একটা মিস করলে Socket বাদ
  for (const ws of wss.clients) {
    if (!ws.isAlive) { ws.terminate(); continue; }
    ws.isAlive = false;
    ws.ping();
  }
}, 30_000);

Resume আর Full Jitter Backoff সহ একটা Browser Client:

let attempt = 0;
let lastSeq = Number(localStorage.getItem('lastSeq') ?? 0);

function connect(ticket) {
  const ws = new WebSocket(`wss://chat.example.com/ws?ticket=${ticket}`); // শুধু একবারের Ticket
  ws.onopen = () => { attempt = 0; ws.send(JSON.stringify({ type: 'resume', after: lastSeq })); };
  ws.onmessage = (e) => {
    const msg = JSON.parse(e.data);
    if (msg.seq <= lastSeq) return;            // Replay থেকে আসা Duplicate
    lastSeq = msg.seq;                          // (আসল Code ফাঁকও ধরে রাখে)
    ws.send(JSON.stringify({ type: 'ack', seq: lastSeq }));
  };
  ws.onclose = (e) => {
    if (e.code === 1000) return;               // ইচ্ছে করে বন্ধ
    const cap = Math.min(30_000, 1_000 * 2 ** attempt++);
    setTimeout(() => getTicket().then(connect), Math.random() * cap); // Full Jitter
  };
}

Lesson ১.১৩-এর Nginx Proxy Setting, Heartbeat-এর চেয়ে লম্বা Timeout সহ:

location /ws {
    proxy_pass http://gateways;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_read_timeout 75s;   # 30 সে.-র Ping Interval-এর চেয়ে বেশি হতেই হবে
}

Browser-এ Developer Tools খোলো → Network → Filter-এ WS দাও, একটা Socket-এ Click করে Messages Tab খোলো: Frame-গুলো দেখা যাবে (Ping বাদে)। WebRTC-র জন্য একটা Call শুরু করে chrome://webrtc-internals খোলো (Firefox-এ about:webrtc): Offer আর Answer-এর SDP, প্রতিটা ICE Candidate আর তার ধরন (host, srflx, relay), বাছাই করা জোড়া, আর Live Bitrate ও Packet Loss দেখতে পাবে।

#সারসংক্ষেপ

মূল শিক্ষা

  • WebSocket হলো একটা Full-duplex, স্থায়ী Connection, যা শুরু হয় Upgrade: websocket সহ একটা HTTP/1.1 GET দিয়ে আর 101 Switching Protocols-এর পর Frame হয়ে যায়। Sec-WebSocket-Accept = base64(SHA-1(Key + নির্দিষ্ট String)) প্রমাণ করে Server WebSocket বোঝে; এটা Security না। সবসময় wss://।
  • Frame-এর Header ২–১৪ Byte: FIN, RSV, Opcode (Text, Binary, Continuation, Close, Ping, Pong), Mask bit, ৭/১৬/৬৪ bit Length, আর প্রতিটা Client Frame-এ ৪ Byte-এর Masking Key, যেটা Proxy-কে Cache Poisoning থেকে বাঁচায়, তোমার Data-কে না। Message টুকরো হতে পারে; Control Frame ≤ ১২৫ Byte, আর মাঝে ঢুকতে পারে।
  • Heartbeat (প্রতি ২০–৩০ সেকেন্ডে Ping/Pong) Idle Timeout হারায় আর Half-open Connection ধরে। Close Code Client-কে বলে Reconnect করবে কি না (১০০০ না, ১০০১/১০১২ হ্যাঁ, ১০১৩ পরে, ১০০৬ = Connection কেটে গেছে)।
  • Authenticate করো Cookie আর Origin যাচাই দিয়ে (CSWSH ঠেকাতে), প্রথম Message-এ Token দিয়ে, বা একবারের Ticket দিয়ে; URL-এ দীর্ঘমেয়াদি Token কখনো না। Socket-এর পুরো জীবন ধরে Authorization আবার যাচাই করো।
  • Scale করো Stateless Gateway আর একটা Pub/Sub Backplane (Redis, NATS, Kafka) দিয়ে: Channel-প্রতি একবার Publish, Gateway-প্রতি একটা কপি, ভেতরে ছড়ানো। Memory আর File Descriptor ঠিক করে একটা Server-এ কত Socket; ছোট Blast Radius-এর জন্য Node-প্রতি ১০ হাজার–১ লাখ রাখো।
  • একটা Socket-এ পৌঁছানো বড়জোর একবার: আগে Store, Conversation-প্রতি Sequence Number, Ack, Resume, Idempotent পাঠানোর জন্য Client ID; ক্রম ঠিক হয় Sequence দিয়ে, পৌঁছানোর সময় দিয়ে না।
  • Full Jitter Backoff আর প্রবেশ নিয়ন্ত্রণ দিয়ে Reconnect-ঝড় এড়াও; Deploy-এ Gateway ধীরে ধীরে খালি করো (ধাপে ধাপে 1012 দিয়ে বন্ধ, লম্বা Grace Period)।
  • WebRTC Media Peer থেকে Peer-এ পাঠায়, কিন্তু SDP Offer/Answer-এর জন্য Signaling (Standard নেই, প্রায়ই একটা WebSocket), Hole Punching-এর জন্য STUN সহ ICE, আর NAT বা Firewall আটকালে Relay করতে TURN লাগে (প্রায় ১০–২০% Session, আর এতে Bandwidth-এর খরচ)। Media সবসময় DTLS-SRTP দিয়ে Encrypted; Data Channel হলো DTLS-এর ওপর SCTP।
  • Group Call চলে SFU দিয়ে (Simulcast বা SVC সহ), কালেভদ্রে MCU; Mesh চলে শুধু হাতে গোনা কয়েকজনে।
  • সবচেয়ে সহজ হাতিয়ার বাছো: কালেভদ্রে Update-এর জন্য Polling, Server থেকে Client-এর Stream-এ SSE, দুই দিকের Messaging-এ WebSocket, Media-র জন্য WebRTC; WebTransport HTTP/3-এর নতুন অতিথি, MQTT IoT-র Standard, আর gRPC Streaming Service থেকে Service-এর বিকল্প।