পাঠ ০৮ · ৫১-এর মধ্যে · মডিউল ২
Home / Courses / System Design / WebSockets ও রিয়েল-টাইম

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

WebSockets & real-time communication
৮ মিনিট পড়া মধ্যম · Intermediate Python কোডসহ সম্পূর্ণ বাংলায়

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

  • WebSocket কী এবং এটি সাধারণ HTTP রিকোয়েস্ট-রেসপন্স থেকে কীভাবে আলাদা
  • পোলিং ও লং-পোলিং-এর তুলনায় WebSocket-এর সুবিধা
  • WebSocket ব্যবহারের বাস্তব উদাহরণ (চ্যাট, নোটিফিকেশন, লাইভ কোলাবোরেশন)
  • একাধিক সার্ভারে WebSocket স্কেল করার মূল চ্যালেঞ্জ ও pub/sub সমাধানের প্রাথমিক ধারণা
  • Python দিয়ে একটি সরল broadcast/pub-sub সিমুলেশন

১ · রিকোয়েস্ট-রেসপন্সের সীমাবদ্ধতা

L06-এ দেখা REST-এর মতো সাধারণ HTTP মডেলে ক্লায়েন্ট সবসময় প্রথমে রিকোয়েস্ট পাঠায়, সার্ভার একটি রেসপন্স দেয়, এবং কানেকশন বন্ধ হয়ে যায় (বা পরবর্তী রিকোয়েস্টের জন্য অপেক্ষা করে)। এই মডেলে সার্ভার নিজে থেকে ক্লায়েন্টকে কিছু পাঠাতে পারে না — ক্লায়েন্টকেই জিজ্ঞাসা করতে হয়। কিন্তু চ্যাট অ্যাপ, লাইভ স্কোরবোর্ড বা শেয়ার মার্কেট টিকারের মতো সিস্টেমে সার্ভারের কাছেই নতুন তথ্য আগে চলে আসে — সার্ভারকে নিজে থেকে ক্লায়েন্টকে জানাতে দেওয়া দরকার।

এই সমস্যার প্রথম সমাধান ছিল polling — ক্লায়েন্ট প্রতি কয়েক সেকেন্ডে বারবার সার্ভারকে জিজ্ঞাসা করে "নতুন কিছু আছে কি?" — বেশিরভাগ সময় উত্তর "না" হয়, তাই প্রচুর অপ্রয়োজনীয় রিকোয়েস্ট নষ্ট হয়। এরপর এলো long-polling — ক্লায়েন্ট একটি রিকোয়েস্ট পাঠায় এবং সার্ভার তা খোলা রাখে যতক্ষণ না নতুন ডেটা আসে (বা টাইমআউট হয়), তারপর রেসপন্স দিয়ে ক্লায়েন্ট সাথে সাথে আরেকটি নতুন রিকোয়েস্ট পাঠায়। এটি পোলিং-এর চেয়ে ভালো কিন্তু এখনও প্রতিটি "চক্র"-এ একটি নতুন HTTP কানেকশন খোলার ওভারহেড আছে।

Polling
বারবার জিজ্ঞাসা — বেশিরভাগ উত্তর "নতুন কিছু নেই", অপচয়।
Long-polling
রিকোয়েস্ট খোলা রাখা হয় ডেটা না আসা পর্যন্ত — ভালো, কিন্তু চক্র প্রতি নতুন কানেকশন।
WebSocket
একবার কানেকশন, দুদিক থেকেই যতবার খুশি বার্তা — সবচেয়ে কম ওভারহেড।

২ · WebSocket কী এবং কীভাবে শুরু হয়

WebSocketWebSocketএকটি একক TCP কানেকশনের উপর তৈরি ফুল-ডুপ্লেক্স (দুদিক থেকেই একসাথে) যোগাযোগ প্রোটোকল, যা একটি HTTP "Upgrade" হ্যান্ডশেক দিয়ে শুরু হয়ে সার্ভার ও ক্লায়েন্ট উভয়কেই যেকোনো সময় বার্তা পাঠানোর সুযোগ দেয়। একটি সাধারণ HTTP রিকোয়েস্ট হিসেবেই শুরু হয় — ক্লায়েন্ট একটি বিশেষ হেডার পাঠায় (Connection: Upgrade, Upgrade: websocket)। সার্ভার রাজি হলে 101 Switching Protocols রেসপন্স দেয়, এবং এরপর সেই একই TCP কানেকশনটি একটি WebSocket কানেকশনে "রূপান্তরিত" হয়ে যায় — আর নতুন করে HTTP হেডার/রিকোয়েস্ট লাগে না। এখন থেকে সার্ভার ও ক্লায়েন্ট উভয়ই এই একই কানেকশনে যখন খুশি বার্তা পাঠাতে পারে — এটাই ফুল-ডুপ্লেক্স।

কেন এটা গুরুত্বপূর্ণ

একবার হ্যান্ডশেক সম্পন্ন হলে, প্রতিটি বার্তার জন্য নতুন করে TCP কানেকশন খোলা, TLS হ্যান্ডশেক করা, বা HTTP হেডার পাঠানোর প্রয়োজন নেই — এই সবকিছুই লেটেন্সি যোগ করে। WebSocket এই ওভারহেড একবারই দেয়, তারপর প্রতিটি বার্তা প্রায় সরাসরি কাঁচা ডেটা হিসেবে যায় — এই কারণেই এটি রিয়েল-টাইম অ্যাপ্লিকেশনে এত দ্রুত।

৩ · ব্যবহারিক ক্ষেত্র

  • চ্যাট/মেসেজিং — নতুন মেসেজ এলে সাথে সাথে সব অংশগ্রহণকারীর কাছে পৌঁছাতে হয় (L45-এ পূর্ণ কেস স্টাডি)।
  • লাইভ নোটিফিকেশন — লাইক, কমেন্ট, ফলো-এর তাৎক্ষণিক আপডেট (L51-এর ক্যাপস্টোনে এই বিষয়ে ফিরে আসব)।
  • কোলাবোরেটিভ এডিটিং — একই ডকুমেন্টে একাধিক ইউজার একসাথে টাইপ করলে সবার স্ক্রিনে সাথে সাথে আপডেট।
  • লাইভ স্কোর/টিকার — শেয়ার বাজারের দাম বা খেলার স্কোর প্রতি মুহূর্তে আপডেট হওয়া।

৪ · স্কেলিং চ্যালেঞ্জ — কানেকশন স্টেটফুল

একটি সাধারণ স্টেটলেস HTTP রিকোয়েস্ট (L12-এ বিস্তারিত) যেকোনো সার্ভার ইনস্ট্যান্স হ্যান্ডল করতে পারে, কারণ প্রতিটি রিকোয়েস্ট স্বতন্ত্র। কিন্তু একটি WebSocket কানেকশন দীর্ঘস্থায়ী — একবার ক্লায়েন্ট সার্ভার A-এর সাথে কানেকশন খুললে, সেই নির্দিষ্ট কানেকশনটি সার্ভার A-এর মেমরিতেই থাকে। এখন সমস্যা — ইউজার B যদি ইউজার A-কে একটি মেসেজ পাঠাতে চায়, কিন্তু ইউজার A-এর কানেকশন সার্ভার A-তে আছে, আর ইউজার B-এর রিকোয়েস্ট লোড ব্যালেন্সার (L11) সার্ভার C-তে পাঠিয়েছে — সার্ভার C কীভাবে জানবে ইউজার A কোথায় আছে?

সমাধান — প্রতিটি সার্ভার ইনস্ট্যান্স একটি শেয়ার্ড pub/sub ব্যাকএন্ডে (যেমন Redis Pub/Sub, M6/L22-এ বিস্তারিত) পাবলিশ করে, এবং যে সার্ভার প্রাপকের কানেকশন ধরে আছে সে সেই টপিক সাবস্ক্রাইব করে বার্তাটি নিজের ধরে রাখা কানেকশনে ফরোয়ার্ড করে। এভাবে যেকোনো সার্ভার যেকোনো ক্লায়েন্টের কাছে বার্তা পৌঁছাতে পারে, কানেকশন আসলে কোন সার্ভারে আছে তা নির্বিশেষে। বিকল্প হিসেবে "sticky sessions" (একই ইউজারকে সবসময় একই সার্ভারে পাঠানো) ব্যবহার করা যায়, কিন্তু সেটি লোড ব্যালেন্সিং-এর নমনীয়তা কমিয়ে দেয়।

ইউজার B Sender (Server A) সার্ভার A WS connection: B Pub/Sub ব্যাকএন্ড Redis / message broker সার্ভার B Subscribed to topic ইউজার A Receiver (Server B)
ইউজার B ও ইউজার A ভিন্ন সার্ভারে কানেক্টেড — pub/sub ব্যাকএন্ড না থাকলে সার্ভার A জানতেই পারবে না ইউজার A কোথায় আছে।

৫ · কোড দিয়ে দেখা — একাধিক ক্লায়েন্টে Broadcast

নিচে একটি সরল broadcast(message, subscribers) ফাংশন — এটি একটি WebSocket সার্ভার একই সময়ে একাধিক কানেক্টেড ক্লায়েন্টকে কীভাবে একটি বার্তা পাঠায় তার মূল ধারণাটি সিমুলেট করে (real pub/sub ব্যাকএন্ড ছাড়াই, শুধু লজিকটি বোঝার জন্য)।

Python
subscribers = ["client_1", "client_2", "client_3", "client_4"]

def broadcast(message, subscribers):
    delivered = []
    for client in subscribers:
        # বাস্তবে এখানে প্রতিটি ক্লায়েন্টের খোলা WebSocket কানেকশনে ডেটা পাঠানো হতো
        delivered.append(client)
        print(f"  পাঠানো হলো -> {client}: \"{message}\"")
    return delivered

print("=== broadcast: নতুন চ্যাট মেসেজ ===")
delivered = broadcast("রহিম: হ্যালো সবাই!", subscribers)
print(f"\nমোট {len(delivered)}টি ক্লায়েন্ট মেসেজ পেয়েছে: {delivered}")

# client_3 চ্যাট রুম থেকে বেরিয়ে গেল (WebSocket কানেকশন বন্ধ, unsubscribe)
subscribers.remove("client_3")

print("\n=== client_3 চলে যাওয়ার পর আবার broadcast ===")
delivered_2 = broadcast("করিম: client_3 কোথায় গেল?", subscribers)
print(f"\nমোট {len(delivered_2)}টি ক্লায়েন্ট মেসেজ পেয়েছে: {delivered_2}")
print(f"client_3 এবার মেসেজ পেয়েছে কি? {'client_3' in delivered_2}")

    
লক্ষ্য করুন — প্রথম broadcast-এ ৪ জন ক্লায়েন্ট মেসেজ পায়, কিন্তু client_3 unsubscribe করার পর দ্বিতীয় broadcast-এ শুধু ৩ জন পায়। বাস্তব সিস্টেমে এই subscribers তালিকাটি রক্ষণাবেক্ষণ করাই সবচেয়ে জটিল অংশ — বিশেষ করে যখন হাজার হাজার সার্ভার ইনস্ট্যান্স জুড়ে লক্ষ লক্ষ কানেকশন ছড়িয়ে থাকে।
মূল কথা · Key takeaway

WebSocket রিকোয়েস্ট-রেসপন্স মডেলের সীমাবদ্ধতা ভেঙে সার্ভার-পুশ সম্ভব করে, কিন্তু এই সুবিধার মূল্য হলো কানেকশন স্টেটফুল হয়ে যাওয়া — যা স্কেল করতে একটি অতিরিক্ত pub/sub স্তর দাবি করে। L12-এ আমরা stateless বনাম stateful ডিজাইনের এই ট্রেড-অফ আরও গভীরে দেখব।

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।

প্র ০১ লং-পোলিং যখন প্রায় WebSocket-এর মতোই ফলাফল দেয়, তখন WebSocket আলাদা করে দরকার কেন?

লং-পোলিং প্রতিটি "চক্র" শেষে ক্লায়েন্টকে আবার নতুন করে একটি HTTP রিকোয়েস্ট পাঠাতে হয় (নতুন TCP+TLS হ্যান্ডশেক ওভারহেডসহ, যদিও কানেকশন-রিইউজ কিছুটা কমায়), এবং প্রতিটি রিকোয়েস্টে পূর্ণ HTTP হেডার বহন করতে হয়। WebSocket একবারই হ্যান্ডশেক করে, এরপর প্রতিটি বার্তা প্রায় কাঁচা ফ্রেম আকারে যায় — অনেক কম ওভারহেড, বিশেষ করে যখন প্রতি সেকেন্ডে বহুবার বার্তা আদান-প্রদান হয় (যেমন কোলাবোরেটিভ এডিটিং বা লাইভ গেম)। উচ্চ-ফ্রিকোয়েন্সি, সত্যিকারের দ্বিমুখী যোগাযোগে WebSocket-এর দক্ষতা পার্থক্য স্পষ্ট হয়ে ওঠে।

প্র ০২ "Sticky sessions" (একই ইউজারকে সবসময় একই সার্ভারে পাঠানো) কি pub/sub-এর একটি সহজ বিকল্প নয়?

আংশিকভাবে — sticky sessions একজন নির্দিষ্ট ইউজারের কানেকশন খুঁজে পাওয়ার সমস্যা সমাধান করে, কিন্তু নতুন সমস্যা তৈরি করে। লোড ব্যালেন্সার (L11) আর স্বাধীনভাবে সবচেয়ে কম-লোডেড সার্ভারে রিকোয়েস্ট পাঠাতে পারে না — একটি নির্দিষ্ট ইউজারকে সবসময় একই সার্ভারে পাঠাতে বাধ্য, যা লোড ভারসাম্যহীন করে দিতে পারে। এবং সেই সার্ভার ক্র্যাশ করলে তার সব কানেকশন হারিয়ে যায় ও পুনরায় সংযোগ করতে হয়। pub/sub এই সীমাবদ্ধতা ছাড়াই যেকোনো সার্ভার থেকে যেকোনো ক্লায়েন্টে বার্তা পৌঁছানোর নমনীয়তা দেয়, তাই বড় স্কেলে এটিই বেশি পছন্দনীয়।

প্র ০৩ WebSocket কানেকশন যদি হঠাৎ বিচ্ছিন্ন হয়ে যায় (যেমন মোবাইল নেটওয়ার্ক পরিবর্তনে), তাহলে কী হতে পারে?

কানেকশন বিচ্ছিন্ন হলে সেই সময়ে পাঠানো যেকোনো বার্তা হারিয়ে যেতে পারে, এবং ক্লায়েন্টকে পুনরায় হ্যান্ডশেক করে নতুন কানেকশন খুলতে হয়। এই কারণে বাস্তব সিস্টেমে প্রায়ই বার্তা প্রথমে স্থায়ীভাবে সংরক্ষণ করা হয় (একটি ডেটাবেস বা কিউতে, M6/L22-এ দেখব) এবং তারপর সরবরাহ করা হয় — যাতে পুনঃসংযোগের পর ক্লায়েন্ট "শেষবার যা দেখেছিল তার পর থেকে" মেসেজগুলো আবার আনতে পারে। এভাবে WebSocket শুধু দ্রুত ডেলিভারির মাধ্যম, ডেটার একমাত্র সংরক্ষণাগার নয়।

অনুশীলন

  1. চিন্তা করুন: একটি নিউজ ওয়েবসাইটের "ব্রেকিং নিউজ" ব্যানার এবং একটি টাইপিং-ইন্ডিকেটর-সহ চ্যাট অ্যাপ — এই দুটির মধ্যে কোনটির জন্য WebSocket বেশি জরুরি এবং কোনটি সাধারণ পোলিং দিয়েও চলতে পারে?

    চ্যাট অ্যাপের টাইপিং-ইন্ডিকেটরে WebSocket প্রায় অপরিহার্য — এটি সেকেন্ডের ভগ্নাংশে বারবার আপডেট হয় এবং সত্যিকারের তাৎক্ষণিক অনুভূতি দরকার। ব্রেকিং নিউজ ব্যানার তুলনামূলক কম-ফ্রিকোয়েন্সি ইভেন্ট (দিনে কয়েকবার আপডেট হতে পারে), তাই একটি সাধারণ ৩০-৬০ সেকেন্ডের পোলিং ইন্টারভালও গ্রহণযোগ্য হতে পারে এবং অনেক সহজ ইনফ্রাস্ট্রাকচারে বাস্তবায়ন করা যায়।

  2. কোড এক্সটেন্ড করুন: উপরের কোড সেলে একটি নতুন selective_broadcast(message, subscribers, exclude) ফাংশন লিখুন যা একটি নির্দিষ্ট ক্লায়েন্টকে (যেমন যে মেসেজ পাঠিয়েছে তাকে) বার্তাটি আবার না পাঠিয়ে বাকি সবাইকে পাঠায়।

    সমাধান — বিদ্যমান subscribers তালিকা থেকে exclude ক্লায়েন্টকে ফিল্টার করে বাকিদের উপর broadcast() চালাতে হবে:

    def selective_broadcast(message, subscribers, exclude):
        targets = [c for c in subscribers if c != exclude]
        return broadcast(message, targets)
    
    # উদাহরণ: client_1 নিজেই মেসেজ পাঠিয়েছে, তাই তাকে বাদ দিয়ে বাকিদের পাঠানো হচ্ছে
    selective_broadcast("client_1: সবাই কেমন আছেন?", subscribers, exclude="client_1")

    এটিই বাস্তব চ্যাট সিস্টেমে সাধারণ আচরণ — নিজের পাঠানো মেসেজ নিজের কাছে আবার সার্ভার থেকে ফেরত পাঠানোর দরকার নেই, কারণ ক্লায়েন্ট নিজেই তা তার UI-তে সাথে সাথে দেখিয়ে দেয়।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

পূর্ববর্তী পাঠ
gRPC ও GraphQL — মডার্ন API প্যারাডাইম