WebSocket ও রিয়েল-টাইম কমিউনিকেশন
এই পাঠে যা শিখবেন
- request-response মডেলের সীমাবদ্ধতা — কেন সার্ভার নিজে থেকে ক্লায়েন্টকে কিছু জানাতে পারে না
- WebSocket persistent connection কী সমস্যার সমাধান করে
- pub-sub প্যাটার্ন — চ্যানেল, subscribe, publish
- Python দিয়ে একটি সম্পূর্ণ, সত্যিকারের কার্যকর
Brokerক্লাস — একাধিক সাবস্ক্রাইবারকে বাস্তবে মেসেজ ডেলিভার করা
১ · কেন WebSocket দরকার
REST বা GraphQL-এ একটি রিকোয়েস্ট-রেসপন্স চক্রেই সবকিছু শেষ — ক্লায়েন্ট রিকোয়েস্ট পাঠায়, সার্ভার রেসপন্স দেয়, কানেকশন বন্ধ হয়ে যায়। সার্ভারের কাছে যদি ১০ সেকেন্ড পরে নতুন কোনো তথ্য (যেমন একটি চ্যাট মেসেজ) চলে আসে, ক্লায়েন্টকে সেটা জানানোর কোনো উপায় নেই, যতক্ষণ না ক্লায়েন্ট নিজে আবার একটি নতুন রিকোয়েস্ট পাঠায়। WebSocketWebSocketএকটি একবার খোলা, দীর্ঘস্থায়ী, দ্বিমুখী কানেকশন যেখানে সার্ভার ও ক্লায়েন্ট উভয়েই যেকোনো সময় বার্তা পাঠাতে পারে। এই সমস্যার সমাধান দেয় — একবার কানেকশন খোলার পর সেটি খোলা থাকে, এবং সার্ভার নিজে থেকেই, যেকোনো মুহূর্তে, ক্লায়েন্টকে ডেটা পাঠাতে পারে।
২ · Pub-Sub প্যাটার্ন — চ্যানেল, Subscribe, Publish
বাস্তব WebSocket সার্ভারগুলো (Socket.IO, Django Channels, ইত্যাদি) সাধারণত pub-sub (publish-subscribe) প্যাটার্নে সংগঠিত হয় — একটি কেন্দ্রীয় "broker" থাকে, যার কাছে ক্লায়েন্টরা নির্দিষ্ট চ্যানেলে সাবস্ক্রাইব করে, আর যেকোনো ইভেন্ট সেই চ্যানেলে পাবলিশ হলে broker সব সাবস্ক্রাইবারকেই জানিয়ে দেয়।
একটি নামযুক্ত টপিক (যেমন
"chat-room-1") — নির্দিষ্ট ইভেন্টগুলো এই নামের অধীনে গ্রুপ করা হয়।একটি callback ফাংশন একটি চ্যানেলের সাথে রেজিস্টার করা — "এই চ্যানেলে কিছু ঘটলে আমাকে জানাও"।
একটি চ্যানেলে একটি মেসেজ পাঠানো — broker তখন সেই চ্যানেলের সব সাবস্ক্রাইবারের callback সাথে সাথে কল করে।
৩ · Python দিয়ে একটি সত্যিকারের Broker
নিচের কোড সেলে Broker ক্লাসে subscribe(channel, callback) callback-গুলোকে
চ্যানেল-নাম দিয়ে কী করা একটি dict-এ সংরক্ষণ করে, আর publish(channel, message) সেই চ্যানেলের
নিবন্ধিত সবগুলো callback-কে সরাসরি, সত্যিকারভাবে কল করে — এটাই মেসেজ ডেলিভারি, কোনো ভুয়া
স্ট্রিং নয়। তিনজন সাবস্ক্রাইবার একই চ্যানেলে, আরেকজন ভিন্ন চ্যানেলে সাবস্ক্রাইব করে দেখানো হয়েছে কে কী পায়।
# ---------- BROKER -- pub-sub-এর মূল ইঞ্জিন ----------
class Broker:
def __init__(self):
self.subscribers = {} # channel_name -> callback ফাংশনের লিস্ট
def subscribe(self, channel, callback):
self.subscribers.setdefault(channel, []).append(callback)
def publish(self, channel, message):
for callback in self.subscribers.get(channel, []):
callback(message) # সত্যিকারের ফাংশন কল -- এটাই মেসেজ ডেলিভারি
received_log = [] # কে কী পেয়েছে তার লগ, যাচাইয়ের জন্য
def make_subscriber(name):
def handler(message):
received_log.append((name, message))
print(f"[{name}] পেল: {message}")
return handler
broker = Broker()
broker.subscribe("chat-room-1", make_subscriber("Subscriber A"))
broker.subscribe("chat-room-1", make_subscriber("Subscriber B"))
broker.subscribe("chat-room-1", make_subscriber("Subscriber C"))
broker.subscribe("notifications", make_subscriber("Subscriber D")) # আলাদা চ্যানেল
print("--- broker.publish('chat-room-1', ...) কল করা হচ্ছে ---")
broker.publish("chat-room-1", "নতুন মেসেজ: হ্যালো সবাই!")
names_that_received = [name for name, _ in received_log]
print(f"\nchat-room-1 পাবলিশের পর কতজন পেল: {len(received_log)}")
print(f"Subscriber D কি এর মধ্যে আছে?: {'Subscriber D' in names_that_received}")
print("\n--- broker.publish('notifications', ...) কল করা হচ্ছে ---")
broker.publish("notifications", "নতুন নোটিফিকেশন: আপনার পোস্টে কমেন্ট এসেছে")
print(f"\nনোটিফিকেশন পাবলিশের পর মোট পেল: {len(received_log)}")
print(f"সর্বশেষ যে পেল: {received_log[-1][0]}")
broker.publish("chat-room-1", ...) কল করলে self.subscribers["chat-room-1"]
লিস্টের তিনটি callback-ই (A, B, C) সরাসরি কল হয়, তাই received_log-এ তিনটি এন্ট্রি যোগ হয় —
Subscriber D এর মধ্যে নেই, কারণ D শুধু "notifications" চ্যানেলে সাবস্ক্রাইব
করেছিল, "chat-room-1"-এ নয়; self.subscribers.get("chat-room-1", [])-এর লিস্টে D-র
callback কখনোই ছিল না। তারপর broker.publish("notifications", ...) কল করলে শুধু D-র callback
কল হয় (A, B, C আবার কল হয় না, কারণ তারা "notifications"-এ সাবস্ক্রাইব করেনি), তাই
received_log তিন থেকে বেড়ে চার-এ পৌঁছায়।
WebSocket একটি persistent, দ্বিমুখী কানেকশন খুলে দেয় যাতে সার্ভার নিজে থেকেই, ক্লায়েন্টের নতুন রিকোয়েস্টের
অপেক্ষা না করে, ডেটা push করতে পারে। এই push সাধারণত pub-sub প্যাটার্নে বাস্তবায়িত হয় — একটি broker
চ্যানেল-ভিত্তিক callback রাখে, আর publish() কল হলে সেই চ্যানেলের সবগুলো callback সরাসরি কল
হয়ে যায়। পরের পাঠে (L46) দেখব WebSocket সবসময় প্রয়োজন কি না — polling ও Server-Sent Events কীভাবে সহজতর
বিকল্প হতে পারে, এবং প্রকৃত নেটওয়ার্ক-কল গুনে এই push পদ্ধতির দক্ষতা যাচাই করব।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।
প্র ০১
broker.publish()-এর ভেতরের callback কলগুলো সিনক্রোনাসভাবে (একটির পর একটি, সাথে সাথে)
হয় — এটাকে কেন তবু "push" বলা হয়?
"Push"-এর মূল অর্থ হলো কে উদ্যোগ নিয়েছে — ক্লায়েন্ট কোনো নতুন রিকোয়েস্ট পাঠায়নি, বরং সার্ভার (broker) নিজে থেকেই সাবস্ক্রাইবারদের callback কল করেছে। এটা "pull" (ক্লায়েন্ট বারবার জিজ্ঞেস করা, যেমন L46-এর polling)-এর ঠিক বিপরীত। সিনক্রোনাস নাকি অ্যাসিনক্রোনাসভাবে callback কল হচ্ছে সেটা push-বনাম-pull প্রশ্নের সাথে সম্পর্কিত না — বাস্তব WebSocket সার্ভারে এই কলগুলো নেটওয়ার্কে অ্যাসিনক্রোনাস হয়, কিন্তু "কে শুরু করলো" প্রশ্নের উত্তর একই থাকে।
প্র ০২
chat-room-1-এ পাবলিশ করার পর Subscriber D কেন কিছুই পেল না?
কারণ Subscriber D-এর callback শুধু self.subscribers["notifications"] লিস্টে
আছে, self.subscribers["chat-room-1"]-এ নয়। publish("chat-room-1", ...)
শুধু self.subscribers.get("chat-room-1", []) লিস্টের উপর লুপ চালায় — D-র callback এই
লিস্টে না থাকায় সেটা কখনো কল হয় না। এটাই চ্যানেল-ভিত্তিক আইসোলেশনের মূল কথা।
প্র ০৩
বাস্তব একটি WebSocket সার্ভারে (যেমন Socket.IO বা Django Channels) উপরের
self.subscribers dict-টা কীসের সমতুল্য হতে পারে?
এটা মূলত একটি "রুম" বা "চ্যানেল রেজিস্ট্রি"-র সমতুল্য — যেখানে প্রতিটি চ্যানেল-নামের বিপরীতে সেই চ্যানেলে সংযুক্ত (connected) সব সক্রিয় WebSocket কানেকশনের একটি তালিকা রাখা হয়। বাস্তবে callback ফাংশনের বদলে সাধারণত একটি কানেকশন অবজেক্ট থাকে যাতে সরাসরি ডেটা পাঠানো যায়, এবং একাধিক সার্ভার প্রসেসের মধ্যে এই রেজিস্ট্রি সমন্বয় করতে প্রায়ই Redis-এর মতো একটি শেয়ার্ড মেসেজ ব্রোকারও যোগ হয় — কিন্তু মূল ধারণাটি (চ্যানেল-নাম → সাবস্ক্রাইবার লিস্ট) একই থাকে।
অনুশীলন
-
চিন্তা করুন: যদি একটি নতুন
Subscriber E-কেbroker.subscribe("chat-room-1", ...)দিয়ে যোগ করা হয়, কিন্তু সেটাbroker.publish("chat-room-1", "হ্যালো সবাই!")কল করার পরে করা হয়, তাহলে E কি সেই মেসেজটা পাবে?না।
publish()শুধু সেই মুহূর্তেself.subscribers["chat-room-1"]লিস্টে যা আছে, তাদের callback কল করে — এটা কোনো মেসেজ "সংরক্ষণ" করে রাখে না। E যদি publish হওয়ার পরে সাবস্ক্রাইব করে, তখন সেই আগের মেসেজটা আর পাবে না, শুধু ভবিষ্যতের নতুন publish কলগুলোই পাবে। এটাই দেখায় এই সাধারণ Broker "at-most-once, live-only" ডেলিভারি করে — মেসেজ হিস্ট্রি রাখা একটা আলাদা (আরও জটিল) বৈশিষ্ট্য। -
পরীক্ষা করুন: উপরের কোড সেলে
"notifications"চ্যানেলেmake_subscriber("Subscriber E")দিয়ে আরেকজন সাবস্ক্রাইবার যোগ করুন (D-এর মতো), তারপরbroker.publish("notifications", ...)কলের পরreceived_log-এর দৈর্ঘ্য কত হয় (চার-এর বদলে) দেখুন।এখন
self.subscribers["notifications"]-এ দুটো callback (D ও E) থাকবে, তাইpublish("notifications", ...)কল করলে দুজনেরই callback কল হবে, আরreceived_log-এর দৈর্ঘ্য চার-এর বদলে পাঁচ হবে (আগের তিনটি chat-room-1 এন্ট্রি + এখন D ও E — মোট পাঁচটা)। এটা দেখায় একই চ্যানেলে একাধিক সাবস্ক্রাইবার থাকলেpublish()-এর একটি মাত্র কল সবাইকেই পৌঁছে দেয়।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পরবর্তী পাঠ: Server-Sent Events ও পোলিং বিকল্প L46 আজকের push/Broker পদ্ধতির সাথে polling-এর প্রকৃত নেটওয়ার্ক-কল সংখ্যা তুলনা করে দক্ষতার পার্থক্য গণনা করা হবে।
- আগের পাঠে ফিরে যান: GraphQL বনাম REST L44 over-fetching/under-fetching সমস্যার প্রকৃত ফিল্ড-সংখ্যা গণনাসহ তুলনা।
- কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৮টি পাঠ আর্কিটেকচার প্যাটার্ন, ফ্রন্ট-এন্ড/ব্যাক-এন্ড ফ্রেমওয়ার্ক ফান্ডামেন্টাল, স্টেট ম্যানেজমেন্ট, REST API, ORM, অথেন্টিকেশন, রেন্ডারিং স্ট্র্যাটেজি ও ডিপ্লয়মেন্ট।
- সব Courses দেখুন ABCL TECH C, C++, Python, Java, JavaScript, DSA, DBMS, Discrete Mathematics, System Design, Cybersecurity, Cloud Computing & DevOps, Computer Networks, Operating Systems, Computer Architecture, Programming Languages & Compiler Design, Software Engineering & Git, Theory of Computation, Engineering Economics ও Full-Stack Web Frameworks — সব এক জায়গায়।