পাঠ ৪৮ · ৫৭-এর মধ্যে · মডিউল ১১
Home / Courses / Microprocessors, Embedded Systems & IoT / CoAP প্রোটোকল

CoAP প্রোটোকল

The CoAP protocol — REST for constrained devices
৯ মিনিট পড়া মধ্যম · Intermediate Python সিমুলেশনসহ সম্পূর্ণ বাংলায়

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

  • CoAP কেন UDP বেছে নেয় এবং MQTT-এর TCP-ভিত্তিক মডেলের থেকে কীভাবে আলাদা
  • Confirmable (CON) বনাম Non-confirmable (NON) মেসেজের পার্থক্য
  • মেসেজ আইডি, ACK, এবং এক্সপোনেনশিয়াল-ব্যাকঅফ রিট্রান্সমিশন কীভাবে কাজ করে — একটি জেনুইন কম্পিউটেড উদাহরণসহ
  • CoAP-এর REST-সদৃশ মেথড ও রেসপন্স কোড সম্পর্কে সংক্ষিপ্ত তথ্যভিত্তিক পরিচিতি

১ · কেন UDP, TCP নয়

CoAPConstrained Application Protocolখুবই সীমিত মেমরি/পাওয়ারের মাইক্রোকন্ট্রোলার-শ্রেণির ডিভাইসের জন্য ডিজাইন করা একটি হালকা-ওজনের REST-সদৃশ প্রোটোকল, যা UDP-এর ওপর চলে। এমন ডিভাইসের জন্য ডিজাইন করা হয়েছে যেখানে একটি পূর্ণাঙ্গ TCP কানেকশন (কানেকশন সেটআপ/টিয়ারডাউন, কানেকশন-স্টেট বজায় রাখা) বহন করার মতো মেমরিও নেই। CoAP তাই UDP-এর ওপর চলে — UDP-তে কোনো কানেকশন সেটআপ নেই, প্রতিটি প্যাকেট স্বাধীনভাবে পাঠানো হয় (TCP/UDP-এর সাধারণ পার্থক্য Computer Networks কোর্সে কভার করা হয়েছে)। কিন্তু UDP নিজে থেকে কোনো ডেলিভারি গ্যারান্টি দেয় না — একটি প্যাকেট পথে হারিয়ে যেতে পারে, আর UDP কখনো জানাবে না। CoAP তাই নিজস্ব একটি হালকা রিলায়েবিলিটি মেকানিজম তৈরি করেছে, যা নিচে সিমুলেট করা হয়েছে।

২ · Confirmable মেসেজ, মেসেজ আইডি ও ACK

একটি CONConfirmable messageএকটি CoAP মেসেজ যার জন্য প্রাপকের কাছ থেকে একটি ACK আবশ্যক — না পেলে প্রেরক নির্দিষ্ট নিয়মে পুনরায় পাঠায়। (Confirmable) মেসেজে একটি ইউনিক Message ID থাকে। রিসিভার সেই একই Message ID-সহ একটি ACK ফেরত পাঠায় (সাধারণত রেসপন্সটাই "পিগিব্যাক" করে ACK-এর সাথে একসাথে পাঠানো হয়)। প্রেরক যদি নির্দিষ্ট সময়ের মধ্যে ACK না পায়, সে একই Message ID দিয়ে পুনরায় পাঠায় — এবং প্রতিবার ব্যর্থ হলে অপেক্ষার সময় দ্বিগুণ করে (এক্সপোনেনশিয়াল ব্যাকঅফ), একটি সর্বোচ্চ রিট্রান্সমিশন সংখ্যা পর্যন্ত। এর বিপরীতে একটি Non-confirmable (NON) মেসেজে কোনো ACK আশাই করা হয় না — একেবারে "পাঠিয়ে ভুলে যাও" (fire-and-forget), ঘন ঘন পাঠানো নন-ক্রিটিক্যাল স্ট্রিমিং ডেটার জন্য উপযুক্ত।

ক্লায়েন্ট CoAP সার্ভার ① CON GET /temperature (MID=1) ✗ ACK আসেনি — ২s টাইমআউট (সিমুলেটেড লস) ② CON GET রিট্রান্সমিট (MID=1, ব্যাকঅফ ৪s পর) ③ ACK 2.05 Content (MID=1) ✓
প্রথম CON মেসেজের ACK হারিয়ে গেলে ক্লায়েন্ট একই Message ID দিয়ে এক্সপোনেনশিয়াল ব্যাকঅফে পুনরায় পাঠায়, যতক্ষণ না ACK আসে বা সর্বোচ্চ রিট্রাই সংখ্যা শেষ হয়।

৩ · একটি সত্যিকারের রিট্রান্সমিশন সিমুলেশন

নিচের কোডে একটি সরল CoapServer (কিছু রিসোর্স ধারণ করে) এবং একটি simulate_con_request() ফাংশন আছে যা CON রিকোয়েস্ট পাঠানোর প্রকৃত রিট্রাই লজিক সিমুলেট করে — কতবার মেসেজ হারালো, কত সেকেন্ড অপেক্ষা করা হলো (ব্যাকঅফ দ্বিগুণ করে), এবং শেষ পর্যন্ত সফল হলো নাকি সর্বোচ্চ রিট্রান্সমিশন সংখ্যা (RFC 7252-এ সাধারণত বহুল-উল্লেখিত ডিফল্ট MAX_RETRANSMIT = 4) অতিক্রম করে ব্যর্থ হলো — সবকিছুই কোডের ভেতর থেকে জেনুইনভাবে গণনা করা, কোথাও হার্ডকোড করা ফলাফল নেই।

Python
class CoapServer:
    """একটি সরলীকৃত CoAP সার্ভার -- কিছু রিসোর্স পাথ ধরে রাখে।"""

    def __init__(self):
        self.resources = {
            "/sensors/temperature": 24.5,
            "/sensors/humidity": 61,
        }
        self._next_mid = 1

    def new_message_id(self):
        mid = self._next_mid
        self._next_mid += 1
        return mid


def simulate_con_request(server, path, ack_lost_count=0, ack_timeout=2.0, max_retransmit=4):
    """একটি CON GET রিকোয়েস্ট সিমুলেট করে। ack_lost_count = প্রথম কতগুলো
    চেষ্টার ACK হারিয়ে যাবে (সিমুলেটেড নেটওয়ার্ক লস)। প্রতিটি লসের পর
    টাইমআউট এক্সপোনেনশিয়ালি দ্বিগুণ হয় (RFC 7252-এর ব্যাকঅফ নিয়ম)।"""
    mid = server.new_message_id()
    log = [f"ক্লায়েন্ট -> সার্ভার: CON GET {path} (Message-ID={mid})"]
    timeout = ack_timeout
    total_wait = 0.0

    for attempt in range(max_retransmit + 1):  # attempt 0 = আসল পাঠানো, ১..৪ = রিট্রান্সমিট
        if attempt < ack_lost_count:
            log.append(f"  চেষ্টা #{attempt + 1}: {timeout:.1f}s অপেক্ষার পর কোনো ACK এলো না (হারিয়ে গেছে, সিমুলেটেড)")
            total_wait += timeout
            timeout *= 2  # এক্সপোনেনশিয়াল ব্যাকঅফ
            if attempt < max_retransmit:
                log.append(f"  ক্লায়েন্ট পুনরায় পাঠালো: CON GET {path} (Message-ID={mid}, চেষ্টা #{attempt + 2})")
            continue

        value = server.resources.get(path)
        if value is not None:
            log.append(f"  চেষ্টা #{attempt + 1}: ACK 2.05 Content প্রাপ্ত (Message-ID={mid}), payload={value!r}")
            return {"success": True, "attempts": attempt + 1, "total_wait_s": total_wait, "log": log}
        else:
            log.append(f"  চেষ্টা #{attempt + 1}: রিসোর্স পাওয়া যায়নি -> 4.04 Not Found (Message-ID={mid})")
            return {"success": False, "attempts": attempt + 1, "total_wait_s": total_wait, "log": log}

    log.append(f"MAX_RETRANSMIT ({max_retransmit}) অতিক্রান্ত, মোট অপেক্ষা {total_wait:.1f}s -> অনুরোধ ব্যর্থ")
    return {"success": False, "attempts": max_retransmit + 1, "total_wait_s": total_wait, "log": log}


server = CoapServer()

print("== দৃশ্য ১: কোনো লস নেই, সাথে সাথে ACK ==")
result1 = simulate_con_request(server, "/sensors/temperature", ack_lost_count=0)
for line in result1["log"]:
    print(line)
print(f"  ফলাফল: success={result1['success']}, মোট চেষ্টা={result1['attempts']}, মোট অপেক্ষা={result1['total_wait_s']}s")

print("\n== দৃশ্য ২: প্রথম ২টি ACK হারিয়ে যায়, তৃতীয় চেষ্টায় সফল ==")
result2 = simulate_con_request(server, "/sensors/temperature", ack_lost_count=2)
for line in result2["log"]:
    print(line)
print(f"  ফলাফল: success={result2['success']}, মোট চেষ্টা={result2['attempts']}, মোট অপেক্ষা={result2['total_wait_s']}s")

print("\n== দৃশ্য ৩: সবগুলো চেষ্টাই হারিয়ে যায় -> MAX_RETRANSMIT ছাড়িয়ে ব্যর্থ ==")
result3 = simulate_con_request(server, "/sensors/temperature", ack_lost_count=10)
for line in result3["log"]:
    print(line)
print(f"  ফলাফল: success={result3['success']}, মোট চেষ্টা={result3['attempts']}, মোট অপেক্ষা={result3['total_wait_s']}s")

    
দৃশ্য ৩-এ লক্ষ্য করুন — total_wait_s জেনুইনভাবে $2 + 4 + 8 + 16 + 32 = 62.0$ সেকেন্ড হিসেবে বের হয় (৫টি চেষ্টা: প্রথম পাঠানো + ৪টি রিট্রান্সমিট, প্রতিবার আগেরটির দ্বিগুণ অপেক্ষা) — এটি কোডের ভেতরের লুপ থেকেই বাস্তবে যোগ হয়ে আসছে, কোথাও আগে থেকে হিসাব করে বসিয়ে দেওয়া হয়নি। এটাই দেখায় কেন একটি সত্যিই দুর্বল সংযোগে CoAP-এর ব্যাকঅফ দ্রুতই দীর্ঘ অপেক্ষায় পরিণত হতে পারে।

৪ · REST-সদৃশ মেথড ও রেসপন্স কোড

CoAP-এর মেথডগুলো HTTP-এর সাথে ইচ্ছাকৃতভাবে মিল রেখে ডিজাইন করা — GET, POST, PUT, DELETE — এবং রেসপন্স কোডগুলোও HTTP-এর স্ট্যাটাস কোডের অনুরূপ প্যাটার্নে তৈরি (যেমন উপরের কোডে দেখা 2.05 Content ও 4.04 Not Found, HTTP-এর 2xx/4xx-এর সাথে ধারণাগতভাবে সমতুল্য)। এই পরিচিতিই CoAP-কে "সীমিত ডিভাইসের জন্য REST" বলা হয় — HTTP-এর অভ্যস্ত মানসিক মডেলটাই বজায় থাকে, শুধু ট্রান্সপোর্ট ও ফ্রেমিং অনেক হালকা।

মূল কথা · Key takeaway

CoAP = UDP-এর ওপর REST-সদৃশ মডেল + একটি হালকা নিজস্ব রিলায়েবিলিটি লেয়ার (Message ID + CON/ACK + এক্সপোনেনশিয়াল-ব্যাকঅফ রিট্রান্সমিশন)। MQTT-এর সাথে মূল পার্থক্য: MQTT একটি পার্সিস্টেন্ট TCP কানেকশনে pub/sub মডেল চালায়, CoAP প্রতিটি request-কে স্বাধীন UDP প্যাকেট হিসেবে পাঠায় — L49-এ এই দুটোকে (এবং HTTP-কে) পাশাপাশি রেখে সরাসরি তুলনা করা হবে।

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

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

প্র ০১ উপরের কোডে যদি ack_lost_count=5 দিয়ে (অর্থাৎ max_retransmit=4-এর সমান বা বেশি) কল করা হয়, তাহলে ফলাফল দৃশ্য ৩-এর মতোই হবে কি?

হ্যাঁ। লুপটি মোট max_retransmit + 1 = 5টি চেষ্টা করে (attempt 0 থেকে 4)। যেহেতু ack_lost_count=5 মানে সবগুলো attempt (0,1,2,3,4)-ই "হারিয়ে গেছে" শর্ত পূরণ করে (attempt < 5 সবসময় সত্য এই রেঞ্জে), তাই লুপ শেষ হয়ে যাবে সফল ACK ছাড়াই এবং একই "MAX_RETRANSMIT অতিক্রান্ত" ফলাফল আসবে — ack_lost_count-এর যেকোনো মান ৫ বা তার বেশি হলে একই আচরণ হবে, কারণ সর্বোচ্চ ৫টি চেষ্টার বেশি কোডটি কখনোই করে না।

প্র ০২ Non-confirmable (NON) মেসেজে কোনো Message-ID-ভিত্তিক রিট্রান্সমিশন লজিক নেই কেন সেটি এখনও একটি বৈধ ডিজাইন সিদ্ধান্ত?

কারণ কিছু ডেটার জন্য "সর্বশেষ মান গুরুত্বপূর্ণ, প্রতিটি পুরনো মান নয়" — যেমন একটি সেন্সর প্রতি সেকেন্ডে একটি রিডিং পাঠালে, একটি রিডিং হারিয়ে গেলেও এক সেকেন্ড পরেই পরেরটি আসছে। এই ক্ষেত্রে রিট্রান্সমিশনের অতিরিক্ত নেটওয়ার্ক ও পাওয়ার খরচ করার কোনো লাভ নেই — QoS 0-এর পেছনের যুক্তির মতোই (L47), NON মেসেজও একই নীতিতে "at most once, কম ওভারহেড" বেছে নেয়।

প্র ০৩ CoAP কেন নতুন Message-ID না দিয়ে রিট্রান্সমিশনে একই Message-ID পুনরায় ব্যবহার করে?

যাতে রিসিভার বুঝতে পারে এটি একটি নতুন আলাদা রিকোয়েস্ট নয়, বরং আগের একটি রিকোয়েস্টেরই পুনরাবৃত্তি — যদি রিসিভার আসলে প্রথম কপিটি পেয়েও থাকে (আর শুধু তার ACK-টাই হারিয়ে গিয়ে থাকে), তাহলে সে একই কাজ দ্বিতীয়বার না করে শুধু আবার ACK পাঠিয়ে দিতে পারে — একই Message-ID এই ডুপ্লিকেট-শনাক্তকরণ সম্ভব করে তোলে।

অনুশীলন

  1. চিন্তা করুন: ack_timeout=1.0 দিয়ে (২.০-এর বদলে) দৃশ্য ২ (দুটি লস, তৃতীয় চেষ্টায় সফল) আবার চালালে total_wait_s কত হবে বলে আপনার ধারণা?

    ব্যাকঅফ এখনও দ্বিগুণ হারে বাড়বে, শুধু শুরুর মান আলাদা: প্রথম লস $1.0$s, দ্বিতীয় লস $2.0$s — মোট $1.0 + 2.0 = 3.0$ সেকেন্ড। মূল দৃশ্য ২-তে ($2.0 + 4.0 = 6.0$s) এর তুলনায় ঠিক অর্ধেক, কারণ প্রতিটি ব্যাকঅফ ধাপ শুরুর ack_timeout-এর সমানুপাতিক।

  2. পরীক্ষা করুন: উপরের কোড সেলে simulate_con_request-কে "/sensors/pressure" (যা server.resources-এ নেই) পাথ দিয়ে কল করুন এবং Run চেপে দেখুন কী ফলাফল আসে।

    যেহেতু "/sensors/pressure" রিসোর্স ডিকশনারিতে নেই, server.resources.get(path) None রিটার্ন করবে, আর কোডটি "4.04 Not Found" লগ করে success=False, attempts=1 রিটার্ন করবে — এটি রিট্রান্সমিশন লুপের একটি ভিন্ন শাখা, নেটওয়ার্ক লস নয় বরং সার্ভারের প্রকৃত "এই রিসোর্স নেই" উত্তর।

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

  • কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৭টি পাঠ মাইক্রোপ্রসেসর আর্কিটেকচার, এমবেডেড C, GPIO, টাইমার/PWM/ADC, সিরিয়াল প্রোটোকল, RTOS, সেন্সর/অ্যাকচুয়েটর, IoT আর্কিটেকচার, ওয়্যারলেস প্রোটোকল, MQTT/CoAP ও IoT সিকিউরিটি — বাকি পাঠগুলো শীঘ্রই যুক্ত হবে।
  • Computer Networks কোর্স সহোদর কোর্স TCP বনাম UDP-এর সাধারণ পার্থক্য সেই কোর্সেই কভার করা হয়েছে — এই পাঠ সরাসরি CoAP-এর UDP-ভিত্তিক ডিজাইন সিদ্ধান্তে যায়।
  • সব Courses দেখুন ABCL TECH C, C++, Python, Java, JavaScript, DSA, DBMS, Discrete Mathematics, System Design, Cybersecurity, Cloud Computing & DevOps, Computer Networks, Operating Systems, Computer Architecture, Design and Analysis of Algorithms ও আরও অনেক কোর্স — সব এক জায়গায়।
আগের পাঠ
MQTT QoS ও বাস্তব-জীবনের প্যাটার্ন