পাঠ ২৪ · ৫৭-এর মধ্যে · মডিউল ৫
Home / Courses / Computer Networks / TCP হ্যান্ডশেক

TCP — কানেকশন ও থ্রি-ওয়ে হ্যান্ডশেক

TCP — connection & the three-way handshake
৯ মিনিট পড়া মধ্যবর্তী · Intermediate Python কোডসহ সম্পূর্ণ বাংলায়

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

  • TCP কানেকশন-ওরিয়েন্টেড হওয়ার অর্থ ও কারণ
  • থ্রি-ওয়ে হ্যান্ডশেকের প্রতিটি ধাপ কী করে
  • কেন ঠিক ৩টি বার্তা দরকার — ২টি কেন যথেষ্ট নয়
  • Python দিয়ে একটি সত্যিকারের স্টেট মেশিন — ক্লায়েন্ট ও সার্ভার উভয় পাশের

১ · TCP কানেকশন-ওরিয়েন্টেড কেন

L23-এ আমরা দেখেছি UDP কানেকশনলেস — কোনো প্রস্তুতি ছাড়াই ডেটাগ্রাম পাঠানো যায়। TCPTransmission Control Protocolএকটি কানেকশন-ওরিয়েন্টেড, নির্ভরযোগ্য ট্রান্সপোর্ট-লেয়ার প্রোটোকল — ডেটা আদান-প্রদানের আগে একটি হ্যান্ডশেকের মাধ্যমে কানেকশন-স্টেট স্থাপন করে। (Transmission Control Protocol) সম্পূর্ণ বিপরীত পথ নেয় — কোনো ডেটা পাঠানোর আগে ক্লায়েন্ট ও সার্ভার একে অপরের সাথে একটি শেয়ার্ড কানেকশন-স্টেট স্থাপন করে নেয়। এই স্টেট স্থাপনের প্রক্রিয়াটিই থ্রি-ওয়ে হ্যান্ডশেকThree-Way HandshakeTCP কানেকশন স্থাপনের তিন-ধাপের প্রক্রিয়া — SYN, SYN-ACK, ACK — যার পরে উভয় পক্ষ ডেটা আদান-প্রদানের জন্য প্রস্তুত (ESTABLISHED) হয়।।

২ · থ্রি-ওয়ে হ্যান্ডশেকের তিনটি ধাপ

  1. ধাপ ১ — SYN: ক্লায়েন্ট একটি SYN (synchronize) বার্তা পাঠায়, যাতে তার নিজস্ব প্রাথমিক সিকোয়েন্স নাম্বার (ISN) প্রস্তাব করা থাকে।
  2. ধাপ ২ — SYN-ACK: সার্ভার ক্লায়েন্টের SYN অ্যাকনলেজ করে এবং নিজের একটি SYN পাঠায় (নিজের ISN সহ) — একই বার্তায় দুটি কাজ একসাথে হয়।
  3. ধাপ ৩ — ACK: ক্লায়েন্ট সার্ভারের SYN অ্যাকনলেজ করে একটি ACK পাঠায় — এরপর উভয় পক্ষ ডেটা পাঠাতে প্রস্তুত।
কেন ঠিক ৩টি ধাপ — ২টি নয়

২টি ধাপ (শুধু SYN ও SYN-ACK) দিয়ে শুধু একদিকের reachability প্রমাণিত হয় — সার্ভার জানে ক্লায়েন্ট তাকে পাঠাতে পেরেছে, কিন্তু ক্লায়েন্ট কি সার্ভারের কাছ থেকে সত্যিই কিছু পেয়েছে তা সার্ভার এখনও নিশ্চিত নয়। ৩য় ধাপ (চূড়ান্ত ACK) দরকার যাতে সার্ভারও নিশ্চিত হয় যে ক্লায়েন্ট তার SYN-ACK সফলভাবে পেয়েছে — তবেই উভয় পক্ষ সংযোগের জন্য রিসোর্স বরাদ্দ করার আগে বাইডিরেকশনাল reachability নিশ্চিত হয়।

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

ক্লায়েন্ট সার্ভার (LISTEN) ধাপ ১ — SYN (seq=x) · ক্লায়েন্ট: SYN_SENT ধাপ ২ — SYN-ACK (seq=y, ack=x+1) · সার্ভার: SYN_RCVD ধাপ ৩ — ACK (ack=y+1) · উভয় পক্ষ: ESTABLISHED
SYN → SYN-ACK → ACK — তিনটি বার্তার পরেই ক্লায়েন্ট ও সার্ভার উভয়ে ESTABLISHED অবস্থায় পৌঁছায়।
Python
# TCP থ্রি-ওয়ে হ্যান্ডশেকের একটি সত্যিকারের স্টেট মেশিন সিমুলেশন — টয় ইন-মেমরি অবজেক্ট, কোনো প্রকৃত সকেট নয়
import random

class TCPEndpoint:
    def __init__(self, name, role, initial_seq):
        self.name = name
        self.role = role  # "client" অথবা "server"
        self.seq = initial_seq
        self.state = "CLOSED"

    def listen(self):
        assert self.role == "server" and self.state == "CLOSED"
        self.state = "LISTEN"

    def send_syn(self):
        assert self.role == "client" and self.state == "CLOSED"
        self.state = "SYN_SENT"
        return {"flags": "SYN", "seq": self.seq}

    def receive_syn(self, msg):
        assert self.role == "server" and self.state == "LISTEN"
        self.state = "SYN_RCVD"
        return {"flags": "SYN-ACK", "seq": self.seq, "ack": msg["seq"] + 1}

    def receive_synack(self, msg):
        assert self.role == "client" and self.state == "SYN_SENT"
        self.state = "ESTABLISHED"
        return {"flags": "ACK", "seq": msg["ack"], "ack": msg["seq"] + 1}

    def receive_ack(self, msg):
        assert self.role == "server" and self.state == "SYN_RCVD"
        self.state = "ESTABLISHED"


random.seed(42)
client = TCPEndpoint("ক্লায়েন্ট", "client", initial_seq=random.randint(1000, 9000))
server = TCPEndpoint("সার্ভার", "server", initial_seq=random.randint(1000, 9000))

print(f"শুরুতে -> ক্লায়েন্ট: {client.state}, সার্ভার: {server.state}")

server.listen()
print(f"সার্ভার listen() ডাকার পর -> সার্ভার: {server.state}")

# ধাপ ১: ক্লায়েন্ট SYN পাঠায়
syn_msg = client.send_syn()
print(f"\nধাপ ১ — ক্লায়েন্ট পাঠাল {syn_msg} | ক্লায়েন্ট এখন: {client.state}")
assert client.state == "SYN_SENT" and server.state == "LISTEN"

# ধাপ ২: সার্ভার SYN পেয়ে SYN-ACK পাঠায়
synack_msg = server.receive_syn(syn_msg)
print(f"ধাপ ২ — সার্ভার পাঠাল {synack_msg} | সার্ভার এখন: {server.state}")
assert server.state == "SYN_RCVD" and client.state == "SYN_SENT"

# মধ্যবর্তী পরীক্ষা — এখনও ২ বার্তা হয়েছে, কেউই ESTABLISHED নয়
print(f"\n(পরীক্ষা) ২ বার্তার পর, ৩য় বার্তার আগে -> ক্লায়েন্ট: {client.state}, সার্ভার: {server.state}")
assert client.state != "ESTABLISHED" and server.state != "ESTABLISHED"

# ধাপ ৩: ক্লায়েন্ট SYN-ACK পেয়ে ACK পাঠায়
ack_msg = client.receive_synack(synack_msg)
print(f"\nধাপ ৩ — ক্লায়েন্ট পাঠাল {ack_msg} | ক্লায়েন্ট এখন: {client.state}")
assert client.state == "ESTABLISHED"

# পরীক্ষা — এই মুহূর্তে ক্লায়েন্ট ESTABLISHED, কিন্তু সার্ভার এখনও চূড়ান্ত ACK পায়নি
print(f"(পরীক্ষা) ক্লায়েন্ট ESTABLISHED, কিন্তু সার্ভার এখনও: {server.state}")
assert server.state == "SYN_RCVD"

# সার্ভার চূড়ান্ত ACK গ্রহণ করে
server.receive_ack(ack_msg)
print(f"\nসার্ভার চূড়ান্ত ACK পাওয়ার পর -> সার্ভার এখন: {server.state}")

print(f"\nহ্যান্ডশেক শেষে -> ক্লায়েন্ট: {client.state}, সার্ভার: {server.state}")
print("উভয় পক্ষ ESTABLISHED?", client.state == "ESTABLISHED" and server.state == "ESTABLISHED")
assert client.state == "ESTABLISHED" and server.state == "ESTABLISHED"

    
লক্ষ্য করুন — ক্লায়েন্ট নিজে ESTABLISHED-এ পৌঁছায় ২য় বার্তা (SYN-ACK) পাওয়ার সাথে সাথেই, কিন্তু সার্ভার ESTABLISHED-এ পৌঁছায় শুধুমাত্র ৩য় বার্তা (চূড়ান্ত ACK) পাওয়ার পরে। তাই "উভয় পক্ষ ESTABLISHED" হতে সবসময় পুরো ৩টি বার্তাই লাগে — কোডের অ্যাসার্শনগুলো এই ঠিক ক্রমটিই যাচাই করে, ধাপে ধাপে।
মূল কথা · Key takeaway

থ্রি-ওয়ে হ্যান্ডশেক শুধু একটি ফর্মালিটি নয় — এটি একটি মৌলিক নিশ্চয়তা তৈরি করে: সংযোগ ESTABLISHED হওয়ার আগে, ক্লায়েন্ট ও সার্ভার উভয়েই প্রমাণ পায় যে তারা একে অপরকে পাঠাতে ও তার থেকে গ্রহণ করতে সক্ষম। L25-এ আমরা দেখব এই হ্যান্ডশেকে স্থাপিত সিকোয়েন্স নাম্বারগুলোই TCP-এর নির্ভরযোগ্য ডেটা ডেলিভারির ভিত্তি।

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

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

প্র ০১ যদি ৩য় ACK বার্তাটি নেটওয়ার্কে হারিয়ে যায়, তাহলে কী হবে বলে মনে হয়?

সার্ভার SYN_RCVD অবস্থাতেই আটকে থাকবে (ESTABLISHED হবে না) এবং একটি টাইমআউটের পর তার SYN-ACK আবার রিট্রান্সমিট করবে (ধরে নিয়ে যে হয়তো সেটিই হারিয়েছিল)। ক্লায়েন্ট ততক্ষণে ESTABLISHED হয়ে গেছে এবং হয়তো ডেটা পাঠানো শুরু করতে চাইবে — বাস্তব TCP-তে এই প্রান্তিক পরিস্থিতিগুলো সামলাতে অতিরিক্ত নিয়ম থাকে (যেমন ডেটাসহ ACK পুনরায় পাঠানো), যা এই পরিচায়ক পাঠের সরলীকৃত পরিসরের বাইরে।

প্র ০২ ক্লায়েন্ট ও সার্ভার উভয়েই নিজস্ব আলাদা ইনিশিয়াল সিকোয়েন্স নাম্বার (ISN) কেন বেছে নেয় — একটি শেয়ার্ড নাম্বার দিয়ে শুরু করলে সমস্যা কী হতো?

TCP আসলে ফুল-ডুপ্লেক্স — উভয় দিকেই স্বাধীনভাবে ডেটা প্রবাহিত হতে পারে, তাই প্রতিটি দিকের নিজস্ব স্বাধীন সিকোয়েন্স নাম্বারিং দরকার। এছাড়া, প্রতিবার এলোমেলো/অনির্ধারিত ISN বেছে নেওয়া একটি নিরাপত্তা ব্যবস্থাও বটে — পুরনো, ইতিমধ্যে বন্ধ হওয়া সংযোগের ডেটা যেন নতুন সংযোগে ভুলবশত গ্রহণযোগ্য না হয়ে যায়, এবং কেউ যেন সহজে সিকোয়েন্স নাম্বার অনুমান করে সংযোগ hijack করতে না পারে (cybersecurity কোর্সে আরও)।

প্র ০৩ সংযোগ বন্ধ করতে TCP কেন থ্রি-ওয়ে নয়, ফোর-ওয়ে প্রক্রিয়া ব্যবহার করে?

স্থাপনের সময় উভয় পক্ষ একই মুহূর্তে প্রস্তুত থাকে বলে SYN-ACK একটি বার্তায় দুটি কাজ (অ্যাকনলেজ + নিজের SYN) একসাথে করতে পারে। কিন্তু বন্ধ করার সময়, একটি পক্ষ পাঠানো বন্ধ করতে চাইলেও অন্য পক্ষের তখনও পাঠানোর মতো ডেটা বাকি থাকতে পারে — তাই প্রতিটি দিক স্বাধীনভাবে বন্ধ হয় (নিজস্ব FIN, নিজস্ব ACK), ফলে সাধারণত চারটি আলাদা বার্তা লাগে (যদিও কিছু ক্ষেত্রে এগুলো একত্রিত হতে পারে)।

অনুশীলন

  1. চিন্তা করুন: উপরের কোডে server.receive_ack(ack_msg) কল করার আগে চেষ্টা করুন print(client.state == "ESTABLISHED" and server.state == "ESTABLISHED") — ফলাফল কী হবে এবং কেন, আগে অনুমান করুন, তারপর কোড চালিয়ে যাচাই করুন।

    ফলাফল হবে False, কারণ যদিও ক্লায়েন্ট ইতিমধ্যে ESTABLISHED (ধাপ ৩ পাঠানোর পরপরই), সার্ভার তখনও SYN_RCVD অবস্থায় — সার্ভারের চূড়ান্ত ACK গ্রহণ করার আগ পর্যন্ত এটি ESTABLISHED হয় না। এটিই প্রমাণ করে কেন "উভয় পক্ষ ESTABLISHED" নিশ্চিত হতে পুরো ৩টি বার্তার সম্পূর্ণ আদান-প্রদান প্রয়োজন।

  2. পরীক্ষা করুন: TCPEndpoint.receive_syn-এর assert self.role == "server" and self.state == "LISTEN" লাইনটি সরিয়ে ফেলে, তারপর server.receive_syn(syn_msg)-কে server.listen() কল করার আগেই ডাকার চেষ্টা করুন — কী হয়?

    অ্যাসার্শন সরিয়ে ফেললে কোডটি চুপচাপ CLOSED অবস্থা থেকেই সরাসরি SYN_RCVD-এ চলে যাবে — যা বাস্তব TCP আচরণের লঙ্ঘন (একটি সার্ভার প্রথমে অবশ্যই LISTEN করতে হবে সংযোগ গ্রহণের জন্য প্রস্তুত হওয়ার আগে)। অ্যাসার্শনগুলো ঠিক এই ধরনের অবৈধ স্টেট-ট্রানজিশন ধরার জন্যই আছে।

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

আগের পাঠ
L23 · UDP — কানেকশনলেস ট্রান্সপোর্ট