1.10
TCP-এর গভীরে: Header, Flow Control ও Congestion Control
Header-এর প্রতিটা Field, Loss সারানো আর SACK, Receive Window, Slow Start আর AIMD, CUBIC ও BBR, Bandwidth-delay Product, Nagle আর Delayed ACK।
45 মিনিট পড়া + Animation
টিপস: ডটেড-আন্ডারলাইন করা যেকোনো শব্দে ট্যাপ বা Hover করলে তার অর্থ দেখবে।
এই Lesson শেষে তুমি পারবে
- TCP Header আর UDP Header-এর প্রতিটা Field পড়তে, প্রতিটা কী কাজে লাগে আর তাতে অদ্ভুত মান থাকলে তার মানে কী, সেটা বলতে।
- TCP কীভাবে Loss ধরে আর সারায় তা বোঝাতে: Sequence ও Acknowledgment Number, RTT মাপা আর Retransmission Timeout, Duplicate ACK, Fast Retransmit ও Fast Recovery, SACK।
- Receive Window, Zero Window, Window Probe আর Window Scaling দিয়ে Flow Control বোঝাতে।
- Congestion Control বোঝাতে: cwnd, Slow Start (সঠিকভাবে), ssthresh, Congestion Avoidance, AIMD, আর Reno, CUBIC ও BBR-এর পার্থক্য; ECN কী যোগ করে তা বলতে।
- Bandwidth-delay Product হিসাব করতে, আর আগেই বলতে পারতে কখন একটা Connection একটা Link ভরাতে পারবে না।
- Nagle আর Delayed ACK মিলে আটকে যাওয়া চিনতে, আর কখন
TCP_NODELAY, TCP Fast Open আর Keepalive লাগবে তা জানতে। - এসব থেকে System Design-এর সিদ্ধান্ত নিতে: Connection আবার ব্যবহার, Buffer Tune করা, Server-এ BBR, আর QUIC কেন Transport-কে User Space-এ নিয়ে গেল।
#এই Lesson কোথায় বসে
Lesson ১.৯-এ আমরা TCP-কে বাইরে থেকে দেখেছি: Handshake, Sequence Number, একটা Segment হারানো, Connection বন্ধ করা, TLS আর QUIC। এবার বাক্সটা খুলব। এখানের সবকিছুই Transport Layer-এর (Layer 4) ব্যাপার। এর কাজ হলো Process থেকে Process-এ (End-to-end) Data পৌঁছানো: এক মেশিনের একটা Program থেকে আরেক মেশিনের ঠিক Program-টার কাছে।
Transport Layer আটটা কাজ করে। TCP আটটাই করে; UDP শুধু প্রথম তিনটা, সবচেয়ে সাদামাটা ভাবে:
| কাজ | মানে কী | TCP কী দিয়ে করে |
|---|---|---|
| Port Addressing | মেশিনের ঠিক Program-টা বেছে নেওয়া | ১৬ bit Source আর Destination Port (০–৬৫৫৩৫) |
| Multiplexing / Demultiplexing | অনেক Program একটাই Network Interface ভাগ করে | প্রতি Connection-এর ৪-tuple (দুটো IP, দুটো Port) |
| Segmentation | বড় Stream-কে Packet-এ আঁটার মতো টুকরো করা | সর্বোচ্চ এক MSS-এর Segment |
| Reassembly | টুকরোগুলো আবার ক্রমমতো জোড়া | Sequence Number |
| Connection Control | কথোপকথন শুরু, চালানো আর শেষ করা | SYN, FIN আর RST |
| Error Control | নষ্ট বা হারানো Data ধরা আর সারানো | Checksum, ACK, আবার পাঠানো |
| Flow Control | Receiver-কে ডুবিয়ে না দেওয়া | Receive Window |
| Congestion Control | Network-কে ডুবিয়ে না দেওয়া | Congestion Window |
Data-র টুকরোর নাম Protocol ভেদে আলাদা: TCP-তে Segment, UDP-তে Datagram। TCP-র প্রতিশ্রুতি হলো একটা Full-duplex, নির্ভরযোগ্য, ক্রমমতো Byte Stream: দুই পাশ একসাথে পাঠাতে পারে, কিছু হারায় না, আগে-পরে হয় না, আর কোনো বার্তার সীমানা নেই (১০০ Byte-এর দুটো Write অন্য পাশে ২০০ Byte-এর একটা Read হয়ে আসতে পারে)।
#TCP বনাম UDP, আরেকবার
| অগ্রাধিকার | TCP নাও যখন… | UDP নাও যখন… |
|---|---|---|
| মূল চিন্তা | প্রতিটা Bit নির্ভুল হতেই হবে | Real-time গতি আর কম Latency; সামান্য Loss চলে |
| কোথায় লাগে | Web Browsing (HTTP/1.1 আর HTTP/2), Email (SMTP, IMAP), File Transfer (FTP), Remote Access (SSH), Database | Online Game, Voice ও Video Call (VoIP, Zoom, Google Meet), DNS Lookup, DHCP, QUIC (HTTP/3) |
| কারণ | Web Page বা Download করা File-এর একটা Byte হারালেও File নষ্ট | Game বা Call-এ এক-দুটো Frame বাদ গেলে কেউ টের পায় না, কিন্তু Lag থাকলে Service ব্যবহারই করা যায় না |
#TCP Header, একটা একটা Field ধরে
প্রতিটা TCP Segment শুরু হয় ২০ Byte আর সাথে ০–৪০ Byte Option দিয়ে, মানে ২০–৬০ Byte-এর Header দিয়ে। একে ৩২ bit-এর সারিতে আঁকা হয়:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-------------------------------+-------------------------------+
| Source Port | Destination Port |
+-------------------------------+-------------------------------+
| Sequence Number |
+---------------------------------------------------------------+
| Acknowledgment Number |
+-------+-------+-+-+-+-+-+-+-+-+-------------------------------+
| Data |Reser- |C|E|U|A|P|R|S|F| |
|Offset | ved |W|C|R|C|S|S|Y|I| Window |
| | |R|E|G|K|H|T|N|N| |
+-------+-------+-+-+-+-+-+-+-+-+-------------------------------+
| Checksum | Urgent Pointer |
+-------------------------------+-------------------------------+
| Options (0–40 bytes) + padding |
+---------------------------------------------------------------+
| Data (payload) |
+---------------------------------------------------------------+
| Field | Bit | কী কাজ করে |
|---|---|---|
| Source Port | ১৬ | পাঠানো Program (প্রায়ই ৫৩০০০-এর মতো Ephemeral Port) |
| Destination Port | ১৬ | গ্রহণকারী Program (HTTPS-এর জন্য ৪৪৩) |
| Sequence Number | ৩২ | এই Segment-এর প্রথম Data Byte-এর নম্বর। TCP Byte গোনে, Packet না; প্রথম নম্বরটা এলোমেলো একটা ISN |
| Acknowledgment Number | ৩২ | এই Segment যে পাঠাচ্ছে, সে পরের কোন Byte চায়; এর আগের সব পৌঁছে গেছে। শুধু ACK চালু থাকলে কার্যকর |
| Data Offset | ৪ | ৪ Byte-এর Word হিসেবে Header-এর দৈর্ঘ্য: ৫ = ২০ Byte, ১৫ = ৬০ Byte (সর্বোচ্চ) |
| Reserved | ৪ | শূন্য থাকতেই হবে |
| Flag | ৮ | CWR, ECE, URG, ACK, PSH, RST, SYN, FIN (নিচের Table) |
| Window | ১৬ | Receive Window: এই পাশ আর কত Byte নিতে পারবে (Flow Control), Window Scale দিয়ে গুণ করে |
| Checksum | ১৬ | Header, Data আর দুটো IP Address সহ একটা Pseudo-header-এর ওপর ভুল ধরার হিসাব |
| Urgent Pointer | ১৬ | জরুরি Data কোথায় শেষ; শুধু URG চালু থাকলে অর্থবহ |
| Option | ০–৩২০ | MSS, Window Scale, SACK, Timestamp, Fast Open…, ৪ Byte-এর গুণিতকে Padding করা |
TCP আর UDP Header, একটা একটা Field ধরে
প্রতিটা সারি ৩২ bit, মানে ৪ Byte। একটা আসল Segment ধাপে ধাপে দেখো, Header কীভাবে ভরে ওঠে; নিচের Flag সারিতে আটটা Flag-এর কোনগুলো চালু, সেটা দেখা যায়।
পরিস্থিতি
Handshake-এর পরে একটা HTTPS Request-এর প্রথম Data Segment (Client ISN ১০০০, Server ISN ৫০০০)।
Source Port · 16
53000
Destination Port · 16
443
Sequence Number · 32
…
Acknowledgment Number · 32
…
Data Offset · 4
…
Reserved · 4
…
Flag · 8
…
Window · 16
…
Checksum · 16
…
Urgent Pointer · 16
…
Option + Padding · ০–৪০ Byte
…
Data (Payload) · বাকিটা
…
Flag (৮ bit)
Port ঠিক করে কোন মেশিনের কোন Program। প্রতিটা ১৬ bit, তাই ০ থেকে ৬৫৫৩৫। IP Header-এর দুটো IP Address-এর সাথে মিলে এরা ৪-tuple বানায়, আর সেটাই এই একটা Connection-এর পরিচয়।
#আটটা Flag
| Flag | নাম | মানে |
|---|---|---|
| SYN | Synchronize | Connection খোলে আর ISN বয়ে আনে; একটা Sequence Number খরচ করে |
| ACK | Acknowledgment | Acknowledgment Number কার্যকর; প্রথম SYN-এর পরের সব Segment-এ চালু |
| FIN | Finish | “আমার আর পাঠানোর কিছু নেই”; এক দিক বন্ধ করে; একটা Sequence Number খরচ করে |
| RST | Reset | এখনই ভেঙে দাও: বন্ধ Port, Crash করা Application, অচেনা Connection |
| PSH | Push | এখনই Application-কে দাও; একটা Write-এর শেষ Segment-এ থাকে |
| URG | Urgent | Urgent Pointer কার্যকর; পুরনো জিনিস (Telnet Interrupt), নতুন Application ব্যবহার করে না |
| ECE | ECN Echo | Receiver একটা “Congestion হচ্ছে” দাগ দেখেছে; গতি কমাও |
| CWR | Congestion Window Reduced | ECE-র উত্তরে Sender: “আমি গতি কমিয়েছি” |
ECE আর CWR যোগ হয়েছে ২০০১ সালে, Explicit Congestion Notification (ECN)-এর জন্য; নিচে আলোচনা আছে। NS নামে একটা পুরনো নবম Flag ছিল, সেটাকে ইতিহাস ঘোষণা করা হয়েছে, এখন সেটা একটা Reserved Bit।
#যে Option-গুলো জানা দরকার
- MSS (Maximum Segment Size), শুধু SYN-এ: এই পাশ সবচেয়ে বড় কত Byte Payload নিতে পারে। Ethernet-এ IPv4-এর জন্য ১৪৬০ (১৫০০ − ২০ − ২০), IPv6-এর জন্য ১৪৪০। এটা MTU না: MSS গোনে শুধু TCP-র Data।
- Window Scale, শুধু SYN-এ: ০ থেকে ১৪ পর্যন্ত একটা Shift। এরপরের প্রতিটা Window-কে ২^shift দিয়ে গুণ করা হয়, ফলে Window ৬৪ KB-এর বদলে প্রায় ১ GB পর্যন্ত যেতে পারে।
- SACK-permitted (SYN-এ) আর SACK Block (পরে): ফাঁকের পরের কোন কোন Byte-অংশ আগেই এসে গেছে, Receiver তার তালিকা দিতে পারে।
- Timestamp (১০ Byte, Padding সহ ১২): প্রতিটা পাশ নিজের ঘড়ির সময় লেখে আর অন্য পাশেরটা ফেরত দেয়। এতে প্রতিটা ACK-এ একটা RTT মাপ মেলে, আর ঘুরে আসা Sequence Number থেকে সুরক্ষা মেলে (PAWS): ৩২ bit Sequence Number ৪ GB পরে ঘুরে শূন্যে আসে, আর ১০ Gbps-এ তাতে লাগে মাত্র প্রায় ৩.৪ সেকেন্ড।
- NOP আর End of List: এক Byte-এর ফাঁক ভরানো জিনিস, Option-গুলোকে সারি মিলিয়ে রাখে।
- Fast Open Cookie: ফিরে আসা Client SYN-এর ভেতরেই Data দিতে পারে (নিচে দেখো)।
SYN-এ যে Option ঠিক হয়, পরে আর তা যোগ করা যায় না। SYN-ACK যদি Window Scaling বা SACK ফেরত না দেয়, পুরো Connection-এ সেটা বন্ধই থাকে।
#UDP Header
UDP-র দরকার মাত্র ৮ Byte, সবসময়:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-------------------------------+-------------------------------+
| Source Port | Destination Port |
+-------------------------------+-------------------------------+
| Length | Checksum |
+-------------------------------+-------------------------------+
| Field | Bit | কী কাজ করে |
|---|---|---|
| Source Port | ১৬ | পাঠানো Program (উত্তর না চাইলে ০ হতে পারে) |
| Destination Port | ১৬ | গ্রহণকারী Program (DNS-এর জন্য ৫৩) |
| Length | ১৬ | Header আর Data মিলিয়ে কত Byte (অন্তত ৮) |
| Checksum | ১৬ | TCP-র মতোই ধারণা। IPv4-এ ঐচ্ছিক (০ মানে হিসাব হয়নি), IPv6-এ বাধ্যতামূলক |
UDP যা বাদ রাখে, আসল কথা সেটাই: Sequence Number নেই, Acknowledgment নেই, Window নেই, Connection-এর কোনো হিসাব নেই। প্রতিটা Datagram একটা স্বাধীন বার্তা। এর বেশি কিছু (ক্রম, আবার চেষ্টা, গতি নিয়ন্ত্রণ) লাগলে সেটা Application-এর কাজ; DNS (Query ID আর Retry) আর QUIC (নিজের Packet Number আর ACK Frame, UDP Payload-এর ভেতরে Encrypted) ঠিক এটাই করে।
নিজেকে যাচাই করোপ্রশ্ন
Capture করা একটা TCP Segment-এর Data Offset ১১। এর Header কত বড়, আর এতে কত Byte Option আছে?
#খোলা আর বন্ধ করা, সংক্ষেপে
Lesson ১.৯-এ আসল নম্বর আর ব্যর্থতাসহ Handshake ধাপে ধাপে দেখানো আছে; এখানে Header-এর ভাষায় ছোট করে বলি।
Client Server (LISTEN)
| ---- SYN seq=x ------------------------> |
| <--- SYN, ACK seq=y, ack=x+1 ---------------- |
| ---- ACK ack=y+1 ----------------------> |
[ESTABLISHED] [ESTABLISHED]
১. SYN (SYN Flag = ১): Client বলে, “আমি তোমার সাথে কথা বলতে চাই; আমার শুরুর Sequence Number হলো x।” ২. SYN-ACK (SYN = ১ আর ACK = ১): Server বলে, “তোমার x পেয়েছি, এবার x+১ দাও (ACK); আর আমার দিক থেকে শুরুর Sequence Number y (SYN)।” ৩. ACK (ACK = ১): Client নিশ্চিত করে, “তোমার y পেয়েছি, এবার y+১ আশা করছি। Connection হয়ে গেল!”
Option-গুলো (MSS, Window Scale, SACK-permitted, Timestamp) দুটো SYN-এর সাথেই যায়। বন্ধ করা হয় চার ধাপে, প্রতি দিকে একটা FIN আর একটা ACK: FIN → ACK → FIN → ACK। প্রতিটা দিক আলাদাভাবে বন্ধ হয় বলে এক পাশ পাঠানো থামিয়েও গ্রহণ চালিয়ে যেতে পারে (Half-close)। আর দ্বিতীয় FIN প্রায়ই ACK-এর সাথে একই Segment-এ চলে যায়, তাই Capture-এ চারটার বদলে প্রায়ই তিনটা Segment দেখবে। যে পাশ আগে বন্ধ করেছে, সে এরপর TIME_WAIT-এ অপেক্ষা করে (Linux-এ ৬০ সেকেন্ড), যাতে পুরনো Segment-এর দেরিতে আসা কপিগুলো মরে যায়। এসবের Animation আছে Lesson ১.৯-এ।
#নির্ভরযোগ্যতা, গভীরে
#Sequence আর Acknowledgment Number
TCP নম্বর দেয় Byte-কে, Packet-কে না। seq=1001 আর ১৪৪৮ Byte Data সহ একটা Segment বহন করে ১০০১ থেকে ২৪৪৮ নম্বর Byte; Receiver উত্তর দেয় ack=2449, মানে “২৪৪৮ পর্যন্ত সব পেয়েছি”। এর তিনটা ফল মনে রাখো:
- ACK জমা হিসাবে চলে (Cumulative)। একটা ACK অনেক Segment নিশ্চিত করতে পারে, আর একটা ACK হারালেও পরেরটা এলে ক্ষতি নেই।
- Receiver শুধু প্রথম ফাঁক পর্যন্ত ACK দিতে পারে। ফাঁকের পরের Data Buffer-এ রাখা থাকে, কিন্তু Ack Number তা স্বীকার করে না। এখান থেকেই Duplicate ACK-এর জন্ম।
- নম্বর ঘুরে আসে। ২³² Byte (৪ GB) পরে Counter আবার শূন্য থেকে শুরু। পুরনো, দেরিতে আসা Segment যেন ঘুরে আসার পর নতুন Data বলে ভুল না হয়, Timestamp (PAWS) সেটা আটকায়।
#Retransmission Timeout আর RTT মাপা
Data পাঠানোর সময় TCP একটা Retransmission Timer চালু করে। Retransmission Timeout (RTO)-এর আগে ওই Data-র ACK না এলে আবার পাঠায়। Timeout খুব ছোট হলে যে Data শুধু দেরি করছিল সেটাও আবার যায়; খুব বড় হলে সত্যিকারের Loss-এর পর Connection অলস বসে থাকে। তাই TCP সারাক্ষণ Round Trip মাপে:
- SRTT (গড় করা RTT): SRTT = ৭/৮ × SRTT + ১/৮ × নতুন মাপ।
- RTTVAR (RTT কতটা ওঠানামা করে): RTTVAR = ৩/৪ × RTTVAR + ১/৪ × |SRTT − মাপ|।
- RTO = SRTT + ৪ × RTTVAR, সাথে একটা নিচের সীমা (Standard বলে ১ সেকেন্ড, Linux নেয় ২০০ ms) আর ওপরের সীমা (Linux-এ ১২০ সেকেন্ড)। কোনো মাপ পাওয়ার আগে RTO থাকে ১ সেকেন্ড।
Karn-এর নিয়ম: আবার পাঠানো Segment থেকে কখনো RTT মাপ নিয়ো না, কারণ ACK-টা কোন কপির, তা বোঝার উপায় নেই। Timestamp এই সমস্যা মেটায়, কারণ ফেরত আসা Timestamp কপিটাকে চিনিয়ে দেয়। প্রতিটা Timeout-এর পর RTO দ্বিগুণ হয় (Exponential Backoff)। Linux চালু থাকা Connection ছেড়ে দেয় ১৫ বার ব্যর্থ চেষ্টার পর, মোটামুটি ১৫ মিনিটে; এজন্যই Application-এর নিজের, আরও ছোট Timeout লাগে।
#Duplicate ACK, Fast Retransmit আর Fast Recovery
Timer-এর অপেক্ষা ধীর, তাই TCP-র কাছে আরও দ্রুত একটা সংকেত আছে। ধরো Packet ১, ৩ আর ৪ পৌঁছাল, কিন্তু ২ হারাল। Receiver পরের প্রতিটা Packet-এর উত্তরে একই ACK পাঠায়, “আমি এখনো Packet ২ চাই”: এগুলোই Duplicate ACK, আর এগুলো সাথে সাথে পাঠানো হয়। তিনটা Duplicate ACK পেলে Sender বুঝে নেয় Packet ২ হারিয়েছে, কিন্তু Network তখনো Data পৌঁছাচ্ছে:
- Fast Retransmit: Timer বাজার আগেই হারানো Segment সাথে সাথে আবার পাঠাও।
- Fast Recovery: এক Segment-এ নেমে যেয়ো না। পথে থাকা Data-র অর্ধেক ssthresh করো, আর সেখান থেকে Congestion Avoidance-এ চলো (Reno Recovery-র সময় cwnd তিন Segment বাড়িয়ে রাখে, Network ছেড়ে বেরোনো Segment-গুলোর হিসাবে, তারপর আবার কমায়)। প্রতিটা বাড়তি Duplicate ACK মানে আরেকটা Segment Network ছেড়েছে, তাই নতুন Data চলতে থাকে।
একটা না, তিনটা কেন? পথে Packet মাঝে মাঝে আগে-পরে হয়ে যায়; এক-দুটো Duplicate স্বাভাবিক, Loss প্রমাণ করে না। তিন হলো পুরনো সমঝোতা। আধুনিক Linux-এ আছে RACK, যা সময় দেখে Loss বিচার করে (“পরে পাঠানো একটা Segment-এর ACK চলে এসেছে, আর যথেষ্ট সময়ও পেরিয়েছে”), আর আছে Tail Loss Probe, সেই অবস্থার জন্য যখন একঝাঁক Segment-এর শেষগুলো হারায় আর পরে কিছু থাকে না যা Duplicate ACK বানাবে।
#SACK: ঠিক কী হারিয়েছে বলে দাও
জমা ACK শুধু প্রথম ফাঁকের কথা বলতে পারে। এক Window-এ দুটো Loss হলে সাধারণ TCP (NewReno) প্রতি Round Trip-এ একটা করে ফাঁক সারায়। SACK থাকলে প্রতিটা Duplicate ACK ফাঁকের পরে পাওয়া Data-র সর্বোচ্চ চারটা Block (Timestamp থাকলে তিনটা) সাথে আনে, যেমন ack 2001, SACK 3001–4000, 5001–7000। Sender একটা হিসাবের খাতা (Scoreboard) রাখে, আর এক Round Trip-এই ঠিক হারানো অংশগুলো আবার পাঠায়। প্রায় সব জায়গায় SACK ডিফল্টে চালু।
হারানো Segment উদ্ধার: Duplicate ACK, Timeout আর SACK
১০০০ Byte-এর Segment; n নম্বর Segment শুরু হয় n×১০০০+১ Byte থেকে। প্রতিটা Scenario চালাও: Loss কীভাবে ধরা পড়ল, তার ওপরই নির্ভর করে দাম কত।
পরিস্থিতি
Segment ২ হারায়, অথচ ১, ৩, ৪ আর ৫ পৌঁছে যায় (নোটের উদাহরণ)। Network তখনো চলছে, তাই উদ্ধার দ্রুত।
Sender-এর cwnd = ৮ Segment, তাই প্রতিটা ACK-এর অপেক্ষা না করে একসাথে কয়েকটা পাঠায়।
| Loss ধরা পড়ল যেভাবে | কখন হয় | দাম |
|---|---|---|
| ৩টা Duplicate ACK | Stream-এর মাঝখানে একটা Segment হারাল | প্রায় এক বাড়তি RTT; cwnd অর্ধেক (CUBIC: ৭০%) |
| SACK Block | এক Window-এ কয়েকটা Segment হারাল | সব ফাঁকের জন্য মিলিয়ে এক RTT, প্রতিটায় একটা না |
| Tail Loss Probe / RACK | একঝাঁকের শেষ Segment হারাল | পুরো Timeout-এর বদলে প্রায় দুই RTT |
| Retransmission Timeout | পথের সব হারাল, বা পরে কোনো Segment নেই | RTO (≥ ২০০ ms, প্রায়ই ১ সেকেন্ড+), তারপর cwnd = ১ Segment আর Slow Start |
#Flow Control: Receive Window
Flow Control Sender-এর গতিকে Receiver-এর সামর্থ্যের সাথে মেলায়। দ্রুত Sender যদি গ্রহণকারী Application-এর পড়ার চেয়ে বেশি দ্রুত Data ঠেলে, Receiver-এর Buffer উপচে পড়ে আর Data হারায়। TCP এটা ঠেকায় একটা Sliding Window দিয়ে।
- প্রতিটা ACK Header-এর Window Field-এ Receive Window (rwnd) বয়ে আনে: এই মুহূর্তে Receiver-এর Socket Buffer-এ কতটা খালি।
- Sender সর্বোচ্চ rwnd Byte পাঠাতে পারে, যার ACK এখনো আসেনি।
- Application যত পড়ে, জায়গা তত খালি হয়; ACK যত আসে, Window-এর বাঁ প্রান্ত তত সামনে সরে। Window Byte Stream ধরে পিছলে চলে।
bytes: ... | sent and ACKed | sent, not ACKed | may send now | not yet allowed ...
^ left edge (last ack) right edge = ack + rwnd ^
আসলে Sender-কে আটকে রাখে দুটো Window: পথে সে রাখতে পারে সর্বোচ্চ min(rwnd, cwnd)। rwnd বাঁচায় Receiver-কে; cwnd (পরের অংশ) বাঁচায় Network-কে।
#Zero Window আর Window Probe
Application যদি পড়া থামায় (লম্বা Garbage Collection বিরতি, আটকে থাকা Database Call, ধীর Disk), Buffer ভরে যায় আর Receiver ঘোষণা করে Window = ০ (Zero Window)। Sender Data পাঠানো থামিয়ে একটা Persist Timer চালু করে। Timer বাজলে একটা ছোট্ট Zero Window Probe পাঠায়, যাতে Receiver তার বর্তমান Window জানিয়ে উত্তর দেয়; উত্তর তখনো শূন্য হলে প্রতিবার বিরতি বাড়ায়। Probe না থাকলে একটা হারানো Window Update (খালি ACK, যেটা TCP কখনো আবার পাঠায় না) দুই পাশকে চিরকাল অপেক্ষায় রাখত।
এর কাছাকাছি একটা ফাঁদ হলো Silly Window Syndrome: যে Receiver কয়েক Byte খালি হলেই ঘোষণা দেয়, সে ছোট ছোট Segment ডেকে আনে, যার বেশিরভাগটাই Header। Receiver এটা সারায় অন্তত এক MSS বা অর্ধেক Buffer খালি না হওয়া পর্যন্ত Window না খুলে; Sender সারায় Nagle-এর Algorithm দিয়ে।
#Window Scaling আর Auto-tuning
Window Field মাত্র ১৬ bit, তাই সাহায্য ছাড়া Window থেমে যায় ৬৫,৫৩৫ Byte-এ। ১৯৮১ সালে সেটা যথেষ্ট ছিল, আজ একেবারেই কম। SYN-এ ঠিক হওয়া Window Scale Option Field-টাকে সর্বোচ্চ ২¹⁴ দিয়ে গুণ করে, ফলে Window প্রায় ১ GB পর্যন্ত যেতে পারে। এরপর Operating System Buffer নিজে নিজে Auto-tune করে, Application তাল রাখলে বাড়াতে থাকে (Linux-এর ডিফল্ট সর্বোচ্চ Receive Buffer প্রায় ৬ MB, net.ipv4.tcp_rmem দিয়ে ঠিক হয়)।
Flow Control: Receive Window, ধীর পাঠক আর Window Probe
Receiver-এর Socket Buffer-এ ৪০০০ Byte আঁটে; Segment ১০০০ Byte করে। Receiver যে Window ঘোষণা করে, সেটা ওই Buffer-এর খালি জায়গা।
পরিস্থিতি
সুস্থ Receiver: Data পড়া আর ACK হওয়ার সাথে সাথে Window সামনে সরে।
Receiver rwnd = ৪০০০ ঘোষণা করেছে, তাই Sender ৪০০০ Byte পর্যন্ত ACK ছাড়াই পাঠাতে পারে। চারটা Segment পাঠিয়ে তাকে থামতে হয়: Window ভরা।
#Congestion Control: Congestion Window
Flow Control কাজ করে Sender আর Receiver-এর মাঝে। Congestion Control রক্ষা করে মাঝের সবকিছুকে: Network-এর পথ আর তার Router-দের। কোনো Router-এ তার বের হওয়ার Link-এর ক্ষমতার চেয়ে বেশি Packet এলে Queue ভরে যায় আর সে Packet ফেলে দিতে শুরু করে। TCP এটা টের পেয়ে গতি কমায়।
এটা ঐচ্ছিক ভদ্রতা না। ১৯৮৬ সালের অক্টোবরে প্রথম দিকের Internet-এ Congestion Collapse হয়েছিল: Link ভরে ছিল আবার পাঠানোর আবার পাঠানোয়, আর California-র দুটো জায়গার মধ্যে Throughput প্রায় হাজার গুণ কমে গিয়েছিল। ১৯৮৮ সালে Van Jacobson-এর Algorithm (Slow Start, Congestion Avoidance, Fast Retransmit) এটা সারায়, আর তারপর থেকে প্রতিটা TCP-তে আছে একটা Congestion Window (cwnd): পথ কতটা Data ধরতে পারে, সে সম্পর্কে Sender-এর নিজের আন্দাজ।
ক্লাসিক (Reno) Algorithm-এর চারটা অংশ।
#Slow Start
নতুন Connection পথের ক্ষমতা জানে না, তাই খুঁজে দেখে:
- শুরু হয় ১০ Segment-এর Initial Window দিয়ে, প্রায় ১৪ KB (RFC 6928; Linux-এ ২০১১ থেকে)। পুরনো Stack নিত ১–৪ Segment।
- নতুন Data-র প্রতিটা ACK-এ cwnd এক Segment বাড়ে। প্রতি Round Trip-এ পুরো এক Window ACK ফেরে, তাই cwnd প্রতি RTT-তে দ্বিগুণ হয়: ১০ → ২০ → ৪০ → ৮০। নাম Slow হলেও বৃদ্ধিটা Exponential।
- cwnd Slow Start Threshold (ssthresh)-এ পৌঁছালে বা Loss হলে Slow Start থামে। ssthresh শুরুতে “অসীম” উঁচু থাকে, তাই প্রথম Slow Start শেষ হয় একটা Loss দিয়েই।
#Congestion Avoidance
cwnd ≥ ssthresh হলে TCP দ্বিগুণ করা থামায়, আর বাড়ায় প্রতি Round Trip-এ মাত্র এক Segment (রৈখিক, বা Additive Increase)। বাড়তি জায়গা আছে কি না, সাবধানে খোঁজে।
#Loss ধরা আর সামলে ওঠা
TCP হারানো Packet-কে Congestion-এর লক্ষণ ধরে, আর Loss কীভাবে ধরা পড়ল তার ওপর দুইভাবে সাড়া দেয়:
১. Timeout (RTO শেষ), গুরুতর জ্যাম। একটাও ACK ফেরেনি। ssthresh = পথে থাকা Data-র অর্ধেক, cwnd = ১ Segment, তারপর আবার Slow Start থেকে শুরু। ২. তিনটা Duplicate ACK, হালকা Loss। কিছু Segment তখনো পৌঁছাচ্ছে। হারানোটা Fast Retransmit করো, তারপর Fast Recovery: ssthresh = অর্ধেক, cwnd = ssthresh, আর এক থেকে না, Congestion Avoidance থেকে চালিয়ে যাও।
প্রতি RTT-তে এক Segment বাড়ানো আর Loss-এ অর্ধেক করা, একে বলে AIMD (Additive Increase, Multiplicative Decrease)। এর Graph হলো বিখ্যাত করাতের দাঁত: ধীরে ওঠা, অর্ধেকে নামা, আবার ধীরে ওঠা। AIMD-র একটা দারুণ গুণ আছে: একই Link ভাগ করা কয়েকটা Flow মোটামুটি সমান ভাগে এসে দাঁড়ায়। কোনো কেন্দ্রীয় নিয়ন্ত্রক ছাড়াই লক্ষ লক্ষ অচেনা Connection এভাবেই Internet ন্যায্যভাবে ভাগ করে নেয়।
সময়ের সাথে Congestion Window
cwnd Segment-এ, প্রতি Round Trip-এ একটা বিন্দু। লাল ড্যাশ রেখা হলো Router ফেলে দেওয়ার আগে পথে কতটা আঁটে; সবুজ ডট রেখা হলো পাইপটা নিজে (Bandwidth-delay Product)।
পরিস্থিতি
একটা পথে ক্লাসিক Reno, যেখানে ৬০টা Segment আঁটে (৪০টা পাইপে + ২০টা Router-এর Queue-তে)।
নতুন Connection শুরু হয় ১০টা Segment-এর Initial Window দিয়ে (প্রায় ১৪ KB)। পথটা কত দ্রুত, এখনো কেউ জানে না।
#Reno বনাম CUBIC বনাম BBR
Reno হলো বইয়ের মডেল। আসল System চালায় তার উত্তরসূরিরা:
| Reno / NewReno | CUBIC | BBR | |
|---|---|---|---|
| সংকেত | Packet Loss | Packet Loss | মাপা Bandwidth আর সর্বনিম্ন RTT |
| বৃদ্ধি | প্রতি RTT-তে +১ Segment | শেষ Loss থেকে সময়ের ওপর একটা Cubic বক্ররেখা: পুরনো সর্বোচ্চ থেকে দূরে দ্রুত, কাছে চ্যাপ্টা, পার হলে আবার দ্রুত | পথে প্রায় এক Bandwidth-delay Product রাখে, মাপা গতিতে Pacing করে; ১.২৫ গুণে খোঁজে, ০.৭৫ গুণে খালি করে |
| Loss-এ সাড়া | অর্ধেক | ৭০%-এ নামে | আলাদা আলাদা Loss প্রায় পাত্তা দেয় না (প্রথম Version); পরের Version ভারী Loss-এও সাড়া দেয় |
| Router-এর Queue | উপচে না পড়া পর্যন্ত ভরায় | উপচে না পড়া পর্যন্ত ভরায় | প্রায় খালি রাখার চেষ্টা করে (কম Latency) |
| কোথায় | বই, পুরনো Stack | Linux (২০০৬ থেকে), Windows আর macOS-এর ডিফল্ট | Google (Search, YouTube, Google Cloud), অনেক CDN; Linux-এ একটা Option |
| দুর্বলতা | দূরের দ্রুত পথে খুব ধীর | এখনো Loss-নির্ভর: Loss-ভরা Wi-Fi আর দূরের Link-এ ভোগে | CUBIC-এর সাথে মিশলে ন্যায্যতা; অগভীর Buffer-এ সাবধানতা লাগে |
মূল কথাটা: Loss মানেই Congestion না। Wi-Fi বা মহাদেশ পেরোনো Link-এ গোলমালের কারণেও Packet হারাতে পারে। Loss-নির্ভর Algorithm তবুও গতি অর্ধেক করে; BBR পথের একটা মডেল বানায় বলে চালিয়ে যায়। এজন্যই বড় Content Provider-রা নিজেদের Server-এ BBR চালায়।
#ECN: ফেলে না দিয়ে সতর্ক করা
Explicit Congestion Notification-এ যে Router-এর Queue জমছে, সে Packet ফেলে না দিয়ে তাতে দাগ দেয় (“Congestion হচ্ছে”, IP Header-এর দুটো Bit)। Receiver ECE Flag দিয়ে দাগটা ফেরত জানায়, Sender ঠিক Loss-এর মতোই cwnd কমায় আর CWR দিয়ে নিশ্চিত করে। কিছুই আবার পাঠাতে হয় না, কেউ Timer-এর অপেক্ষা করে না, তাই Latency কম থাকে। ECN Handshake-এ ঠিক হয়, আর দুই প্রান্ত আর Router, সবার সমর্থন লাগে। Data Centre-এ এর ব্যাপক ব্যবহার (DCTCP), আর Internet ধীরে ধীরে এটা নিচ্ছে, সর্বশেষ L4S-এর জন্য (কম Latency, কম Loss, বাড়ানো যায় এমন Throughput)।
#Bandwidth-delay Product আর দূরের মোটা পাইপ
একটা Link ব্যস্ত রাখতে কতটা Data “বাতাসে” থাকতে হবে? পথটাকে একটা পাইপ ভাবো: এর চওড়া হলো Bandwidth, দৈর্ঘ্য হলো Round-trip Time। পাইপের আয়তন হলো Bandwidth-delay Product (BDP):
BDP = Bandwidth × RTT
একটা হাতে-কলমে উদাহরণ: এশিয়া থেকে আমেরিকা, ১ Gbps Link, ২০০ ms Round Trip:
১. ১ Gbps × ০.২ সেকেন্ড = ২০,০০,০০,০০০ bit = ২,৫০,০০,০০০ Byte = ২৫ MB। ২. Link ভরাতে TCP-কে ACK না আসা ২৫ MB পথে রাখতে হবে, তাই Receive Window আর Congestion Window, দুটোকেই অন্তত ২৫ MB হতে হবে। ৩. ৬৪ KB Window হলে (Window Scaling ছাড়া): প্রতি Round Trip-এ এক Window = ৬৫,৫৩৫ Byte / ০.২ সেকেন্ড ≈ ৩২৮ KB/s ≈ ২.৬ Mbps। Link-এর মাত্র ০.২৬%। প্রতিটা Round Trip-এর ৯৯.৭% সময় Sender ACK-এর অপেক্ষায় বসে থাকে। ৪. একটা Connection-এর Throughput সর্বোচ্চ Window / RTT। তাই ২০০ ms-এ ১ Gbps পেতে ২৫ MB Window লাগে; ১ ms-এ (Data Centre-এর ভেতরে) ১২৫ KB-ই যথেষ্ট।
বড় BDP-র পথকে বলে Long Fat Network (“দূরের মোটা পাইপ”)। এরা তিন জায়গায় ধারণা ভাঙে: ১৬ bit Window (সারায় Window Scaling), ডিফল্ট Socket Buffer-এর সীমা (সারায় Tuning), আর Loss-এ স্পর্শকাতরতা। Loss-নির্ভর Sender-এর Throughput মোটামুটি আটকে থাকে (MSS / RTT) × ১.২২ / √Loss-এর হার-এ (Mathis-এর সূত্র)। ২০০ ms-এ মাত্র ০.০১% Loss হলেও সেটা প্রায় ৭ Mbps, Window যত বড়ই হোক। দূরের পথে সামান্য Loss-ও বিশাল Throughput খেয়ে ফেলে; ওষুধ হলো BBR, সমান্তরাল কয়েকটা Connection, বা ব্যবহারকারীর কাছের Server।
Bandwidth-delay Product: একটা Connection কি এই পাইপ ভরাতে পারবে?
একটা Preset বেছে নাও, নয়তো চারটা Input নিজে ঠিক করো। পাইপটা দেখায়, প্রতিটা Round Trip-এ একটা TCP Connection Link-এর কতটা ব্যস্ত রাখতে পারে।
Preset
তারে একটা Round Trip (1 ms)
Bandwidth-delay Product
125 KB
Window-এর সীমা (Window ÷ RTT)
34 Gbps
Loss-এর সীমা (Reno ধাঁচ, Mathis)
∞
একটা Connection পায়
1 Gbps
Link ব্যবহার
100 %
১০ GB কপি করতে সময়
1.3 min
১ Gbps × ১ ms = ১২৫ KB। আধুনিক যেকোনো Window এর চেয়ে বড়, তাই প্রথম সেকেন্ড থেকেই একটা Connection Link ভরিয়ে ফেলে। এখানে সীমা Link নিজেই: Window পুরো Bandwidth-delay Product ঢেকে ফেলে, তাই পাইপ সবসময় ভরা থাকে। এটাই সুস্থ অবস্থা।
#Head-of-line Blocking, আরেকবার
TCP যেহেতু একটাই ক্রমমতো Byte Stream দেয়, একটা Segment হারালে তার পেছনের সবকিছু আটকে থাকে, এমনকি যা পৌঁছে গেছে তাও, যতক্ষণ না আবার পাঠানোটা ফাঁক ভরায়। Lesson ১.৯-এ দেখেছ HTTP-র জন্য এর মানে কী: HTTP/2 একটা TCP Connection-এ অনেক Request একসাথে চালায়, তাই একটা Loss সবগুলোকে থামিয়ে দেয়। এটাই TCP স্তরের Head-of-line Blocking, আর HTTP/3 যে QUIC-এর ওপর চলে, যেখানে প্রতিটা Stream আলাদাভাবে ক্রম রাখে, তার মূল কারণ এটাই।
#Nagle-এর Algorithm, Delayed ACK আর TCP_NODELAY
১৯৮০-র দশকের দুটো Optimisation Packet বাঁচায়, কিন্তু দুটো একসাথে হলে Latency বাড়াতে পারে।
- Nagle-এর Algorithm (Sender): পথে ACK না আসা Data থাকলে ছোট (এক MSS-এর কম) Segment পাঠিয়ো না; ACK না আসা বা পূর্ণ Segment তৈরি না হওয়া পর্যন্ত ছোট Write জমাও। এটা বানানো হয়েছিল সেইসব Terminal-এর জন্য, যারা প্রতিটা Keystroke আলাদা Packet-এ পাঠাত: ৪১ Byte-এর Packet-এ ১ Byte Data।
- Delayed ACK (Receiver): প্রতিটা Segment-এর ACK সাথে সাথে দিয়ো না। একটু অপেক্ষা করো (Linux-এ ৪০ ms পর্যন্ত, Windows-এ ২০০ ms; Standard ৫০০ ms পর্যন্ত অনুমতি দেয়), এই আশায় যে ACK-টা Response Data-র সাথে জুড়ে যাবে, বা এক ACK-এ দুটো Segment স্বীকার হবে। তবে প্রতি দ্বিতীয় পূর্ণ Segment-এর ACK তখনই যায়।
আলাদাভাবে দুটোই যুক্তিসঙ্গত। একসাথে হলে Write-Write-Read ধরন আটকে যায়: Client একটা Request Header লেখে (সাথে সাথে যায়), তারপর Body (Nagle আটকে রাখে, Header-এর ACK-এর অপেক্ষায়); Server-এর কাছে আছে অর্ধেক Request, উত্তর দেওয়ার কিছু নেই, তাই সে ACK দেরি করে। Delayed ACK Timer না বাজা পর্যন্ত দুজনই অপেক্ষা করে: প্রতিটা Request-এ ৪০ ms (বা ২০০ ms) বাড়তি।
Nagle-এর Algorithm আর Delayed ACK-এর ধাক্কা: ৪০ ms আটকে থাকা
দুটো বুদ্ধিমান Optimisation, দুই পাশে একটা করে, যারা মিলে প্রতিটা Request-এ ৪০ ms (Windows-এ ২০০ ms) যোগ করতে পারে। এখানে RTT ৫০ ms।
পরিস্থিতি
একটা Remote Terminal তোমার প্রতিটা Keystroke টাইপের সাথে সাথে পাঠায়।
একটা Keystroke, এক Byte।
সমাধানগুলো, সবচেয়ে ভালোটা আগে:
১. প্রতিটা বার্তা এক Call-এ লেখো (একটা Buffer-এ বানিয়ে নাও, বা writev ব্যবহার করো), যাতে কখনো ছোট দ্বিতীয় Write না থাকে।
২. Socket-এ TCP_NODELAY চালু করো, Nagle বন্ধ হবে। Latency নিয়ে সংবেদনশীল Software (বেশিরভাগ HTTP Server ও Client, gRPC, অনেক Redis আর Database Client Library, Go-র net Package) ডিফল্টে এটা করে। দাম: Application অনেক ছোট টুকরো লিখলে ছোট Packet বেশি।
৩. Linux-এ বড় Data পাঠানো Program-এর জন্য TCP_CORK বা MSG_MORE বলে, “আরও আসছে, আগে Segment ভরাও”; আর Receiver-এ TCP_QUICKACK কিছু সময়ের জন্য Delayed ACK বন্ধ রাখে।
#TCP Fast Open আর Keepalive
TCP Fast Open (TFO) ফিরে আসা Client-কে SYN-এর ভেতরেই Data পাঠাতে দেয়। প্রথম Connection-এ Client একটা Cookie চায় (Client-এর IP Address আর একটা গোপন চাবি থেকে Server যে ছোট Code বানায়); পরের Connection-গুলোতে সে Cookie আর Request SYN-এ ভরে পাঠায়, আর Server SYN-ACK-এই উত্তর দিতে পারে; এক Round Trip বাঁচে। দুটো সমস্যা: SYN-এর Data কেউ আবার চালাতে বা ডুপ্লিকেট করতে পারে, তাই শুধু Idempotent Request নিরাপদ; আর কিছু মাঝের যন্ত্র Data-ভরা SYN ফেলে দেয়, তখন Client-কে পিছিয়ে আসতে হয়। Browser কখনো TFO ব্যাপকভাবে চালু করেনি। আজ একই সুবিধা দেয় TLS 1.3 আর QUIC 0-RTT, যারা Network-এর নাক গলানোর নাগালের বাইরে চলে।
Keepalive ধরে এমন অন্য পাশকে, যে FIN বা RST ছাড়াই হারিয়ে গেছে (বিদ্যুৎ গেল, Cable খুলে গেল, NAT Mapping ভুলে গেল)। অলস TCP Connection কিছুই পাঠায় না, তাই Keepalive ছাড়া সেটা চিরকাল “চালু” দেখাতে পারে। প্রতিটা Socket-এ SO_KEEPALIVE দিয়ে চালু করলে (ডিফল্টে বন্ধ), Linux ২ ঘণ্টা অলস থাকার অপেক্ষা করে, তারপর ৭৫ সেকেন্ড পরপর সর্বোচ্চ ৯টা Probe পাঠায়: মৃত অন্য পাশ ধরা পড়ে ২ ঘণ্টা ১১ মিনিট পরে। Service-গুলো এটা কমিয়ে নেয় (যেমন ৬০ সেকেন্ড অলস, ১০ সেকেন্ড বিরতি, ৩টা Probe) বা Application Heartbeat ব্যবহার করে (HTTP/2 Ping Frame, WebSocket Ping, Connection Pool-এর যাচাই), আর পথে যেকোনো NAT বা Load Balancer-এর Idle Timeout-এর চেয়ে ছোট রাখে।
TCP Fast Open আর Keepalive
দুটো ঐচ্ছিক Feature: একটা আবার আসার সময় Handshake-এর Round Trip বাঁচায়, আরেকটা কিছু না বলে উধাও হয়ে যাওয়া অন্য পাশকে ধরে।
পরিস্থিতি
সাধারণ Handshake-এর মধ্যেই Client একটা Cookie চায়।
খালি Fast Open Option মানে “আমাকে একটা Cookie দাও”।
#System Design-এ TCP
System বানানো আর চালানোর সময় এসবের মানে দাঁড়ায়:
- নতুন Connection ধীর; আবার ব্যবহার করো। নতুন Connection TCP-র জন্য এক RTT, TLS-এর জন্য এক বা দুই RTT দেয়, তারপর শুরু করে মাত্র ১০ Segment cwnd নিয়ে। নতুন Connection-এ ১ MB Download করতে Slow Start-এর প্রায় ৭টা Round Trip লাগে; বড় cwnd সহ গরম Connection-এ লাগে ১–২টা। HTTP Keep-alive, Database আর Service-এ Connection Pool, আর HTTP/2 বা HTTP/3-এর Multiplexing ব্যবহার করো।
- গরম Connection গরম রাখো। RTO-র চেয়ে লম্বা অলস সময়ের পর Linux cwnd আবার শুরুতে নিয়ে যায় (
net.ipv4.tcp_slow_start_after_idle = 1)। দীর্ঘস্থায়ী, থেমে থেমে ব্যস্ত Connection-ওয়ালা Server-এ (Load Balancer-এর পেছনের API, Database Pool) এটা ০ করো। - অঞ্চল পেরোনো Throughput আটকে থাকে BDP-তে। মহাদেশ পেরিয়ে Database Replicate বা Backup কপি করতে লাগে বড় Socket Buffer (
net.core.rmem_max,net.ipv4.tcp_rmem,tcp_wmem৩২–৬৪ MB-তে তোলা), প্রতিটা Firewall পেরিয়েও অক্ষত Window Scaling, আর প্রায়ই কয়েকটা সমান্তরাল Stream (aws s3 cp,rcloneআর Multipart Upload যেমন করে)। - দূরের বা Mobile Client-এর সাথে কথা বলা Server-এ BBR চালাও।
net.ipv4.tcp_congestion_control = bbr(fqQueueing Discipline সহ) Loss-ভরা পথে প্রায়ই Throughput বাড়ায় আর Latency কমায়। আগে তোমার অন্য Traffic-এর সাথে ন্যায্যতা যাচাই করো; Data Centre-এর ভেতরে CUBIC-ই নিরাপদ ডিফল্ট। - ছোট, বারবার কথা বলা RPC-তে
TCP_NODELAYআর এক Write লাগে, নইলে প্রতিটা Call-এ ৪০ ms যোগ হতে পারে। - Application Timeout আর Heartbeat ঠিক করো: শুধু TCP মৃত Server-এর জন্য ১৫ মিনিট, চুপচাপ Server-এর জন্য ২ ঘণ্টা অপেক্ষা করতে পারে।
- Capture-এ Zero Window মানে দোষ গ্রাহকের, Network-এর না: ধীর Client, চাপে থাকা Worker বা ভরা Disk।
- একসাথে Reconnect ক্ষতি করে: হাজার হাজার Client একসাথে আবার যুক্ত হলে সবাই Slow Start থেকে শুরু করে, সবাই একসাথে Handshake করে। Jitter দিয়ে Reconnect ছড়িয়ে দাও (Retry আর Backoff আছে Chapter ২৮-এ)।
- QUIC কেন Transport-কে User Space-এ নিল: TCP থাকে Operating System-এর Kernel-এ, আর এর Header Firewall আর NAT যন্ত্র দেখতে পায় (প্রায়ই বদলেও দেয়), তাই TCP বদলাতে বছর লেগে যায় (TFO আর নতুন Option এখনো মাঝের যন্ত্রে আটকে যায়)। QUIC চলে UDP-র ওপর, Application-এর ভেতরে, আর নিজের Transport Header Encrypt করে; তাই Congestion Control, Loss Recovery আর নতুন Feature আসে Browser বা Server Update-এর সাথেই। TCP-র Head-of-line Blocking আর আবার পাঠানো নিয়ে দ্ব্যর্থতাও এতে নেই (Packet Number কখনো আবার ব্যবহার হয় না)।
| লক্ষণ | সম্ভাব্য TCP Mechanism | কোথায় দেখবে |
|---|---|---|
| প্রতিটা Request প্রত্যাশার চেয়ে প্রায় ৪০ ms বা ২০০ ms ধীর | Nagle + Delayed ACK | TCP_NODELAY, প্রতি বার্তায় কয়টা Write |
| দ্রুত Link-এ অঞ্চল পেরোনো কপি কয়েক Mbps-এ আটকে | BDP-র চেয়ে ছোট Window বা Buffer, বা দূরের পথে Loss | ss -ti (rcv_space, cwnd, wscale), Buffer sysctl, BBR |
| Mobile বা Wi-Fi-তে Throughput ধসে পড়ে | রেডিওর Loss-এ সাড়া দেওয়া Loss-নির্ভর Congestion Control | BBR, HTTP/3 |
| Page Load মাঝে মাঝে ~১ সেকেন্ড থেমে থাকে | হারানো SYN বা RTO-র অপেক্ষায় থাকা শেষ Segment | Capture-এ আবার পাঠানো |
| Capture-এ অনেক Zero Window | গ্রহণকারী Application ধীর | গ্রাহকের CPU, Garbage Collection বিরতি, Disk |
| Server মরে যাওয়ার পর Connection “ঝুলে” থাকে | Keepalive বা Application Timeout নেই | Keepalive Setting, Heartbeat, Client Timeout |
#নিজে করে দেখো
# Linux: প্রতিটা Connection-এর ভেতরের খবর (Congestion Algorithm, Window Scale, RTO, RTT, MSS, cwnd, আবার পাঠানো)
ss -tin state established '( dport = :443 )'
# cubic wscale:7,7 rto:204 rtt:3.1/1.4 mss:1448 cwnd:10 ssthresh:20 bytes_acked:52011 retrans:0/2 ...
# কোন Congestion Control চলছে, আর কোনগুলো আছে?
sysctl net.ipv4.tcp_congestion_control
sysctl net.ipv4.tcp_available_congestion_control
# Buffer-এর সীমা (min default max, Byte-এ) আর এই Lesson-এর অন্য Setting
sysctl net.ipv4.tcp_rmem net.ipv4.tcp_wmem net.ipv4.tcp_window_scaling net.ipv4.tcp_sack
sysctl net.ipv4.tcp_slow_start_after_idle net.ipv4.tcp_keepalive_time net.ipv4.tcp_fastopen
# একটা Test মেশিনে BBR চালিয়ে দেখো (যাচাই ছাড়া Production-এ না)
sudo sysctl -w net.core.default_qdisc=fq
sudo sysctl -w net.ipv4.tcp_congestion_control=bbr
# দুটো মেশিনের মধ্যে আসল Throughput মাপো
iperf3 -s # Server-এ
iperf3 -c 203.0.113.50 -t 10 # Client থেকে একটা Stream
iperf3 -c 203.0.113.50 -P 4 # চারটা সমান্তরাল Stream
iperf3 -c 203.0.113.50 -C bbr # Congestion Algorithm বেছে নাও (Linux)
# শুধু Handshake, Reset আর FIN Capture করো
sudo tcpdump -i any -nn 'tcp[tcpflags] & (tcp-syn|tcp-fin|tcp-rst) != 0'
# Port কি খোলা? (Connection Refused = RST, ঝুলে থাকা = Filter করা)
nc -vz example.com 443
telnet example.com 443
Windows-এ netsh interface tcp show global দেখায় Auto-tuning আর Congestion Provider, আর PowerShell-এ Get-NetTCPConnection Connection আর তাদের অবস্থার তালিকা দেয়।
Wireshark-এ একটা বড় Download-এর Capture খুলে এই Display Filter-গুলো চালাও: tcp.analysis.retransmission, tcp.analysis.fast_retransmission, tcp.analysis.duplicate_ack, tcp.analysis.zero_window, tcp.analysis.window_full, tcp.options.sack_le (SACK Block বহন করা Segment) আর tcp.flags.reset == 1। তারপর Statistics → TCP Stream Graphs → Stevens দিয়ে দেখো সময়ের সাথে Sequence Number কীভাবে ওঠে (শুরুর বাঁকটাই Slow Start), আর Window Scaling দিয়ে Receive Window-এর ওঠানামা দেখো।
#সারসংক্ষেপ
মূল শিক্ষা
- Transport Layer Data পৌঁছায় Process থেকে Process-এ: Port, Multiplexing, Segmentation আর Reassembly, Connection, Error, Flow আর Congestion Control। TCP পাঠায় Segment, নির্ভরযোগ্য, ক্রমমতো, Full-duplex Byte Stream হিসেবে; UDP পাঠায় স্বাধীন Datagram।
- TCP Header: ২০–৬০ Byte: Port, ৩২ bit Sequence (প্রথম Byte) আর Acknowledgment (পরের যে Byte চাই) Number, Data Offset (৪ Byte-এর Word-এ Header-এর দৈর্ঘ্য), আটটা Flag (CWR, ECE, URG, ACK, PSH, RST, SYN, FIN), ১৬ bit Window, Checksum, Urgent Pointer, Option (MSS, Window Scale, SACK, Timestamp)। UDP Header: ৮ Byte: Port, Length, Checksum।
- Loss সারায় RTO (SRTT + ৪ × RTTVAR, প্রতি Timeout-এ দ্বিগুণ), বা আরও দ্রুত তিনটা Duplicate ACK-এর পর Fast Retransmit; SACK এক RTT-তে কয়েকটা ফাঁক সারায়।
- Flow Control: Receiver rwnd ঘোষণা করে; Buffer ভরলে Zero Window, Sender পাঠায় Window Probe; Window Scaling ৬৪ KB-র সীমা তুলে দেয়।
- Congestion Control: Sender রাখে cwnd, আর পাঠাতে পারে min(cwnd, rwnd)। Slow Start শুরু হয় ১০ Segment দিয়ে, প্রতি RTT-তে দ্বিগুণ হয় (প্রতি ACK-এ এক Segment) ssthresh পর্যন্ত; তারপর প্রতি RTT-তে +১ Segment; তিনটা Duplicate ACK অর্ধেক করে (AIMD-র করাতের দাঁত), Timeout এক Segment-এ নামায়। আজকের ডিফল্ট CUBIC; BBR Bandwidth আর RTT-র মডেল বানায়; ECN ফেলে না দিয়ে দাগ দেয়।
- BDP = Bandwidth × RTT: ১ Gbps × ২০০ ms = ২৫ MB পথে থাকতে হবে। একটা Connection-এর Throughput ≤ Window / RTT, আর দূরের পথে সামান্য Loss-ও Loss-নির্ভর TCP-কে আটকে দেয়।
- Nagle + Delayed ACK প্রতি Request-এ ৪০–২০০ ms যোগ করতে পারে; এক Write বা
TCP_NODELAYব্যবহার করো। TFO এক Round Trip বাঁচায়, কিন্তু ভঙ্গুর; ডিফল্ট Keepalive মৃত অন্য পাশকে ধরে ২ ঘণ্টা ১১ মিনিট পরে। - System Design-এ: Connection আবার ব্যবহার করো, অঞ্চল পেরোনো Transfer-এর জন্য Buffer Tune করো, দূরের Client-এর জন্য BBR ভাবো, Application Timeout রাখো, আর মনে রাখো QUIC কেন Transport-কে User Space-এ নিয়ে গেল।
আগে নিজে উত্তর দাও, তারপর ব্যাখ্যাটা পড়ো। ঠিক হলেও পড়ো, কারণ ভুল Option-গুলো কেন ভুল, সেটাও ওখানে আছে।
নিজেকে যাচাই করোপ্রশ্ন
IP-র ওপরে Transport Layer কী যোগ করে?
নিজেকে যাচাই করোপ্রশ্ন
TCP আর UDP Header কত বড়?
নিজেকে যাচাই করোপ্রশ্ন
কোন তালিকায় আধুনিক TCP Header-এর আটটা Flag-ই আছে?
নিজেকে যাচাই করোপ্রশ্ন
একটা Segment-এর seq=7001, আর তাতে ১৪৪৮ Byte আছে। এর আগের সব Receiver-এর কাছে আছে। Receiver কোন Acknowledgment Number পাঠাবে?
নিজেকে যাচাই করোপ্রশ্ন
কোন দুটো TCP Option শুধু SYN আর SYN-ACK-এই ঠিক করা যায়?
বেছে নাও ২
নিজেকে যাচাই করোপ্রশ্ন
UDP Header-এ Sequence Number বা Window Field নেই কেন?
নিজেকে যাচাই করোপ্রশ্ন
Sender-এর SRTT ৮০ ms আর RTTVAR ১০ ms। Standard সূত্রে RTO কত হয়, আর Linux আসলে কত নেয়?
নিজেকে যাচাই করোপ্রশ্ন
Segment ১–৬ পাঠানো হলো; Segment ৩ হারাল, বাকিগুলো পৌঁছাল। Fast Retransmit-এ Segment ৩ আবার পাঠানোর আগে Sender কয়টা Duplicate ACK দেখে?
নিজেকে যাচাই করোপ্রশ্ন
একটা API-র Response ছোট (৩টা Segment)। মাঝে মাঝে শেষ Segment হারায়, আর Client প্রায় ২০০ ms বাড়তি অপেক্ষা করে। Fast Retransmit কেন কাজে আসে না, আর Linux কী করে?
নিজেকে যাচাই করোপ্রশ্ন
একই Window-এ দুটো Segment হারাল। SACK কী বদলায়?
নিজেকে যাচাই করোপ্রশ্ন
Karn-এর নিয়ম কেন আবার পাঠানো Segment থেকে RTT মাপ নিতে মানা করে?
নিজেকে যাচাই করোপ্রশ্ন
একটা Receiver-এর Buffer ভরা, সে Window ০ ঘোষণা করেছে। পরে তার Window Update হারিয়ে গেল। Connection চিরকাল অপেক্ষায় থাকা থেকে কী বাঁচায়?
নিজেকে যাচাই করোপ্রশ্ন
একটা Service-এর Server-এ নেওয়া Capture-এ হাজার হাজার Zero Window, সবগুলো Client-দের ঘোষণা করা। সবচেয়ে সম্ভাব্য কারণ কোনটা?
নিজেকে যাচাই করোপ্রশ্ন
একটা Firewall SYN থেকে Window Scale Option মুছে দেয়। ১০০ ms-এর পথে কী হবে?
নিজেকে যাচাই করোপ্রশ্ন
Slow Start নিয়ে কোন কথাটা ঠিক?
নিজেকে যাচাই করোপ্রশ্ন
Reno Congestion Avoidance-এ আছে, cwnd = ৪০, এমন সময় তিনটা Duplicate ACK এল। Recovery-র পর ssthresh আর cwnd কত?
নিজেকে যাচাই করোপ্রশ্ন
cwnd ৬৪ নিয়ে চলা একটা Connection-এ Retransmission Timeout হলো। ক্লাসিক TCP এরপর কী করে?
নিজেকে যাচাই করোপ্রশ্ন
AIMD (Additive Increase, Multiplicative Decrease) কেন TCP Flow-গুলোকে একটা Link ন্যায্যভাবে ভাগ করায়?
নিজেকে যাচাই করোপ্রশ্ন
CUBIC আর BBR নিয়ে কোন দুটো কথা ঠিক?
বেছে নাও ২
নিজেকে যাচাই করোপ্রশ্ন
একটা Video Service-এর Hotel আর Café-র Wi-Fi-তে থাকা ব্যবহারকারীরা কম Throughput পাচ্ছে, অথচ Link-এ জ্যাম নেই। Server-এর দিকে কোন বদল সবচেয়ে বেশি কাজে আসবে?
নিজেকে যাচাই করোপ্রশ্ন
সাধারণ Congestion সংকেতের তুলনায় ECN কী বদলায়?
নিজেকে যাচাই করোপ্রশ্ন
১০০ Mbps-এর একটা পথ, RTT ৫০ ms। Bandwidth-delay Product কত?
নিজেকে যাচাই করোপ্রশ্ন
একটা দল ঢাকা থেকে আমেরিকার একটা Region-এ ১০ GB Backup কপি করছে, ১ Gbps Link, RTT ২০০ ms। Window ৪ MB-তে আটকানো। একটা TCP Connection মোটামুটি কত Throughput পাবে?
নিজেকে যাচাই করোপ্রশ্ন
একই ১ Gbps, ২০০ ms পথে এখন ৩২ MB Buffer, কিন্তু স্থায়ী ০.০১% এলোমেলো Loss। CUBIC বা Reno-র Transfer কেন এখনো ধীর, আর কোন দুটো সমাধান কাজে আসে?
বেছে নাও ২
নিজেকে যাচাই করোপ্রশ্ন
১০ Segment (প্রতিটা ১৪৪৮ Byte) Initial Window-এর একটা নতুন HTTPS Connection-কে ১ MB Response পৌঁছাতে হবে। Loss না ধরলে Slow Start-এ Data Transfer-এর মোটামুটি কয়টা Round Trip লাগবে?
নিজেকে যাচাই করোপ্রশ্ন
একটা Client Library প্রতিটা RPC লেখে দুটো আলাদা Send Call-এ, ২০ Byte Header আর তারপর ৩০০ Byte Body, তারপর উত্তরের অপেক্ষা করে। প্রতিটা Call Network-এর Round Trip-এর চেয়ে প্রায় ৪০ ms বেশি সময় নেয়। কী হচ্ছে?
নিজেকে যাচাই করোপ্রশ্ন
TCP_NODELAY চালু করার ভালো কারণ কোনটা না?
নিজেকে যাচাই করোপ্রশ্ন
TCP Fast Open নিয়ে কোন দুটো কথা ঠিক?
বেছে নাও ২
নিজেকে যাচাই করোপ্রশ্ন
একটা Database Client ৩৫০ সেকেন্ড Idle Timeout-ওয়ালা একটা NAT Gateway দিয়ে অলস Connection-এর একটা Pool রাখে। চুপচাপ এক রাতের পর আবার ব্যবহার করতে গেলে Connection “Connection Reset” দিয়ে ব্যর্থ হয়। কোন সমাধান আসল কারণ ধরে?
নিজেকে যাচাই করোপ্রশ্ন
QUIC কেন TCP-কে উন্নত না করে UDP-র ওপর User Space-এ নিজের Transport (Loss Recovery, Congestion Control) বানাল?
নিজেকে যাচাই করোপ্রশ্ন
Engineer-রা দেখল, অন্য Region-এর একটা Service-এ প্রতিটা নতুন Connection-এর প্রথম Request ধীর, অথচ একই Connection-এর পরের Request দ্রুত। কোন দুটো ব্যবস্থা এর সমাধান?
বেছে নাও ২