পাঠ ৪৫ · ৫৮-এর মধ্যে · মডিউল ১০
Home / Courses / Full-Stack Web Frameworks / WebSocket ও রিয়েল-টাইম

WebSocket ও রিয়েল-টাইম কমিউনিকেশন

WebSockets & Real-Time Communication
১০ মিনিট পড়া মধ্যম · Intermediate Python কোডসহ সম্পূর্ণ বাংলায়

এই পাঠে যা শিখবেন

  • request-response মডেলের সীমাবদ্ধতা — কেন সার্ভার নিজে থেকে ক্লায়েন্টকে কিছু জানাতে পারে না
  • WebSocket persistent connection কী সমস্যার সমাধান করে
  • pub-sub প্যাটার্ন — চ্যানেল, subscribe, publish
  • Python দিয়ে একটি সম্পূর্ণ, সত্যিকারের কার্যকর Broker ক্লাস — একাধিক সাবস্ক্রাইবারকে বাস্তবে মেসেজ ডেলিভার করা

১ · কেন WebSocket দরকার

REST বা GraphQL-এ একটি রিকোয়েস্ট-রেসপন্স চক্রেই সবকিছু শেষ — ক্লায়েন্ট রিকোয়েস্ট পাঠায়, সার্ভার রেসপন্স দেয়, কানেকশন বন্ধ হয়ে যায়। সার্ভারের কাছে যদি ১০ সেকেন্ড পরে নতুন কোনো তথ্য (যেমন একটি চ্যাট মেসেজ) চলে আসে, ক্লায়েন্টকে সেটা জানানোর কোনো উপায় নেই, যতক্ষণ না ক্লায়েন্ট নিজে আবার একটি নতুন রিকোয়েস্ট পাঠায়। WebSocketWebSocketএকটি একবার খোলা, দীর্ঘস্থায়ী, দ্বিমুখী কানেকশন যেখানে সার্ভার ও ক্লায়েন্ট উভয়েই যেকোনো সময় বার্তা পাঠাতে পারে। এই সমস্যার সমাধান দেয় — একবার কানেকশন খোলার পর সেটি খোলা থাকে, এবং সার্ভার নিজে থেকেই, যেকোনো মুহূর্তে, ক্লায়েন্টকে ডেটা পাঠাতে পারে।

REST/GraphQL -- প্রতিবার নতুন রিকোয়েস্ট লাগে ক্লায়েন্ট সার্ভার -- কানেকশন বন্ধ, সার্ভার একা কিছু পাঠাতে পারে না WebSocket -- একবার খোলা, persistent, দ্বিমুখী ক্লায়েন্ট সার্ভার সার্ভার নিজে থেকে push করলো (কোনো নতুন রিকোয়েস্ট ছাড়াই)
WebSocket-এ কানেকশনটি একবার খোলার পর খোলাই থাকে — সার্ভার যেকোনো মুহূর্তে, ক্লায়েন্টের নতুন রিকোয়েস্টের অপেক্ষা না করেই, ডেটা push করতে পারে।

২ · Pub-Sub প্যাটার্ন — চ্যানেল, Subscribe, Publish

বাস্তব WebSocket সার্ভারগুলো (Socket.IO, Django Channels, ইত্যাদি) সাধারণত pub-sub (publish-subscribe) প্যাটার্নে সংগঠিত হয় — একটি কেন্দ্রীয় "broker" থাকে, যার কাছে ক্লায়েন্টরা নির্দিষ্ট চ্যানেলে সাবস্ক্রাইব করে, আর যেকোনো ইভেন্ট সেই চ্যানেলে পাবলিশ হলে broker সব সাবস্ক্রাইবারকেই জানিয়ে দেয়।

Channel
একটি নামযুক্ত টপিক (যেমন "chat-room-1") — নির্দিষ্ট ইভেন্টগুলো এই নামের অধীনে গ্রুপ করা হয়।
Subscribe
একটি callback ফাংশন একটি চ্যানেলের সাথে রেজিস্টার করা — "এই চ্যানেলে কিছু ঘটলে আমাকে জানাও"।
Publish
একটি চ্যানেলে একটি মেসেজ পাঠানো — broker তখন সেই চ্যানেলের সব সাবস্ক্রাইবারের callback সাথে সাথে কল করে।

৩ · Python দিয়ে একটি সত্যিকারের Broker

নিচের কোড সেলে Broker ক্লাসে subscribe(channel, callback) callback-গুলোকে চ্যানেল-নাম দিয়ে কী করা একটি dict-এ সংরক্ষণ করে, আর publish(channel, message) সেই চ্যানেলের নিবন্ধিত সবগুলো callback-কে সরাসরি, সত্যিকারভাবে কল করে — এটাই মেসেজ ডেলিভারি, কোনো ভুয়া স্ট্রিং নয়। তিনজন সাবস্ক্রাইবার একই চ্যানেলে, আরেকজন ভিন্ন চ্যানেলে সাবস্ক্রাইব করে দেখানো হয়েছে কে কী পায়।

Python
# ---------- 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 তিন থেকে বেড়ে চার-এ পৌঁছায়।
মূল কথা · Key takeaway

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-এর মতো একটি শেয়ার্ড মেসেজ ব্রোকারও যোগ হয় — কিন্তু মূল ধারণাটি (চ্যানেল-নাম → সাবস্ক্রাইবার লিস্ট) একই থাকে।

অনুশীলন

  1. চিন্তা করুন: যদি একটি নতুন 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" ডেলিভারি করে — মেসেজ হিস্ট্রি রাখা একটা আলাদা (আরও জটিল) বৈশিষ্ট্য।

  2. পরীক্ষা করুন: উপরের কোড সেলে "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-এ আপনার পরবর্তী পদক্ষেপ

আগের পাঠ
GraphQL বনাম REST — কখন কোনটি ব্যবহার করবেন