UDP ক্লায়েন্ট-সার্ভার প্রোগ্রামিং
এই পাঠে যা শিখবেন
- real Python UDP সার্ভার ও ক্লায়েন্ট কোডের গঠন —
recvfrom/sendto - UDP কেন
connect()/accept()ছাড়াই কাজ করে, এবং এটি TCP থেকে কীভাবে ভিন্ন - প্রতিটি
recvfrom()-এ প্রেরকের ঠিকানা কেন গুরুত্বপূর্ণ — কানেকশন-স্টেট না থাকার সরাসরি ফল - Python দিয়ে একাধিক ক্লায়েন্টকে ঠিকানা-ভিত্তিকভাবে আলাদাভাবে সাড়া দেওয়ার সিমুলেশন
১ · real UDP সার্ভার ও ক্লায়েন্ট কোডের গঠন
M7/L35-এ আমরা দেখেছি TCP সার্ভারের bind → listen → accept ধারা। UDP-তে এই ধারা অনেক সহজ,
কারণ UDP connectionlessConnectionlessকোনো আগাম হ্যান্ডশেক বা প্রতিষ্ঠিত কানেকশন-স্টেট ছাড়াই সরাসরি ডেটা পাঠানো — M5/L23-এর UDP-এর মূল বৈশিষ্ট্য। (M5/L23) —
কোনো হ্যান্ডশেক নেই, তাই connect() বা accept()-এর প্রয়োজনই নেই —
s.bind((host, port))data, client_addr = s.recvfrom(1024)s.sendto(response, client_addr)
s.sendto(request, (host, port))data, _ = s.recvfrom(1024)
২ · TCP-এর সাথে মূল পার্থক্য — প্রতিটি recvfrom()-এ প্রেরকের ঠিকানা
M7/L35-এ TCP সার্ভারে accept() প্রতিটি ক্লায়েন্টের জন্য একটি ডেডিকেটেড conn সকেট
তৈরি করে — সেই conn নিজেই "মনে রাখে" এটি কোন ক্লায়েন্টের সাথে কথা বলছে। UDP-তে এমন কোনো
ডেডিকেটেড কানেকশন অবজেক্ট নেই — একটিমাত্র সকেট সব ক্লায়েন্টের ডেটাগ্রাম গ্রহণ করে। তাই প্রতিটি
recvfrom() কল ডেটার পাশাপাশি প্রেরকের ঠিকানাও ফেরত দেয় — অ্যাপ্লিকেশনকেই
নিজে সিদ্ধান্ত নিতে হয় কীভাবে বিভিন্ন প্রেরককে আলাদা রাখবে (যেমন একটি ঠিকানা-ভিত্তিক ডিকশনারি ব্যবহার করে), যদি
একাধিক ক্লায়েন্টকে ভিন্নভাবে সাড়া দেওয়ার প্রয়োজন হয়।
TCP-তে "কে পাঠিয়েছে" প্রশ্নের উত্তর কানেকশন-স্তরেই এমবেডেড থাকে (আপনি জানেন এই নির্দিষ্ট conn
মানেই সেই নির্দিষ্ট ক্লায়েন্ট) — UDP-তে এই তথ্য প্রতিটি বার্তার সাথে আলাদাভাবে বহন করতে হয়, কারণ কোনো
প্রতিষ্ঠিত কানেকশন-স্টেট নেই যা সেটি মনে রাখবে। এটিই M5/L23-এর "connectionless" ধারণার কোড-স্তরের
সরাসরি প্রতিফলন।
M7/L34-L35-এর মতোই, Pyodide-এর ব্রাউজার-স্যান্ডবক্স real bind()/recvfrom()/
sendto() চালাতে পারে না। তাই নিচের কোড সেল একটি MockUDPServer ব্যবহার করে, যা
উপরের real API-এর recvfrom/sendto গঠন অনুসরণ করে কিন্তু ভেতরে শুধু ইন-মেমরি
ডিকশনারি দিয়ে কাজ করে — কোনো real পোর্ট বাইন্ড হচ্ছে না।
# *** সিমুলেশন — real bind()/recvfrom()/sendto()-এর in-memory প্রতিরূপ ***
# real সার্ভার কোড হতো: s.bind(...); data, client_addr = s.recvfrom(1024)
# s.sendto(response, client_addr)
# real ক্লায়েন্ট কোড হতো: s.sendto(request, (host, port)); s.recvfrom(1024)
class MockUDPServer:
def __init__(self, address):
self.address = address
# UDP-তে কোনো persistent per-client কানেকশন নেই — শুধু কে কী পাঠিয়েছে তার একটি লগ
self.received_log = {}
print(f"[server] bind({address}) — connectionless: connect()/accept()-এর দরকার নেই")
def recvfrom(self, data, sender_addr):
self.received_log.setdefault(sender_addr, []).append(data)
print(f"[server] recvfrom() -> data={data!r}, sender={sender_addr}")
return data, sender_addr
def sendto(self, response, dest_addr):
print(f"[server] sendto({response!r}, {dest_addr})")
return response
def mock_udp_client(name, client_addr, server, request):
print(f"[client:{name}] sendto({request!r}, {server.address})")
data, sender_addr = server.recvfrom(request, client_addr)
response = f"ACK from server: {data}"
server.sendto(response, sender_addr) # ঠিক সেই প্রেরকের ঠিকানাতেই ফেরত পাঠানো হচ্ছে
print(f"[client:{name}] recvfrom() -> {response!r}")
return response
server = MockUDPServer(("0.0.0.0", 9500))
reply_a = mock_udp_client("client-A", ("192.168.1.10", 51000), server, "query-1")
print()
reply_b = mock_udp_client("client-B", ("192.168.1.11", 52000), server, "query-2")
print("\nসার্ভারের received_log (প্রতিটি প্রেরক-ঠিকানা অনুযায়ী):")
for addr, messages in server.received_log.items():
print(" ", addr, "->", messages)
print("\nclient-A রেসপন্স:", reply_a)
print("client-B রেসপন্স:", reply_b)
MockUDPServer-এর কোনো per-client persistent কানেকশন অবজেক্ট নেই (M7/L35-এর
MockTCPConnection-এর মতো কিছু নেই); বরং প্রতিটি recvfrom()-এর সাথে আসা
sender_addr দিয়েই সার্ভার জানতে পারছে কাকে রেসপন্স পাঠাতে হবে। received_log
ডিকশনারিটি দেখায় সার্ভার একই সময়ে একাধিক ভিন্ন প্রেরককে ঠিকানা দিয়ে আলাদা রাখছে।
UDP প্রোগ্রামিং TCP-এর চেয়ে সহজ (কোনো হ্যান্ডশেক/accept ধাপ নেই), কিন্তু এই সরলতার বিনিময়ে অ্যাপ্লিকেশনকে নিজে প্রতিটি বার্তার প্রেরক ঠিকানা ট্র্যাক করতে হয় — M5/L23-এর "connectionless" ট্রেড-অফ সরাসরি কোডের দায়িত্বে রূপান্তরিত হয়।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।
প্র ০১
UDP সার্ভারে কেন accept()-এর মতো কোনো ধাপ নেই, অথচ TCP সার্ভারে আছে?
accept() একটি TCP-নির্দিষ্ট ধাপ কারণ TCP-তে একটি কানেকশন "প্রতিষ্ঠিত" (established) হতে হয়
থ্রি-ওয়ে হ্যান্ডশেকের (M5/L24) মাধ্যমে — accept() সেই হ্যান্ডশেক সম্পূর্ণ করে একটি নতুন
কানেকশন অবজেক্ট তৈরি করে। UDP-তে কোনো হ্যান্ডশেক বা প্রতিষ্ঠিত কানেকশন-স্টেট নেই (M5/L23) — প্রতিটি
ডেটাগ্রাম স্বাধীন, তাই "গ্রহণ করার" কোনো আলাদা ধাপের প্রয়োজন নেই, সরাসরি recvfrom()-এই
ডেটা পাওয়া যায়।
প্র ০২
একটি UDP সার্ভার যদি নিজে sender_addr ট্র্যাক না রাখে (যেমন উপরের কোডে received_log না থাকলে), তাহলে ব্যবহারিকভাবে কী সমস্যা হতে পারে?
যদি সার্ভারকে একাধিক ক্লায়েন্টের সাথে চলমান কোনো কথোপকথন (যেমন কে কতগুলো বার্তা পাঠিয়েছে) মনে রাখতে হয়,
আর সে sender_addr ট্র্যাক না করে, তাহলে সে ভুল ক্লায়েন্টকে রেসপন্স পাঠাতে পারে বা একাধিক
ক্লায়েন্টের ডেটা গুলিয়ে ফেলতে পারে — যেহেতু UDP নিজে কোনো কানেকশন-স্তরের নিশ্চয়তা দেয় না, এই ট্র্যাকিং-এর
সম্পূর্ণ দায়িত্ব অ্যাপ্লিকেশন কোডের।
প্র ০৩ DNS (M6/L30) সাধারণত UDP ব্যবহার করে — উপরের কোড প্যাটার্নের সাথে এটি কীভাবে সামঞ্জস্যপূর্ণ?
একটি DNS সার্ভার একই সময়ে হাজার হাজার ভিন্ন ক্লায়েন্ট থেকে ছোট, স্বতন্ত্র প্রশ্ন (query) পায় — প্রতিটি
প্রশ্নের জন্য একটি TCP-এর মতো ভারী হ্যান্ডশেক/ডেডিকেটেড কানেকশন প্রয়োজন হলে তা অপ্রয়োজনীয় ওভারহেড হতো।
উপরের MockUDPServer প্যাটার্নের মতোই real DNS সার্ভার প্রতিটি ইনকামিং recvfrom()-এ
পাওয়া প্রেরক ঠিকানায় সরাসরি sendto() দিয়ে উত্তর পাঠায় — কোনো persistent কানেকশন-স্টেট ছাড়াই,
যা DNS-এর মতো ছোট, দ্রুত request-response প্যাটার্নের জন্য আদর্শ।
অনুশীলন
-
চিন্তা করুন: M7/L35-এর
MockTCPServerও এই পাঠেরMockUDPServer-এর গঠন পাশাপাশি রেখে তুলনা করুন — কোন ক্লাসে "per-client dedicated অবজেক্ট" আছে, আর কোনটিতে নেই?MockTCPServer-এ প্রতিটি ক্লায়েন্টের জন্যaccept()একটি আলাদাMockTCPConnectionঅবজেক্ট তৈরি করে (per-client dedicated অবস্থা)।MockUDPServer-এ এমন কোনো আলাদা অবজেক্ট নেই — একটিমাত্র সার্ভার অবজেক্টই সব ক্লায়েন্টের ডেটাগ্রাম হ্যান্ডল করে, শুধু ঠিকানা দিয়ে আলাদা করে চেনে। এই পার্থক্যটাই TCP-এর কানেকশন-ওরিয়েন্টেড বনাম UDP-এর connectionless প্রকৃতির কোড-স্তরের সবচেয়ে স্পষ্ট প্রতিফলন। -
পরীক্ষা করুন: উপরের কোড সেলে একটি তৃতীয় ক্লায়েন্ট (
client-C, ভিন্ন ঠিকানা ও প্রশ্নসহ) যোগ করেreceived_log-এ তিনটি ভিন্ন এন্ট্রি দেখা যায় কি না পরীক্ষা করুন।হ্যাঁ —
mock_udp_client("client-C", ("192.168.1.12", 53000), server, "query-2")-এর মতো একটি নতুন কল যোগ করলেreceived_log-এ তৃতীয় একটি ঠিকানা-চাবি যোগ হবে, প্রতিটি এন্ট্রি স্বাধীনভাবে থেকে যাবে — এটিই দেখায় সার্ভার কোনো নির্দিষ্ট সংখ্যক ক্লায়েন্টে সীমাবদ্ধ নয়, প্রতিটি নতুন ঠিকানার জন্য স্বয়ংক্রিয়ভাবে একটি নতুন এন্ট্রি তৈরি হয়।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৭টি পাঠ পরবর্তী পাঠ — ব্লকিং, নন-ব্লকিং ও থ্রেডেড সার্ভার প্যাটার্ন।
- Python কোর্স সঙ্গী কোর্স ডিকশনারি ও ডেটা স্ট্রাকচারের গভীরতর ব্যবহার শিখতে দেখুন।
- Cybersecurity & Ethical Hacking কোর্স সঙ্গী কোর্স UDP-ভিত্তিক আক্রমণ (যেমন স্পুফিং, ফ্লাডিং) কীভাবে কাজ করে তা শিখতে দেখুন।
- সব Courses দেখুন ABCL TECH C, C++, Python, Java, JavaScript, DSA, DBMS, Discrete Mathematics, System Design, Cybersecurity, Cloud Computing & DevOps ও Computer Networks — সব এক জায়গায়।