Course-এর সূচি

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 ControlReceiver-কে ডুবিয়ে না দেওয়াReceive Window
Congestion ControlNetwork-কে ডুবিয়ে না দেওয়া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), DatabaseOnline 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)                        |
+---------------------------------------------------------------+
FieldBitকী কাজ করে
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 ৫০০০)।

08162431

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)

CWR0ECE0URG0ACK0PSH0RST0SYN0FIN0
ধাপ 1 / 10
1

Port ঠিক করে কোন মেশিনের কোন Program। প্রতিটা ১৬ bit, তাই ০ থেকে ৬৫৫৩৫। IP Header-এর দুটো IP Address-এর সাথে মিলে এরা ৪-tuple বানায়, আর সেটাই এই একটা Connection-এর পরিচয়।

#আটটা Flag

Flagনামমানে
SYNSynchronizeConnection খোলে আর ISN বয়ে আনে; একটা Sequence Number খরচ করে
ACKAcknowledgmentAcknowledgment Number কার্যকর; প্রথম SYN-এর পরের সব Segment-এ চালু
FINFinish“আমার আর পাঠানোর কিছু নেই”; এক দিক বন্ধ করে; একটা Sequence Number খরচ করে
RSTResetএখনই ভেঙে দাও: বন্ধ Port, Crash করা Application, অচেনা Connection
PSHPushএখনই Application-কে দাও; একটা Write-এর শেষ Segment-এ থাকে
URGUrgentUrgent Pointer কার্যকর; পুরনো জিনিস (Telnet Interrupt), নতুন Application ব্যবহার করে না
ECEECN EchoReceiver একটা “Congestion হচ্ছে” দাগ দেখেছে; গতি কমাও
CWRCongestion Window ReducedECE-র উত্তরে 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            |
+-------------------------------+-------------------------------+
FieldBitকী কাজ করে
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 তখনো চলছে, তাই উদ্ধার দ্রুত।

Sender198.51.100.20:443cwnd 8
ReceiverBrowser, Port 53000
seg 1 · seq 1001
ধাপ 1 / 10
1

Sender-এর cwnd = ৮ Segment, তাই প্রতিটা ACK-এর অপেক্ষা না করে একসাথে কয়েকটা পাঠায়।

Loss ধরা পড়ল যেভাবেকখন হয়দাম
৩টা Duplicate ACKStream-এর মাঝখানে একটা 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 সামনে সরে।

Sender198.51.100.20:443পথে 4000 / 4000
Receiver TCPBuffer ৪০০০ BBuffer 4000 / 4000
Receiver AppSocket থেকে পড়ে
seg 1–4 · 4000 B
ধাপ 1 / 6
1

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-তে)।

0204060800102030405060Round Trip →Segmentএর ওপরে Drop 60পাইপ (BDP) 40
Reno cwnd10ssthresh
ধাপ 1 / 7
1

নতুন Connection শুরু হয় ১০টা Segment-এর Initial Window দিয়ে (প্রায় ১৪ KB)। পথটা কত দ্রুত, এখনো কেউ জানে না।

#Reno বনাম CUBIC বনাম BBR

Reno হলো বইয়ের মডেল। আসল System চালায় তার উত্তরসূরিরা:

Reno / NewRenoCUBICBBR
সংকেতPacket LossPacket Lossমাপা Bandwidth আর সর্বনিম্ন RTT
বৃদ্ধিপ্রতি RTT-তে +১ Segmentশেষ Loss থেকে সময়ের ওপর একটা Cubic বক্ররেখা: পুরনো সর্বোচ্চ থেকে দূরে দ্রুত, কাছে চ্যাপ্টা, পার হলে আবার দ্রুতপথে প্রায় এক Bandwidth-delay Product রাখে, মাপা গতিতে Pacing করে; ১.২৫ গুণে খোঁজে, ০.৭৫ গুণে খালি করে
Loss-এ সাড়াঅর্ধেক৭০%-এ নামেআলাদা আলাদা Loss প্রায় পাত্তা দেয় না (প্রথম Version); পরের Version ভারী Loss-এও সাড়া দেয়
Router-এর Queueউপচে না পড়া পর্যন্ত ভরায়উপচে না পড়া পর্যন্ত ভরায়প্রায় খালি রাখার চেষ্টা করে (কম Latency)
কোথায়বই, পুরনো StackLinux (২০০৬ থেকে), 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

Link-এর Bandwidth
Round-trip Time
সবচেয়ে বড় Window (rwnd ও Send Buffer-এর ছোটটা)
এলোমেলো Packet Loss

তারে একটা Round Trip (1 ms)

পাঠাচ্ছে 100%বসে আছে, ACK-এর অপেক্ষা

Bandwidth-delay Product

125 KB

Window-এর সীমা (Window ÷ RTT)

34 Gbps

Loss-এর সীমা (Reno ধাঁচ, Mathis)

∞

একটা Connection পায়

1 Gbps

Link ব্যবহার

100 %

১০ GB কপি করতে সময়

1.3 min

i

১ 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 টাইপের সাথে সাথে পাঠায়।

Client Appwrite() ডাকে
Client TCPKernel
ServerTCP + App
write “l” (1 B)
ধাপ 1 / 6
1

একটা 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 চায়।

Client192.0.2.10
Server198.51.100.20:443
SYN · Fast Open Cookie চাই
ধাপ 1 / 3
1

খালি 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 (fq Queueing 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 ACKTCP_NODELAY, প্রতি বার্তায় কয়টা Write
দ্রুত Link-এ অঞ্চল পেরোনো কপি কয়েক Mbps-এ আটকেBDP-র চেয়ে ছোট Window বা Buffer, বা দূরের পথে Lossss -ti (rcv_space, cwnd, wscale), Buffer sysctl, BBR
Mobile বা Wi-Fi-তে Throughput ধসে পড়েরেডিওর Loss-এ সাড়া দেওয়া Loss-নির্ভর Congestion ControlBBR, HTTP/3
Page Load মাঝে মাঝে ~১ সেকেন্ড থেমে থাকেহারানো SYN বা RTO-র অপেক্ষায় থাকা শেষ SegmentCapture-এ আবার পাঠানো
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-এ নিয়ে গেল।