কেস স্টাডি: সকেট দিয়ে একটি চ্যাট অ্যাপ্লিকেশন বানানো
এই পাঠে যা শিখবেন
- একটি TCP-ভিত্তিক চ্যাট অ্যাপ্লিকেশনের সার্ভার-সাইড ডিজাইন
- ব্রডকাস্ট প্যাটার্ন — এক ক্লায়েন্টের মেসেজ বাকি সবার কাছে পাঠানো
- TCP-এর কাঁচা বাইট স্ট্রিমের উপর কেন ও কীভাবে মেসেজ সীমানা নির্ধারণ করতে হয়
- Python দিয়ে সম্পূর্ণ ইন-মেমরি সিমুলেশনে এই প্যাটার্ন যাচাই
১ · দৃশ্যপট (Scenario)
ধরুন আপনি একটি সাধারণ গ্রুপ চ্যাট অ্যাপ্লিকেশন বানাচ্ছেন — একাধিক ব্যবহারকারী একটি সার্ভারে সংযুক্ত হবেন এবং যেকোনো একজনের পাঠানো মেসেজ বাকি সবাই দেখতে পাবেন। এটি সাধারণ request-response প্যাটার্নের (যেমন একটি ওয়েব সার্ভার) চেয়ে ভিন্ন — এখানে সার্ভারকে একাধিক সক্রিয় কানেকশন একসাথে পরিচালনা করতে হয় এবং একটি কানেকশন থেকে আসা ডেটা বাকি কানেকশনগুলোতে ফরোয়ার্ড করতে হয়।
২ · ওয়াকথ্রু (Walkthrough)
সার্ভার একটি well-known পোর্টে শোনে (M5/L22-এ শেখা পোর্ট-ভিত্তিক মাল্টিপ্লেক্সিং), এবং প্রতিটি
নতুন ক্লায়েন্ট সংযোগের জন্য একটি আলাদা কানেকশন গ্রহণ করে — M7/L35-এ শেখা accept() প্যাটার্নের
অনুরূপ, যেখানে প্রতিটি ক্লায়েন্ট তার নিজস্ব ডেডিকেটেড কানেকশন পায়। কিন্তু এখানে সার্ভারকে একটি ধাপ এগিয়ে
যেতে হয় — শুধু একটি ক্লায়েন্টের সাথে কথা বলা নয়, বরং সক্রিয় ক্লায়েন্টদের একটি তালিকা বজায়
রাখা এবং যেকোনো একজনের মেসেজ বাকি সবার কাছে broadcast করা — M7/L37-এ আলোচিত
মাল্টি-ক্লায়েন্ট-হ্যান্ডলিং প্যাটার্নের একটি বাস্তব প্রয়োগ।
M5/L25-এ শেখা TCP-এর নির্ভরযোগ্যতার গ্যারান্টি নিশ্চিত করে যে বাইট সঠিক ক্রমে ও সম্পূর্ণ পৌঁছাবে — কিন্তু
TCP নিজে "একটি চ্যাট মেসেজ" ধারণাটি জানে না, এটি শুধু বাইটের একটি অবিচ্ছিন্ন স্ট্রিম। তাই
অ্যাপ্লিকেশনকেই নিজের মেসেজ সীমানা নির্ধারণ করতে হয় — সবচেয়ে সাধারণ দুটি সমাধান হলো একটি ডিলিমিটার
ক্যারেক্টার (যেমন নিউলাইন \n) অথবা একটি length-prefix হেডার (মেসেজের দৈর্ঘ্য আগে থেকে পাঠানো)।
এটি নতুনদের জন্য প্রায়ই একটি বিস্ময়কর বাস্তবতা — "TCP তো নির্ভরযোগ্য, তাহলে মেসেজ আলাদা করার দরকার কেন?"
# এই সিমুলেশন কোনো প্রকৃত socket খোলে না — Pyodide sandbox real port bind করতে
# পারে না, তাই এটি real socket API-এর আচরণের একটি ইচ্ছাকৃত ইন-মেমরি সিমুলেশন।
class MockSocket:
"""M7-এর মক-সকেট প্যাটার্নের পুনর্ব্যবহার — recv()-এর মতো একটি ইনবক্স রাখে।"""
def __init__(self, client_id):
self.client_id = client_id
self.inbox = []
def deliver(self, message):
"""সার্ভার এই মেথড কল করে এই ক্লায়েন্টকে একটি মেসেজ 'পাঠায়' (real sendall()-এর সিমুলেশন)।"""
self.inbox.append(message)
class ChatServer:
"""একটি well-known পোর্টে (M5/L22) 'শোনে' — একাধিক ক্লায়েন্ট কানেকশনের
তালিকা রাখে (M7/L35 accept(), M7/L37 multi-client handling)।"""
def __init__(self, port=5000):
self.port = port
self.clients = {} # client_id -> MockSocket
def connect(self, client_id):
sock = MockSocket(client_id)
self.clients[client_id] = sock
return sock
def broadcast(self, sender_id, message, clients):
"""প্রেরক ছাড়া বাকি সব সংযুক্ত ক্লায়েন্টের কাছে মেসেজ পাঠায়।"""
framed_message = f"[{sender_id}] {message}\n".strip() # নিউলাইন ডিলিমিটার = মেসেজ সীমানা
for cid, sock in clients.items():
if cid != sender_id:
sock.deliver(framed_message)
# --- সিমুলেশন: ৩ জন ক্লায়েন্ট একটি চ্যাট সার্ভারে সংযুক্ত ---
server = ChatServer(port=5000)
alice = server.connect("Alice")
bob = server.connect("Bob")
carol = server.connect("Carol")
print(f"সার্ভার পোর্ট {server.port}-এ 'শুনছে', সংযুক্ত ক্লায়েন্ট: {list(server.clients)}\n")
server.broadcast("Alice", "সবাই কেমন আছেন?", server.clients)
print("Alice-এর ইনবক্স (নিজের পাঠানো মেসেজ নিজে পায় না):", alice.inbox)
print("Bob-এর ইনবক্স: ", bob.inbox)
print("Carol-এর ইনবক্স:", carol.inbox)
assert alice.inbox == []
assert bob.inbox == carol.inbox == ["[Alice] সবাই কেমন আছেন?"]
print("\nব্রডকাস্ট প্যাটার্ন সঠিক — প্রেরক ছাড়া বাকি সবাই মেসেজ পেয়েছে।")
ChatServer-এর প্রতিটি MockSocket আসলে একটি real
socket.socket কানেকশন হবে (M7/L34-L35-এ শেখানো real API অনুযায়ী), এবং সার্ভার সাধারণত threading
বা asyncio ব্যবহার করে একাধিক ক্লায়েন্ট একসাথে পরিচালনা করে। কিন্তু broadcast()-এর মূল
যুক্তি — "প্রেরক ছাড়া বাকি সবাইকে পাঠাও" — ঠিক এই সিমুলেশনের মতোই থাকে।
একটি চ্যাট অ্যাপ্লিকেশন সাধারণ request-response সার্ভারের চেয়ে ভিন্ন কারণ এটি একাধিক সক্রিয় কানেকশনের মধ্যে ডেটা ফরোয়ার্ড করে, শুধু একটির উত্তর দেয় না — আর TCP নির্ভরযোগ্য বাইট ডেলিভারি দিলেও, সেই বাইটের মধ্যে "একটি মেসেজ কোথায় শেষ" তা নির্ধারণ করার দায়িত্ব সবসময় অ্যাপ্লিকেশন স্তরেরই।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।
প্র ০১ প্রেরক নিজেই কেন তার নিজের পাঠানো মেসেজ ফেরত পায় না — এটি কি একটি বাগ?
না, এটি ইচ্ছাকৃত ডিজাইন — প্রেরকের নিজের UI ইতিমধ্যেই তার পাঠানো মেসেজ দেখায় (স্থানীয়ভাবে, সার্ভারে
পাঠানোর সাথে সাথেই)। সার্ভার যদি প্রেরককেও মেসেজ ফেরত পাঠাত, তাহলে সেই মেসেজ দুইবার দেখা যেত —
তাই broadcast() ইচ্ছাকৃতভাবে প্রেরককে বাদ দেয়।
প্র ০২
যদি একজন ক্লায়েন্টের মেসেজে নিজেই একটি নিউলাইন ক্যারেক্টার (\n) থাকে, তাহলে নিউলাইন-ডিলিমিটার পদ্ধতিতে কী সমস্যা হতে পারে?
রিসিভার ভুলভাবে সেই একটি মেসেজকে দুটি আলাদা মেসেজ হিসেবে পড়তে পারে, কারণ ডিলিমিটার শুধু "মেসেজ শেষ" বোঝাতে ব্যবহৃত হয় — মেসেজের ভেতরের কনটেন্ট থেকে আলাদা করা যায় না। এই সমস্যা এড়াতে length-prefix পদ্ধতি প্রায়ই বেশি নির্ভরযোগ্য, কারণ সেখানে মেসেজের দৈর্ঘ্য আগে থেকেই জানা থাকে, কনটেন্ট নির্বিশেষে।
প্র ০৩
এই সার্ভার যদি একসাথে ১০,০০০ ক্লায়েন্ট পরিচালনা করতে হয়, তাহলে self.clients-এর মতো একটি সাধারণ ডিকশনারি যথেষ্ট হবে কি?
যুক্তিগতভাবে হ্যাঁ, কিন্তু ব্যবহারিকভাবে প্রতিটি কানেকশনের জন্য একটি ব্লকিং থ্রেড ব্যবহার করলে (M7/L37-এ আলোচিত ব্লকিং বনাম থ্রেডেড প্যাটার্ন) হাজার হাজার থ্রেড ম্যানেজ করা ব্যয়বহুল হয়ে ওঠে — বাস্তব বড় স্কেলের চ্যাট সিস্টেম সাধারণত asyncio বা event-driven I/O ব্যবহার করে একই থ্রেডে হাজার হাজার কানেকশন দক্ষতার সাথে পরিচালনা করে।
অনুশীলন
-
চিন্তা করুন: কোড সেলে একটি চতুর্থ ক্লায়েন্ট ("Dave") যোগ করুন এবং Bob একটি মেসেজ পাঠালে কারা কারা সেটি পাবে তা যাচাই করুন।
Bob ছাড়া বাকি সবাই — Alice, Carol ও Dave — মেসেজটি পাবে।
broadcast()ফাংশনটি সর্বদা শুধু প্রেরককে বাদ দিয়ে বাকি সব সংযুক্ত ক্লায়েন্টের কাছে মেসেজ পাঠায়, ক্লায়েন্ট সংখ্যা নির্বিশেষে। -
পরীক্ষা করুন: একটি ক্লায়েন্ট "ডিসকানেক্ট" করার একটি
disconnect(client_id)মেথড যোগ করুন যাself.clientsথেকে সেই ক্লায়েন্ট সরিয়ে দেয়, তারপর যাচাই করুন ডিসকানেক্ট হওয়া ক্লায়েন্ট আর ব্রডকাস্ট মেসেজ পায় না।def disconnect(self, client_id): del self.clients[client_id]— এরপরbroadcast()কল করলে সেই ক্লায়েন্ট আরself.clients-এ না থাকায় তারMockSocket.inbox-এ নতুন কোনো মেসেজ যোগ হবে না, কারণ ফাংশনটি শুধু বর্তমানে সংযুক্ত ক্লায়েন্টদের উপর iterate করে।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৭টি পাঠ পরের কেস স্টাডি — নেটওয়ার্ক সমস্যা সিস্টেমেটিকভাবে ডিবাগ করা।
- Cloud Computing & DevOps কোর্স সঙ্গী কোর্স এই ধরনের রিয়েল-টাইম অ্যাপ্লিকেশন কীভাবে স্কেলযোগ্যভাবে ক্লাউডে হোস্ট করা হয় তা শিখতে দেখুন।
- Cybersecurity & Ethical Hacking কোর্স সঙ্গী কোর্স একটি চ্যাট সার্ভার কীভাবে অননুমোদিত ব্যবহারকারীদের থেকে সুরক্ষিত রাখা হয় তা শিখতে দেখুন।
- সব Courses দেখুন ABCL TECH C, C++, Python, Java, JavaScript, DSA, DBMS, Discrete Mathematics, System Design, Cybersecurity, Cloud Computing & DevOps ও Computer Networks — সব এক জায়গায়।