এই পর্বে যা শিখবেন
- যেকোনো segment-এর আকার দেওয়া থাকলে sequence আর ACK নম্বর হাতে হিসাব করা — পরীক্ষার সবচেয়ে প্রচলিত অঙ্ক
- MSS, segmentation, piggybacked ACK আর delayed ACK — কখন ACK যায়, কখন অপেক্ষা করে
- RTT মেপে RTO বের করা: SRTT, RTTVAR, Karn-এর নিয়ম, exponential backoff আর timestamps option
- Duplicate ACK, fast retransmit আর SACK — কেন TCP একই সাথে Go-Back-N আর Selective Repeat-এর মিশ্রণ
- Nagle আর delayed ACK-এর বিখ্যাত দেরি,
TCP_NODELAY, আর Linux-এরTCP_INFOদিয়ে আসল সংযোগের RTT ও RTO দেখা
১ · অনির্ভরযোগ্য পথের ওপর নির্ভরযোগ্য সেবা
আপনি বড় একটি ফাইল ডাউনলোড করছেন। হাজার হাজার packet সাগরতলের cable আর কয়েকটি দেশের router পার হয়ে আসছে। কোনো router-এর buffer ভরে গেলে সে নতুন packet চুপচাপ ফেলে দেয়; কোনো packet অন্য পথে পরে পৌঁছায়। তবু শেষে ফাইলের প্রতিটি byte ঠিক জায়গায়। কাজটি করে TCP.
নিচের স্তরের IP একটি best effort সেবা — “চেষ্টা করব, কথা দিচ্ছি না।” IP-র পথে চার রকম বিপদ ঘটতে পারে:
| বিপদ | কেন ঘটে | TCP-র হাতিয়ার |
|---|---|---|
| Packet হারানো | Router-এর queue ভরা, বেতারে নষ্ট হয়ে বাদ পড়া | ACK + timer + আবার পাঠানো (retransmission) |
| দুবার পৌঁছানো | ACK দেরিতে আসায় প্রেরক অকারণে আবার পাঠিয়েছে | Sequence নম্বর দেখে নকল বাদ |
| উল্টো ক্রমে পৌঁছানো | দুটি packet দুই পথে গেছে, বা এক পথে কোনোটি queue-তে আটকেছে | প্রাপকের buffer-এ সাজিয়ে রাখা |
| Bit বদলে যাওয়া | নষ্ট মেমরি, ত্রুটিপূর্ণ যন্ত্র, link-এর ধরা না পড়া ত্রুটি | Checksum না মিললে segment বাদ (তারপর আবার পাঠানো) |
পর্ব ১০-এ এক link-এর জন্য এই সমস্যার সমাধান দেখেছেন — ARQ: stop-and-wait, Go-Back-N আর Selective Repeat. TCP সেই ধারণাগুলোই ব্যবহার করে, কিন্তু তিনটি বড় পার্থক্য নিয়ে। প্রথমত TCP কাজ করে পুরো পথের দুই প্রান্তে — মাঝের router কিছুই জানে না। দ্বিতীয়ত পথের RTT জানা নেই, আর তা মুহূর্তে মুহূর্তে বদলায় — একই সংযোগে কখনো ২০ ms, কখনো ৪০০ ms. তৃতীয়ত TCP নম্বর দেয় byte-কে, frame-কে নয়।
একটি কুরিয়ার সার্ভিসের কথা ভাবুন, যারা একটি মোটা বই পাতা পাতা করে আলাদা খামে পাঠায়। প্রতিটি খামে লেখা থাকে “এই খামে পৃষ্ঠা ১০১ থেকে ১৫০”। প্রাপক রসিদে লেখেন “পৃষ্ঠা ১৫০ পর্যন্ত সব পেয়েছি, এবার ১৫১ চাই”। কোনো খাম হারালে রসিদে একই কথা বারবার আসে — “১৫১ চাই”। আর অনেকক্ষণ রসিদ না এলে অফিস ধরে নেয় খাম হারিয়েছে, আবার পাঠায়। কতক্ষণ অপেক্ষা করবে? ঢাকার ভেতরে এক ঘণ্টা, রাজশাহীতে এক দিন — পথ বুঝে। TCP-র পুরো নির্ভরযোগ্যতা এই কয়েকটি নিয়মেরই নিখুঁত রূপ।
নির্ভরযোগ্যতা কেন দুই প্রান্তে — end-to-end যুক্তি
প্রতিটি link নিখুঁত হলেও তা যথেষ্ট নয় — পর্ব ০৩-এর end-to-end principle. ডেটা নষ্ট হতে পারে router-এর মেমরিতে, যেখানে link-এর CRC নেই; router নিজেই packet ফেলে দিতে পারে। পুরো পথে সব ঠিক আছে কি না, নিশ্চিত হতে পারে শুধু দুই প্রান্ত। তাই link-এর ARQ (যেমন Wi-Fi-তে) গতি বাড়ানোর কৌশল, শেষ দায়িত্ব TCP-র। অংশ ৮-এ দেখবেন, যুক্তিটি আরও এক ধাপ ওপরে যায়।
১৯৭৪ সালে Vint Cerf আর Bob Kahn তাঁদের বিখ্যাত প্রবন্ধ “A Protocol for Packet Network Intercommunication”-এ যে protocol প্রস্তাব করেন, তাতেই byte-ভিত্তিক নম্বর আর window-এর ধারণা ছিল। পরে এটি দুই ভাগ হয় — IP আর TCP. ১৯৮১ সালে RFC 793 TCP-র মান হিসেবে প্রকাশিত হয়, আর চার দশকের সংশোধন একত্র করে ২০২২ সালে এসেছে RFC 9293. Timeout-এর হিসাব আলাদা মান হিসেবে আছে RFC 6298-এ, আর ACK আর আবার পাঠানোর অনেক নিয়ম RFC 5681-এ।
২ · Byte-stream আর sequence নম্বর
TCP-র কাছে application-এর ডেটা কোনো “বার্তা” নয় — একটি অবিরাম byte-stream, যেন একটি লম্বা ফিতা। Application তিনবার send() করে ১০০, ২০০ আর ৩০০ byte লিখলে TCP সেগুলো একটি ৬০০ byte-এর segment-এ পাঠাতে পারে, বা অন্যভাবে ভাগ করতে পারে; প্রাপকের একটি recv()-এ আসতে পারে ৬০০, বা ৫০ — বার্তার সীমানা TCP রাখে না (পর্ব ৪১-এ socket প্রোগ্রামিংয়ে এটি খুব জরুরি)। ফিতার প্রতিটি byte-এর একটি নম্বর আছে।
MSS আর segmentation
ফিতা কেটে কেটে segment বানানোকে বলে segmentation. একটি segment-এ সর্বোচ্চ কত byte ডেটা যাবে, তা MSS ঠিক করে। IP packet যাতে link-এর MTU-তে আঁটে, সেজন্য Ethernet-এ (MTU ১৫০০) IPv4-এর ২০ byte আর TCP-র ২০ byte header বাদ দিয়ে MSS = ১৫০০ − ৪০ = ১৪৬০ byte; IPv6-এ (৪০ byte header) ১৪৪০. সংযোগ শুরুর সময় দুই পক্ষ SYN segment-এর option-এ নিজের MSS জানায় (পর্ব ২৯)। Option না এলে IPv4-এ ধরে নেওয়া হয় ৫৩৬ byte. আর প্রতিটি segment-এ timestamps-এর মতো option থাকলে আসল ডেটা আরও কমে — যেমন ১২ byte option থাকলে ১৪৪৮.
দুটি নম্বরের সংজ্ঞা — পরীক্ষার মূল কথা
Sequence number = segment-এর প্রথম ডেটা byte-এর নম্বর। seq = S আর ডেটা L byte হলে segment বহন করে byte S থেকে S + L − 1, আর একই দিকের পরের segment-এর seq হবে S + L.
Acknowledgement number = প্রাপক পরের যে byte-টি আশা করছে তার নম্বর। “ACK 1501” মানে “1500 পর্যন্ত সব পেয়েছি, এবার 1501 চাই”। এটি cumulative ACK — মাঝে একটি byte না এলে তার পরের যত ডেটাই আসুক, ACK নম্বর ওই ফাঁকেই আটকে থাকে।
শুরুর নম্বর শূন্য নয়। প্রতিটি সংযোগ একটি এলোমেলো initial sequence number (ISN) দিয়ে শুরু হয় — পুরোনো সংযোগের দেরিতে আসা segment যাতে নতুন সংযোগে মিশে না যায়, আর বাইরের কেউ যাতে নম্বর আন্দাজ করে নকল segment ঢোকাতে না পারে (RFC 6528). SYN নিজে একটি নম্বর খরচ করে, তাই ISN = x হলে প্রথম ডেটা byte-এর নম্বর x + 1; FIN-ও একটি নম্বর খরচ করে (পর্ব ২৯)। Wireshark তাই ডিফল্টভাবে ISN-কে ০ ধরে “relative” নম্বর দেখায়। নম্বর ৩২ bit-এর, ২৩২-এর পর ঘুরে আবার ০ (অনুশীলন ৪)।
নিচের stepper-এ A (client) আর B (server)-এর সংযোগ ইতিমধ্যে তৈরি। A-র ISN 1000, তাই A-র প্রথম ডেটা byte 1001; B-র ISN 5000, তাই B-র প্রথম ডেটা byte 5001. A চারটি segment পাঠায় — ৫০০, ১০০০, ৩০০ আর ২০০ byte; মাঝে B-ও ২০০ byte উত্তর পাঠায়। প্রতিটি ধাপে seq আর ack নিজে হিসাব করে তারপর মিলিয়ে দেখুন।
| দিক | |
| seq | |
| ডেটা (byte) | |
| বহন করা byte | |
| ack | |
| A-র পরের seq · B চায় |
- প্রথম segment. A পাঠাল seq = 1001, ৫০০ byte — অর্থাৎ byte 1001 থেকে 1500. A এখনো B-র কোনো ডেটা পায়নি, তাই A-র ack ঘরে আছে 5001 (B-র প্রথম ডেটা byte — B-র SYN-এর পরের নম্বর)। A-র পরের seq হবে 1001 + 500 = 1501.
- খালি ACK. B সব পেয়েছে, তাই ack = 1501 — “1500 পর্যন্ত পেয়েছি, এবার 1501 চাই”। এই segment-এ ডেটা নেই; খালি ACK কোনো sequence নম্বর খরচ করে না, তাই B-র seq এখনো 5001. খেয়াল করুন: ACK নম্বর শেষ পাওয়া byte (1500) নয়, তার পরেরটি।
- দ্বিতীয় segment. seq = 1501, ১০০০ byte: byte 1501–2500. পরের seq = 2501. B এবার সঙ্গে সঙ্গে ACK পাঠাল না — সে delayed ACK-এর অপেক্ষায় (অংশ ৩)।
- তৃতীয় segment. seq = 2501, ৩০০ byte: byte 2501–2800. B-র কাছে এখন দুটি অস্বীকৃত segment, কিন্তু তার application উত্তর লিখে ফেলেছে — তাই আলাদা ACK না পাঠিয়ে উত্তরের সাথেই পাঠাবে।
- Piggybacked ACK. B পাঠাল নিজের ২০০ byte ডেটা (seq = 5001, byte 5001–5200), আর একই segment-এর ack ঘরে 2801 — দুটি segment-এর প্রাপ্তিস্বীকার একসাথে, ডেটার পিঠে চড়ে। একটি ACK দিয়ে আগের সব byte স্বীকার — এটাই cumulative ACK-এর সুবিধা।
- চতুর্থ segment. A পাঠাল seq = 2801, ২০০ byte (2801–3000), আর ack = 5201 — B-র ২০০ byte (5001–5200) পেয়েছে, এবার 5201 চায়।
- শেষ ACK. B পাঠাল ack = 3001; তার seq 5201, কারণ B-র পরের ডেটা byte এটিই। মোট: A পাঠাল ৫০০ + ১০০০ + ৩০০ + ২০০ = ২০০০ byte (1001–3000), আর শেষ ACK = 1001 + 2000 = 3001. যাচাইয়ের সহজ সূত্র: শেষ ACK = প্রথম seq + মোট byte.
একটি application একবারে ১০,০০০ byte লিখল। MSS = ১৪৬০, আর প্রথম ডেটা byte-এর নম্বর 100000. কয়টি segment, প্রতিটির seq, আর সব পৌঁছালে শেষ ACK কত?
- ১০০০০ ÷ ১৪৬০ = ৬.৮৫ ⇒ ৬টি পূর্ণ segment (৬ × ১৪৬০ = ৮৭৬০ byte) + শেষটিতে ১০০০০ − ৮৭৬০ = ১২৪০ byte — মোট ৭টি.
- seq: 100000, 101460, 102920, 104380, 105840, 107300, 108760 (প্রতিবার +১৪৬০)।
- শেষ segment বহন করে 108760 থেকে 109999; শেষ ACK = 100000 + 10000 = 110000.
৩ · ACK-এর কৌশল — cumulative, piggybacked আর delayed
Cumulative ACK-এর সুবিধা: একটি ACK হারালেও ক্ষতি নেই — “3001 চাই” মানে 3000 পর্যন্ত সব পৌঁছেছে। কিন্তু প্রতিটি segment-এর আলাদা ACK মানে উল্টো পথে অনেক ৪০ byte-এর প্রায় ফাঁকা packet. তাই TCP দুটি কৌশলে ACK কমায়।
Piggybacking
দুই দিকেই ডেটা চললে ACK আলাদা segment-এ না গিয়ে উল্টো দিকের ডেটার header-এ বসে যায় — উপরের stepper-এর ধাপ ৫. পর্ব ১০-এ এটিকে বলা হয়েছিল piggybacking. TCP-র প্রতিটি segment-এ (প্রথম SYN ছাড়া) ACK ঘর বৈধ থাকে, তাই piggybacking এখানে স্বাভাবিক নিয়ম।
Delayed ACK
উত্তর আসতে যদি একটু সময় লাগে? Delayed ACK-এ প্রাপক ACK পাঠাতে একটু অপেক্ষা করে — হয়তো এর মধ্যে আরেকটি segment আসবে (দুটির জন্য একটি ACK), বা application উত্তর লিখবে (ACK তার পিঠে যাবে)। নিয়মের সীমা RFC 1122 আর RFC 5681-এ:
- টানা পূর্ণ আকারের segment এলে অন্তত প্রতি দুটি segment-এ একটি ACK পাঠানো উচিত।
- প্রথম অস্বীকৃত segment আসার পর ACK ৫০০ ms-এর মধ্যে যেতেই হবে।
- ক্রমের বাইরের segment এলে, বা কোনো segment ফাঁক পূরণ করলে, ACK সঙ্গে সঙ্গে পাঠানো উচিত — দেরি নয়। (এই নিয়ম থেকেই duplicate ACK-এর জন্ম — নিচে।)
বাস্তবের মান OS আর সংস্করণভেদে আলাদা। Linux-এ timer সাধারণত ৪০ ms থেকে ট্রাফিক দেখে ২০০ ms পর্যন্ত ওঠানামা করে, আর সংযোগের শুরুতে “quick ACK” mode-এ ACK সঙ্গে সঙ্গে যায়। Windows-এ ডিফল্ট প্রচলিতভাবে ২০০ ms. সংখ্যা মুখস্থ না করে মাপা ভালো (অংশ ৮-এর C প্রোগ্রাম Linux-এ মানটি দেখায়)।
ক্রমের বাইরের segment আর duplicate ACK
ধরুন byte 1001–2000 হারাল, কিন্তু 2001–3000 পৌঁছাল। Go-Back-N-এর প্রাপক এটি ফেলে দিত। TCP-র প্রাপক সাধারণত ফেলে না — ক্রমের বাইরের (out-of-order) ডেটা নিজের receive buffer-এ রেখে দেয়, কিন্তু application-কে দেয় না (application শুধু ফাঁকহীন ধারাবাহিক byte পায়)। আর সঙ্গে সঙ্গে ACK পাঠায় — কিন্তু ACK নম্বর আটকে আছে 1001-এ, কারণ 1001 এখনো আসেনি। প্রেরক তাই আগের ACK-টিই আবার দেখে: একে বলে duplicate ACK (dup ACK). প্রতিটি dup ACK প্রেরককে বলে, “1001 এখনো পাইনি, কিন্তু পরের কিছু একটা পেয়েছি।” পথ খোলা আছে, শুধু একটি টুকরো নেই — অংশ ৬-এ এই সংকেত কাজে লাগবে।
RFC-গুলো প্রাপককে ক্রমের বাইরের ডেটা রাখতে বাধ্য করে না — এটি দৃঢ় সুপারিশ, আর বাস্তবের সব প্রধান OS তা মানে। পরীক্ষায় “TCP-র প্রাপক ক্রমের বাইরের segment ফেলে দেয়” লিখলে সাধারণত ভুল ধরা হবে; আধুনিক TCP buffer-এ রাখে।
৪ · Nagle আর delayed ACK — ছোট লেখার ফাঁদ
১৯৮০-র দশকে Telnet-এর মতো প্রোগ্রাম প্রতিটি কী-চাপ আলাদা segment-এ পাঠাত: ১ byte ডেটা, সাথে ৪০ byte header — ৪০০০% বাড়তি বোঝা। ধীর লাইনে এমন হাজার হাজার ক্ষুদ্র packet নেটওয়ার্ক অচল করে দিত। ১৯৮৪ সালে John Nagle RFC 896-এ সমাধান দেন, যা Nagle-এর algorithm নামে পরিচিত:
জমিয়ে রাখুন, যতক্ষণ না ACK আসে বা পুরো একটি MSS ভরে।
ফল: এক RTT-তে ছোট segment বড়জোর একটি; ধীর পথে অনেক কী-চাপ জমে এক segment-এ যায়। চমৎকার নিয়ম — যতক্ষণ না অপর প্রান্তে delayed ACK বসে থাকে।
বিখ্যাত ২০০ ms দেরি
একটি client request পাঠায় দুই ধাপে: আগে write()-এ ১০০ byte header, তারপর আরেকটি write()-এ ৫০ byte body. Server পুরো ১৫০ byte না পেলে উত্তর দেয় না। এবার দেখুন কী ঘটে:
- প্রথম ১০০ byte সঙ্গে সঙ্গে যায় (পথে কিছু অস্বীকৃত নেই)।
- দ্বিতীয় ৫০ byte ছোট, আর আগের ১০০ byte-এর ACK আসেনি — Nagle এটি আটকে রাখে।
- Server ১০০ byte পেয়েছে, কিন্তু request অসম্পূর্ণ — উত্তর নেই, তাই piggyback-এর সুযোগও নেই। দুটি পূর্ণ segment-ও আসেনি। তাই সে delayed ACK-এর timer ফুরানো পর্যন্ত বসে থাকে।
- দুজনেই অপরজনের অপেক্ষায় — একটি ছোটখাটো “অচলাবস্থা”, যা ভাঙে শুধু timer ফুরালে।
নিচের প্রোগ্রাম এই সময়রেখাটি হিসাব করে — LAN, এক দিকের delay ১ ms, চার রকম পরিস্থিতিতে:
// Client: write(100-byte header), write(50-byte body), then waits for the reply.
// Server: replies only after the whole 150-byte request has arrived. One-way delay 1 ms (LAN).
// Server uses delayed ACK; small segments never make "two full-sized segments",
// so its ACK leaves only when the timer fires or piggybacked on the reply.
function run(title, nagle, writes, delack) {
const d = 1, need = 150, log = [], at = (ms, s) => log.push(String(ms).padStart(5) + " ms " + s);
let got = 0, arrive = 0, inFlight = false, held = 0;
for (const w of writes) {
if (nagle && inFlight) { held += w; at(0, `Nagle holds ${w} B (earlier data not yet ACKed)`); continue; }
at(0, `client sends ${w} B`); arrive = d; got += w; inFlight = true;
}
if (got < need) { // server has half a request: no reply to piggyback on
at(arrive, `server got ${got} B, waits for the rest; delayed-ACK timer starts`);
const fire = arrive + delack;
at(fire, "timer fires -> server sends a pure ACK");
at(fire + d, `ACK reaches client -> Nagle releases ${held} B`);
arrive = fire + 2 * d; got += held;
}
at(arrive, `server has all ${got} B -> sends reply (ACK piggybacked)`);
at(arrive + d, "client gets the reply");
console.log(`== ${title}: ${arrive + d} ms\n` + log.join("\n") + "\n");
}
run("Nagle on, two writes, 200 ms delayed ACK (Windows-like)", true, [100, 50], 200);
run("Nagle on, two writes, 40 ms delayed ACK (Linux minimum)", true, [100, 50], 40);
run("TCP_NODELAY, two writes", false, [100, 50], 200);
run("Nagle on, ONE write of 150 B (build the buffer, or writev)", true, [150], 200);আউটপুট:
== Nagle on, two writes, 200 ms delayed ACK (Windows-like): 204 ms
0 ms client sends 100 B
0 ms Nagle holds 50 B (earlier data not yet ACKed)
1 ms server got 100 B, waits for the rest; delayed-ACK timer starts
201 ms timer fires -> server sends a pure ACK
202 ms ACK reaches client -> Nagle releases 50 B
203 ms server has all 150 B -> sends reply (ACK piggybacked)
204 ms client gets the reply
== Nagle on, two writes, 40 ms delayed ACK (Linux minimum): 44 ms
0 ms client sends 100 B
0 ms Nagle holds 50 B (earlier data not yet ACKed)
1 ms server got 100 B, waits for the rest; delayed-ACK timer starts
41 ms timer fires -> server sends a pure ACK
42 ms ACK reaches client -> Nagle releases 50 B
43 ms server has all 150 B -> sends reply (ACK piggybacked)
44 ms client gets the reply
== TCP_NODELAY, two writes: 2 ms
0 ms client sends 100 B
0 ms client sends 50 B
1 ms server has all 150 B -> sends reply (ACK piggybacked)
2 ms client gets the reply
== Nagle on, ONE write of 150 B (build the buffer, or writev): 2 ms
0 ms client sends 150 B
1 ms server has all 150 B -> sends reply (ACK piggybacked)
2 ms client gets the reply
পড়ুন সংখ্যাগুলো: যে request ২ ms-এ শেষ হওয়ার কথা, Nagle আর ২০০ ms delayed ACK মিলে তা নিল ২০৪ ms — একশো গুণ। ৪০ ms timer-এ (Linux-এর সর্বনিম্ন মান) দেরি ৪৪ ms. পুরো দেরিটা আসলে delayed-ACK timer; নেটওয়ার্ক মাত্র ৪ ms নিয়েছে। শেষ দুটি পরিস্থিতিতে দেরি নেই: TCP_NODELAY দিলে দ্বিতীয় টুকরো আটকায় না, আর পুরো request একবারে লিখলে (বা writev() দিয়ে কয়েকটি buffer একটি call-এ) আটকানোর মতো দ্বিতীয় টুকরোই থাকে না।
মডেলটি সরল (আসল Linux-এ quick ACK mode আর বদলাতে থাকা timer আছে), কিন্তু “write-write-read” ধাঁচের প্রোগ্রামে হঠাৎ ঠিক ৪০ বা ২০০ ms দেরি — বাস্তবের খুব পরিচিত সমস্যা।
সমাধান — কোনটি কখন
- একবারে লেখা: request পুরোটা একটি buffer-এ সাজিয়ে একটি
send(), বাwritev(). সবচেয়ে পরিষ্কার সমাধান — অতিরিক্ত ছোট packet-ও হয় না। - TCP_NODELAY:
setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof one)দিয়ে Nagle বন্ধ। Interactive আর request-response প্রোগ্রামে (RPC, ডেটাবেস client, গেম, SSH-এর মতো interactive session) খুব প্রচলিত; Go ভাষার standard library TCP সংযোগে এটি ডিফল্টভাবেই চালু রাখে। সাবধান: বন্ধ করলে প্রতিটি ছোটwrite()আলাদা ছোট packet হয়ে যেতে পারে — ঠিক যে সমস্যা Nagle ঠেকাত। - Linux-এর বিশেষ option:
TCP_CORK(জমিয়ে রেখে পরে একসাথে ছাড়া) আরTCP_QUICKACK(প্রাপকের দিকে delayed ACK সাময়িক বন্ধ) — দুটোই Linux-নির্দিষ্ট, অন্য OS-এ নাম বা আচরণ ভিন্ন।
৫ · Timeout কত হবে — RTT মেপে RTO
প্রতিটি অস্বীকৃত ডেটার জন্য প্রেরক একটি timer চালায়; সময়মতো ACK না এলে timeout, আর আবার পাঠানো। এই অপেক্ষার সময়কে বলে RTO (retransmission timeout). কঠিন প্রশ্নটি হলো — কত?
- খুব ছোট RTO: ACK পথে আছে, কিন্তু প্রেরক অধৈর্য হয়ে আবার পাঠাল — অকারণ retransmission, নেটওয়ার্কে বাড়তি বোঝা, আর পর্ব ৩২-এ দেখবেন, অকারণে গতি কমানো।
- খুব বড় RTO: segment সত্যিই হারিয়েছে, কিন্তু প্রেরক বসে আছে — ব্যবহারকারী দেখেন পাতা আটকে গেছে।
এক link-এ (পর্ব ১০) RTT প্রায় স্থির। ইন্টারনেটে একই সংযোগের RTT queue-র ভিড়ে মুহূর্তে বদলায় — ঢাকা থেকে সিঙ্গাপুরে হয়তো ৪০–৬০ ms, আমেরিকায় ২০০ ms-এর বেশি। তাই TCP প্রতিটি সংযোগে RTT মেপে RTO হিসাব করে।
নমুনা থেকে গড়: SRTT
একটি segment পাঠানোর সময় থেকে তার ACK আসা পর্যন্ত সময় একটি নমুনা — SampleRTT (RFC-তে R). একটি নমুনায় ভরসা করা যায় না, তাই TCP একটি চলমান গড় রাখে — EWMA (exponentially weighted moving average):
(Kurose-Ross-এর পাঠ্যবইয়ে নাম EstimatedRTT; RFC 6298-এ SRTT — একই জিনিস।) কেন “exponential”? সূত্রটি বারবার বসালে পাই: SRTTn = α·Rn + α(1 − α)·Rn−1 + α(1 − α)2·Rn−2 + … অর্থাৎ k ধাপ পুরোনো নমুনার ওজন α(1 − α)k — প্রতি ধাপে ⅞ গুণ কমে। সব ওজনের যোগফল ১ (একটি গুণোত্তর ধারা: α/(1 − (1 − α)) = 1). মোটামুটি শেষ ৮টি নমুনা (১/α) প্রভাব রাখে; পুরোনোগুলো ধীরে মুছে যায়। আর ৮ দিয়ে ভাগ মানে ৩ bit শিফট — পুরোনো যন্ত্রে দশমিক গুণ ছাড়াই হিসাব।
ওঠানামাও মাপা: RTTVAR
শুধু গড় দিয়ে RTO ঠিক করলে বিপদ: RTT বেশি ওঠানামা করলে অনেক নমুনাই গড়ের ওপরে যাবে, আর প্রতিবার ভুল timeout. তাই TCP গড় থেকে বিচ্যুতিরও চলমান গড় রাখে:
RTO = SRTT + 4·RTTVAR
(পাঠ্যবইয়ে RTTVAR-এর নাম DevRTT.) কেন ৪ গুণ? RTO-কে প্রায় সব স্বাভাবিক RTT-র চেয়ে বড় রাখতে। RTT মোটামুটি normal বণ্টনের হলে mean deviation ≈ ০.৮σ, তাই 4·RTTVAR ≈ ৩.২σ — গড়ের এত ওপরে স্বাভাবিক নমুনা খুব কমই যায়। Standard deviation-এর বদলে mean deviation, কারণ বর্গমূল লাগে না। সূত্রটি ১৯৮৮ সালে Van Jacobson-এর প্রস্তাব (ভাবনার প্রশ্ন ২)।
- শুরুতে (কোনো নমুনা নেই): RTO = ১ সেকেন্ড (আগের RFC 2988-এ ছিল ৩ সেকেন্ড)।
- প্রথম নমুনা R: SRTT = R, RTTVAR = R/2, RTO = SRTT + 4·RTTVAR (= ৩R).
- পরের নমুনাগুলো: আগে RTTVAR (পুরোনো SRTT দিয়ে), তারপর SRTT, তারপর RTO.
- সর্বনিম্ন: হিসাবের RTO ১ সেকেন্ডের কম হলে ১ সেকেন্ড ধরা উচিত (SHOULD); সর্বোচ্চ সীমা রাখা যায়, তবে তা অন্তত ৬০ সেকেন্ড।
- Timeout হলে: RTO দ্বিগুণ (backoff), আর সেই segment আবার।
বাস্তব আর মান এখানে আলাদা। Linux সর্বনিম্ন হিসেবে ২০০ ms ব্যবহার করে (আর ১ সেকেন্ডের নিয়ম মানে না), আর তার হিসাব মোটামুটি RTO ≈ SRTT + max(4·RTTVAR, ২০০ ms) — তাই কম RTT-র পথে Linux-এর RTO প্রায় RTT + ২০০ ms. সর্বোচ্চ সীমা Linux-এ ১২০ সেকেন্ড। অন্য OS-এর সর্বনিম্ন মান ভিন্ন হতে পারে; সংস্করণভেদে বদলায়ও। পরীক্ষায় RFC-র মান লিখুন, আর বলুন বাস্তবে OS-ভেদে আলাদা।
ক্রমের ফাঁদ: RFC 6298 বলে RTTVAR আগে, পুরোনো SRTT দিয়ে। কিছু পাঠ্যবই আগে EstimatedRTT হালনাগাদ করে, তারপর নতুন মান দিয়ে DevRTT. দুই পদ্ধতিতে উত্তর সামান্য আলাদা আসে (অনুশীলন ২-এ দুটোই দেখানো আছে)। প্রশ্নে যে ক্রম বা সূত্র দেওয়া থাকে সেটিই মানুন, আর উত্তরে নিজের ক্রম লিখে দিন।
নিচের stepper RFC 6298 মেনে তিনটি নমুনা (৫০, ৬০, ৪০ ms) থেকে RTO বের করে — তারপর একটি আবার পাঠানো segment আর একটি timeout. প্রতিটি সংখ্যা ক্যালকুলেটরে নিজে মিলিয়ে নিন।
| ঘটনা | |
| RTTVAR | |
| SRTT | |
| SRTT + 4·RTTVAR | |
| RTO (RFC, ন্যূনতম ১০০০) |
- শুরু। সংযোগে এখনো কোনো RTT মাপা হয়নি। RFC 6298 বলে শুরুর RTO ১ সেকেন্ড — অজানা পথে সাবধানী একটি অনুমান।
- প্রথম নমুনা ৫০ ms. বিশেষ নিয়ম: SRTT = R = ৫০, RTTVAR = R/2 = ২৫. RTO = ৫০ + ৪ × ২৫ = ১৫০ ms. কিন্তু ১ সেকেন্ডের কম, তাই RFC মেনে RTO = ১০০০ ms (Linux-এর মতো ২০০ ms নিয়মে হতো ৫০ + max(১০০, ২০০) = ২৫০)।
- দ্বিতীয় নমুনা ৬০ ms. আগে RTTVAR, পুরোনো SRTT (৫০) দিয়ে: |৫০ − ৬০| = ১০, তাই ০.৭৫ × ২৫ + ০.২৫ × ১০ = ১৮.৭৫ + ২.৫ = ২১.২৫. তারপর SRTT = ০.৮৭৫ × ৫০ + ০.১২৫ × ৬০ = ৪৩.৭৫ + ৭.৫ = ৫১.২৫. RTO = ৫১.২৫ + ৮৫ = ১৩৬.২৫ → ন্যূনতম নিয়মে ১০০০.
- তৃতীয় নমুনা ৪০ ms. |৫১.২৫ − ৪০| = ১১.২৫; RTTVAR = ১৫.৯৩৭৫ + ২.৮১২৫ = ১৮.৭৫. SRTT = ৪৪.৮৪৩৭৫ + ৫ = ৪৯.৮৪৪ (প্রায় ৪৯.৮৪)। RTO = ৪৯.৮৪ + ৭৫ = ১২৪.৮৪ ms. খেয়াল করুন: নমুনাগুলো যত কাছাকাছি, RTTVAR তত কমে, আর RTO গড়ের কাছে নামে।
- Karn-এর নিয়ম। একটি segment আবার পাঠাতে হয়েছিল; তার ACK এলো প্রথম পাঠানোর ১৮০ ms পরে। কিন্তু ACK-টি কোন কপির — প্রথম না দ্বিতীয়? জানার উপায় নেই। তাই এই নমুনা বাদ: SRTT আর RTTVAR অপরিবর্তিত।
- Timeout আর backoff. একটি segment-এর timer ফুরাল। প্রেরক সেটি আবার পাঠায় আর RTO দ্বিগুণ করে: ১০০০ → ২০০০ ms. আবার ফুরালে ৪০০০, তারপর ৮০০০ … নতুন বৈধ নমুনা (আবার না পাঠানো segment থেকে) এলে তবেই RTO আবার সূত্র থেকে হিসাব হয়।
Karn-এর algorithm — দ্ব্যর্থক নমুনা বাদ
উপরের ধাপ ৫-এর সমস্যাটি গভীর। একটি segment পাঠানো হলো, timeout হলো, আবার পাঠানো হলো, তারপর ACK এলো। ACK-টি প্রথম কপির হলে আসল RTT লম্বা; দ্বিতীয়টির হলে খুব ছোট। ভুল ধরলে দুই দিকেই বিপদ — RTO অকারণে বাড়তে থাকে, অথবা এত কমে যে আরও ভুল timeout. ১৯৮৭ সালে Phil Karn আর Craig Partridge সমাধান দেন, যা Karn-এর algorithm নামে পরিচিত: (১) আবার পাঠানো segment-এর RTT নমুনা কখনো নেওয়া হবে না; (২) backoff করা RTO রেখে দেওয়া হবে, যতক্ষণ না একটি আবার-না-পাঠানো segment-এর বৈধ নমুনা আসে।
Exponential backoff
একই segment বারবার timeout হলে প্রতিবার RTO দ্বিগুণ — exponential backoff. বারবার হারানো মানে সম্ভবত প্রচণ্ড ভিড়; সবাই একই তালে আবার পাঠাতে থাকলে ভিড় কাটে না। কতবার চেষ্টার পর সংযোগ ছেড়ে দেওয়া হবে, তা OS-এর সেটিং — Linux-এ net.ipv4.tcp_retries2 (ডিফল্ট ১৫, যা kernel-এর নথি অনুযায়ী প্রায় ১৫ মিনিটের সমান)।
Timestamps option — প্রতিটি ACK থেকে নমুনা
পুরোনো পদ্ধতিতে প্রতি RTT-তে মোটামুটি একটি segment-এর সময় মাপা হতো। RFC 7323-এর timestamps option (প্রতি segment-এ ১০ byte: TSval আর TSecr) এটি বদলে দিয়েছে: প্রেরক নিজের ঘড়ির মান TSval-এ লেখে, প্রাপক ACK-এ সেটি TSecr-এ ফেরত দেয়। ACK আসামাত্র “এখন − TSecr” = RTT — প্রতিটি ACK থেকে নমুনা, আর ফেরত মান বলে দেয় কোন কপির ACK, তাই আবার পাঠানোর পরের দ্ব্যর্থতাও অনেকটা কাটে। এটি sequence নম্বর ঘুরে আসার বিপদও ঠেকায় (PAWS, অনুশীলন ৪)। Linux-এ timestamps ডিফল্টভাবে চালু; সব OS-এ নয়।
নিচের প্রোগ্রাম সাতটি নমুনা নিয়ে RFC 6298 চালায় — চতুর্থটি আবার পাঠানো segment-এর, তাই Karn-এর নিয়মে বাদ। পাশে তুলনার জন্য Linux-ধাঁচের হিসাব (সরলীকৃত), আর শেষে বারবার timeout হলে RTO-র দ্বিগুণ হওয়া:
// RTO estimation the RFC 6298 way (alpha = 1/8, beta = 1/4, K = 4). Times in ms.
// RFC 6298: RTO >= 1 s. Linux-like: SRTT + max(4*RTTVAR, 200 ms) (simplified).
// Each sample: [measured RTT, was this segment retransmitted?]
const samples = [[50, false], [60, false], [40, false], [180, true],
[64, false], [48, false], [52, false]];
const a = 1 / 8, b = 1 / 4, K = 4;
let srtt = null, rttvar = null;
const f = x => x.toFixed(1).padStart(7);
console.log(" # Sample | SRTT RTTVAR | raw RTO RFC 6298 Linux-like");
samples.forEach(([r, retx], i) => {
const n = String(i + 1).padStart(2) + String(r).padStart(7) + " | ";
if (retx) { console.log(n + "skipped: retransmitted segment (Karn's rule)"); return; }
if (srtt === null) { srtt = r; rttvar = r / 2; } // first measurement
else {
rttvar = (1 - b) * rttvar + b * Math.abs(srtt - r); // uses the OLD srtt
srtt = (1 - a) * srtt + a * r;
}
const rto = srtt + K * rttvar;
console.log(n + f(srtt) + f(rttvar) + " | " + f(rto) +
f(Math.max(rto, 1000)).padStart(10) + f(srtt + Math.max(K * rttvar, 200)).padStart(12));
});
// The same segment times out again and again: RTO doubles each time (exponential backoff).
let rto = Math.max(srtt + K * rttvar, 1000);
const steps = [];
for (let k = 1; k <= 6; k++) { steps.push(rto); rto = Math.min(2 * rto, 60000); }
console.log("\nBackoff (RFC, cap 60 s): " + steps.map(x => (x / 1000).toFixed(2) + " s").join(" -> "));
console.log("Total waiting in these 6 timeouts: " + (steps.reduce((s, x) => s + x, 0) / 1000).toFixed(2) + " s");আউটপুট:
# Sample | SRTT RTTVAR | raw RTO RFC 6298 Linux-like
1 50 | 50.0 25.0 | 150.0 1000.0 250.0
2 60 | 51.3 21.3 | 136.3 1000.0 251.3
3 40 | 49.8 18.8 | 124.8 1000.0 249.8
4 180 | skipped: retransmitted segment (Karn's rule)
5 64 | 51.6 17.6 | 122.0 1000.0 251.6
6 48 | 51.2 14.1 | 107.6 1000.0 251.2
7 52 | 51.3 10.8 | 94.4 1000.0 251.3
Backoff (RFC, cap 60 s): 1.00 s -> 2.00 s -> 4.00 s -> 8.00 s -> 16.00 s -> 32.00 s
Total waiting in these 6 timeouts: 63.00 s
প্রথম তিন সারি stepper-এর সংখ্যাই (১৫০, ১৩৬.৩, ১২৪.৮)। সারি ৪-এ ১৮০ ms-এর নমুনা বাদ গেছে — নিলে SRTT এক লাফে বাড়ত, RTTVAR প্রায় দ্বিগুণ হতো। সারি ৫–৭: নমুনা ৪৮–৬৪ ms-এ স্থির থাকায় RTTVAR ১৭.৬ থেকে ১০.৮-এ নামছে, আর raw RTO ১২২ থেকে ৯৪.৪ ms. কিন্তু RFC কলামে সবসময় ১০০০ — এই পথে ১ সেকেন্ডের ন্যূনতম সীমাই সব ঠিক করছে, অর্থাৎ একটি segment হারালে timeout পর্যন্ত RTT-র প্রায় ২০ গুণ অপেক্ষা। Linux-ধাঁচের কলাম প্রায় ২৫০ ms — SRTT + ২০০. এজন্যই অংশ ৬-এর fast retransmit এত গুরুত্বপূর্ণ। শেষ দুই লাইন: ছয়বার পরপর timeout হলে অপেক্ষা ১ + ২ + ৪ + ৮ + ১৬ + ৩২ = ৬৩ সেকেন্ড।
৬ · Fast retransmit — timeout-এর আগেই হারানো ধরা
অংশ ৫-এর হিসাব দেখাল, RTO প্রায়ই RTT-র কয়েক গুণ — RFC মানলে অন্তত ১ সেকেন্ড। প্রতিটি loss-এ এতক্ষণ বসে থাকা মানে বিশাল অপচয়। কিন্তু প্রেরকের হাতে একটি আগাম সংকেত আছে: duplicate ACK. প্রাপক ক্রমের বাইরের প্রতিটি segment পেয়ে একই ACK নম্বর আবার পাঠায়। একই নম্বর বারবার আসা মানে — পরের segment-গুলো পৌঁছাচ্ছে, শুধু একটি নেই।
একই ACK নম্বরের ৩টি duplicate ACK এলে (অর্থাৎ মূল ACK-সহ মোট ৪টি একই ACK) প্রেরক ধরে নেয় ওই নম্বর থেকে শুরু হওয়া segment হারিয়েছে, আর timer ফুরানোর অপেক্ষা না করে সেটি সঙ্গে সঙ্গে আবার পাঠায়। এটিই fast retransmit.
কেন ঠিক ৩? Duplicate ACK-এর একটি নিরীহ কারণও আছে — reordering: দুটি segment জায়গা বদল করে এলে একটি dup ACK যায়, তারপর “হারানো” segment এসে পড়ে। ১–২টি dup ACK প্রায়ই এমন ক্রম-বদল; ৩টি হলে loss-এর সম্ভাবনা বেশি। সংখ্যাটি প্রমাণ নয়, ১৯৮০-র দশকের শেষে BSD Unix-এর TCP-তে চালু হওয়া একটি পরীক্ষিত আপস। দুর্বলতাও আছে: loss-এর পরে অন্তত তিনটি segment পাঠানো না থাকলে dup ACK আসেই না — যেমন ছোট transfer-এর শেষ segment হারালে। তখন timeout ছাড়া উপায় নেই (অংশ ৮-এ আধুনিক সমাধান)।
Loss ধরা পড়লে প্রেরক কতটা গতি কমাবে — congestion window-এর প্রতিক্রিয়া, fast recovery — সেটি পর্ব ৩২-এর বিষয়। এখানে শুধু loss শনাক্তের দিকটি। নিচের stepper-এ ৬টি segment (MSS ১০০০ byte, relative seq 1 থেকে), এক দিকের delay ২ একক, আর byte 1001–2000-এর segment হারায় (অংশ ৭-এর simulator-এর প্রথম রান হুবহু):
| সময় t | |
| প্রেরক পাঠাল | |
| প্রাপক পেল | |
| প্রাপকের buffer | |
| প্রাপক পাঠাল | |
| প্রেরকে dup ACK |
- t = ০–১. প্রেরক পাঠাল seq 1 (byte 1–1000) আর seq 1001 (byte 1001–2000); দ্বিতীয়টি পথে হারাল। প্রেরক তা জানে না — টানা পাঠাতে থাকে।
- t = ২–৩. 2001 আর 3001 রওনা দিল। t = ২-এ প্রাপক seq 1 পেল — ক্রম ঠিক, application-এ দিল, আর ACK 1001 পাঠাল (“1000 পর্যন্ত পেয়েছি”)।
- t = ৪. প্রাপক পেল 2001 — কিন্তু সে চায় 1001! Segment-টি ফেলে না দিয়ে buffer-এ রাখল, আর সঙ্গে সঙ্গে আবার ACK 1001 পাঠাল — প্রথম duplicate. একই মুহূর্তে প্রেরকের কাছে পৌঁছাল প্রথম ACK 1001 (seq 1-এর) — এটি নতুন ACK, dup নয়।
- t = ৫–৬. 3001 আর 4001 পৌঁছাল; buffer এখন 2001–5000, প্রতিবার আবার ACK 1001. t = ৬-এ প্রথম dup ACK প্রেরকে পৌঁছাল — গণনা ১.
- t = ৭. 5001-ও buffer-এ; আরেকটি dup ACK. প্রেরকের গণনা ২ — এখনো reordering হতে পারে, তাই অপেক্ষা।
- t = ৮ — fast retransmit. তৃতীয় dup ACK এলো। প্রেরক সিদ্ধান্ত নিল 1001 হারিয়েছে, আর timer-এর অপেক্ষা না করে সঙ্গে সঙ্গে 1001 আবার পাঠাল। Timeout-এর অপেক্ষা করলে অন্তত RTO (১ সেকেন্ড বা ২০০ ms) লাগত।
- t = ৯–১২ — ফাঁক পূরণ. t = ৯-এ চতুর্থ dup ACK এলো — একবার আবার পাঠানো হয়ে গেছে, তাই উপেক্ষা। t = ১০-এ 1001 পৌঁছাল: ফাঁক পূরণ, buffer-এর 2001–6000 একসাথে application-এ, আর ACK এক লাফে 6001. t = ১২-এ প্রেরক সব স্বীকৃতি পেল — মোট ৭ বার পাঠানো, বাড়তি মাত্র একটি। Go-Back-N হলে 2001 থেকে 5001-ও আবার যেত।
৭ · SACK — cumulative ACK-এর অন্ধত্ব দূর করা
Cumulative ACK একটি কথাই বলতে পারে: “এই byte পর্যন্ত ফাঁকহীন পেয়েছি।” ফাঁকের পরে কী পৌঁছেছে, বলতে পারে না। একটি window-তে দুটি segment হারালে প্রেরক fast retransmit-এ প্রথমটি পাঠায়; ACK লাফ দিয়ে দ্বিতীয় ফাঁকে থামে (partial ACK) — দ্বিতীয় loss জানা গেল এক RTT পরে। পুরোনো Reno TCP এমন অবস্থায় প্রায়ই timeout-এ পড়ত; NewReno (RFC 6582) partial ACK দেখে পরের ফাঁকটি পাঠায় — তবু প্রতি RTT-তে একটি করে।
সমাধান SACK (selective acknowledgement), RFC 2018 (১৯৯৬)। সংযোগের শুরুতে দুই পক্ষ SYN-এ “SACK-permitted” option দিয়ে সম্মতি জানায়। তারপর প্রাপক ACK-এর সাথে option-এ কয়েকটি block পাঠায় — প্রতিটি block-এ দুটি ৩২ bit নম্বর: left edge (block-এর প্রথম byte) আর right edge (block-এর শেষ byte-এর পরের নম্বর, ACK নম্বরের মতোই)। “ACK 1001, SACK 4001-6001, 2001-3001” মানে: ১০০০ পর্যন্ত ফাঁকহীন, তারপর 2001–3000 আর 4001–6000 আছে; অর্থাৎ ফাঁক 1001–2000 আর 3001–4000.
- জায়গার সীমা: TCP option-এর জায়গা মোট ৪০ byte; SACK option-এ ২ byte + প্রতি block ৮ byte, তাই সর্বোচ্চ ৪টি block. Timestamps option (১০ byte + ২ byte padding) থাকলে ৩টি.
- ক্রম: প্রথম block-এ থাকে সবচেয়ে সাম্প্রতিক পৌঁছানো segment, যাতে কিছু ACK হারালেও তথ্য না হারায়; একই block কয়েকটি ACK-এ পুনরাবৃত্তি হয়।
- শুধু পরামর্শ: SACK করা ডেটাও প্রাপক চাইলে (মেমরির চাপে) ফেলে দিতে পারে — একে বলে reneging. তাই প্রেরক cumulative ACK না আসা পর্যন্ত ডেটা নিজের buffer থেকে মোছে না।
নিচের simulator দুই প্রান্তকে byte নম্বরসহ চালায়: প্রাপক ক্রমের বাইরের ডেটা buffer-এ রেখে প্রতিটি segment-এ ACK (আর SACK block) পাঠায়; প্রেরক তৃতীয় dup ACK-এ fast retransmit করে, partial ACK-এ পরের ফাঁক পাঠায় (NewReno-র মতো), আর SACK থাকলে জানা সব ফাঁক একসাথে। এতে timeout নেই, তাই শেষ segment হারানোর নকশা দেবেন না।
function run(title, lost, useSack) { // 6 segments x MSS 1000 (seq 1..6000), delay 2, 1 send/unit
const MSS = 1000, END = 6001, D = 2, ev = [], q = [1, 1001, 2001, 3001, 4001, 5001];
const buf = new Set(), done = new Set(), lossLeft = new Set(lost), sacked = new Set();
let una = 1, dup = 0, recover = 0, rcvNxt = 1, sndMax = 1, tx = 0, t = 0;
for (console.log("== " + title); una < END; t++) {
for (const e of ev.filter(e => e.t === t)) {
if (e.seq) { // segment reaches the receiver
if (e.seq === rcvNxt) for (rcvNxt += MSS; buf.delete(rcvNxt); ) rcvNxt += MSS;
else if (e.seq > rcvNxt) buf.add(e.seq); // out of order: buffer it
const blk = [], has = (b, x) => b[0] <= x && x < b[1]; // SACK blocks
[...buf].sort((x, y) => x - y).forEach(x => blk.length && blk[0][1] === x ? blk[0][1] += MSS : blk.unshift([x, x + MSS]));
blk.sort((p, r) => has(r, e.seq) - has(p, e.seq)); // newest block first
const sk = useSack && blk.length ? " SACK " + blk.map(b => b.join("-")).join(", ") : "";
console.log(`t=${t} rcv seq=${e.seq} -> ACK ${rcvNxt}${sk}`);
ev.push({ t: t + D, ack: rcvNxt, blk: useSack ? blk : [] });
} else { // ACK reaches the sender
e.blk.forEach(([l, r]) => { for (let x = l; x < r; x += MSS) sacked.add(x); });
if (e.ack > una) { una = e.ack; dup = 0;
if (una < recover && !done.has(una)) { q.unshift(una); done.add(una);
console.log(`t=${t} partial ACK ${una} -> retransmit ${una}`); }
} else if (++dup === 3) { // third duplicate ACK
recover = sndMax; const holes = [], top = useSack ? Math.max(...sacked) : una + 1;
for (let x = una; x < top; x += MSS) if (!sacked.has(x) && !done.has(x)) holes.push(x);
holes.forEach(x => done.add(x)); q.unshift(...holes);
console.log(`t=${t} 3rd dup ACK ${una} -> fast retransmit ${holes.join(", ")}`);
}
}
}
const s = q.shift(); if (s === undefined) continue; sndMax = Math.max(sndMax, s + MSS); tx++;
if (lossLeft.delete(s)) console.log(`t=${t} send seq=${s} LOST`); else ev.push({ t: t + D, seq: s });
}
console.log(` ACK ${END} reaches sender at t=${t - 1}, ${tx} transmissions\n`);
}
[["lose 1001, no SACK", [1001], false], ["lose 1001 and 3001, no SACK", [1001, 3001], false],
["lose 1001 and 3001, with SACK", [1001, 3001], true]].forEach(a => run(...a));আউটপুট:
== lose 1001, no SACK
t=1 send seq=1001 LOST
t=2 rcv seq=1 -> ACK 1001
t=4 rcv seq=2001 -> ACK 1001
t=5 rcv seq=3001 -> ACK 1001
t=6 rcv seq=4001 -> ACK 1001
t=7 rcv seq=5001 -> ACK 1001
t=8 3rd dup ACK 1001 -> fast retransmit 1001
t=10 rcv seq=1001 -> ACK 6001
ACK 6001 reaches sender at t=12, 7 transmissions
== lose 1001 and 3001, no SACK
t=1 send seq=1001 LOST
t=2 rcv seq=1 -> ACK 1001
t=3 send seq=3001 LOST
t=4 rcv seq=2001 -> ACK 1001
t=6 rcv seq=4001 -> ACK 1001
t=7 rcv seq=5001 -> ACK 1001
t=9 3rd dup ACK 1001 -> fast retransmit 1001
t=11 rcv seq=1001 -> ACK 3001
t=13 partial ACK 3001 -> retransmit 3001
t=15 rcv seq=3001 -> ACK 6001
ACK 6001 reaches sender at t=17, 8 transmissions
== lose 1001 and 3001, with SACK
t=1 send seq=1001 LOST
t=2 rcv seq=1 -> ACK 1001
t=3 send seq=3001 LOST
t=4 rcv seq=2001 -> ACK 1001 SACK 2001-3001
t=6 rcv seq=4001 -> ACK 1001 SACK 4001-5001, 2001-3001
t=7 rcv seq=5001 -> ACK 1001 SACK 4001-6001, 2001-3001
t=9 3rd dup ACK 1001 -> fast retransmit 1001, 3001
t=11 rcv seq=1001 -> ACK 3001 SACK 4001-6001
t=12 rcv seq=3001 -> ACK 6001
ACK 6001 reaches sender at t=14, 8 transmissions
প্রথম রান অংশ ৬-এর stepper-এর হুবহু: t = ৮-এ তৃতীয় dup ACK, fast retransmit, t = ১২-এ শেষ — ৭ বার পাঠানো। একটি loss-এ SACK থাকলেও ফল একই হতো; পার্থক্য খোলে একাধিক loss-এ।
দ্বিতীয় রান (SACK ছাড়া, দুটি loss): fast retransmit-এ শুধু 1001 গেল (প্রেরক জানে না 3001-ও নেই)। t = ১১-এ ACK লাফ দিল 3001-এ — partial ACK, যা t = ১৩-এ প্রেরকে পৌঁছাল; তখন 3001 গেল। শেষ t = ১৭-এ। তৃতীয় রান (SACK): t = ৭-এর ACK-এ block “4001-6001, 2001-3001” দেখে প্রেরক দুটি ফাঁকই চিনে ফেলল, তাই t = ৯ আর ১০-এ 1001 আর 3001 দুটোই পাঠাল। শেষ t = ১৪-এ — ৩ একক আগে, প্রায় একটি RTT (এখানে RTT = ৪ একক)। পাঠানোর সংখ্যা দুই ক্ষেত্রেই ৮ — SACK বাড়তি পাঠানো নয়, অপেক্ষা কমিয়েছে; বড় window আর বেশি loss-এ এই সঞ্চয় অনেক বড়।
TCP — Go-Back-N আর Selective Repeat-এর মিশ্রণ
পর্ব ১০-এর দুই ARQ-এর সাথে মিলিয়ে দেখলে TCP কোনো একটির হুবহু রূপ নয়:
| বৈশিষ্ট্য | Go-Back-N | Selective Repeat | TCP |
|---|---|---|---|
| ACK | Cumulative | প্রতি frame আলাদা | Cumulative (GBN-এর মতো) + SACK block (SR-এর মতো) |
| ক্রমের বাইরের ডেটা | ফেলে দেয় | Buffer-এ রাখে | Buffer-এ রাখে (SR-এর মতো) |
| Timer | সবচেয়ে পুরোনো অস্বীকৃত frame-এর একটি | প্রতি frame-এ একটি | একটি retransmission timer, সবচেয়ে পুরোনো অস্বীকৃত ডেটার জন্য (RFC 6298; GBN-এর মতো) |
| Timeout হলে আবার যায় | সেটি থেকে পরের সব | শুধু সেটি | সাধারণত শুধু সবচেয়ে পুরোনো segment; SACK থাকলে স্বীকৃত অংশ বাদ |
| নম্বর | Frame-এর, m bit, ঘুরে আসে ছোট চক্রে | Byte-এর, ৩২ bit | |
| দ্রুত loss শনাক্ত | নেই (বা NAK) | NAK থাকতে পারে | ৩টি dup ACK → fast retransmit; SACK |
৮ · বাস্তবের জটিলতা — অকারণ retransmission, দুর্বল checksum আর application-এর ACK
অকারণ retransmission
“হারানো” ভেবে আবার পাঠানো segment সবসময় হারায় না। মোবাইল নেটওয়ার্কে handover-এ কয়েকশো ms-এর delay-লাফ এলে RTO ফুরিয়ে যায়, অথচ segment পথেই ছিল; reordering ৩ ঘরের বেশি হলে fast retransmit ভুল সংকেত পায়। একে বলে spurious retransmission — বাড়তি ডেটা, আর অকারণে গতি কমানো (পর্ব ৩২)। শনাক্তের মানসম্মত কৌশল:
- DSACK (RFC 2883): একই ডেটা দুবার পৌঁছালে প্রাপক প্রথম SACK block-এ সেই নকল অংশটি জানায়। প্রেরক বোঝে, “আবার পাঠানোর দরকার ছিল না।”
- F-RTO (RFC 5682): timeout-এর পর আবার পাঠিয়ে প্রেরক কিছু নতুন ডেটা পাঠায়; ফিরতি ACK যদি দেখায় যে মূল segment-গুলোও পৌঁছেছিল, timeout-টি অকারণ ছিল।
- Eifel (RFC 3522): timestamps দেখে বোঝা যায় ACK-টি মূল কপির না আবার পাঠানোর।
- RACK-TLP (RFC 8985, ২০২১): dup ACK গোনার বদলে সময় দেখে loss ধরে — “পরে পাঠানো segment পৌঁছে গেছে, অথচ এটি একটি reordering-সময় পেরিয়েও আসেনি”। সাথে tail loss probe শেষ segment হারালে timeout-এর আগে একটি probe পাঠায়। সাম্প্রতিক Linux kernel-এ এটি ব্যবহৃত হয়।
TCP checksum কতটা ভরসার
TCP-র ১৬ bit checksum (পর্ব ০৯-এর internet checksum) segment-এর header, ডেটা আর IP ঠিকানাসহ একটি “pseudo-header”-এর ওপর হিসাব হয়। এটি সব একক-bit ত্রুটি ধরে, কিন্তু দুর্বলতা স্পষ্ট: দুটি ১৬ bit শব্দ জায়গা বদল করলে যোগফল একই থাকে; এক শব্দে +x আর আরেকটিতে −x ত্রুটি একে অপরকে কাটিয়ে দেয়; 0x0000 আর 0xFFFF ones' complement-এ সমান। CRC-র তুলনায় অনেক দুর্বল। ২০০০ সালে Stone আর Partridge-এর গবেষণায় আসল ট্রাফিকে এমন ত্রুটি পাওয়া যায় যা link-এর CRC পার হয়ে (router বা host-এর মেমরিতে নষ্ট হয়ে) আসে আর TCP checksum-ও ধরে না — হার কম, কিন্তু বিপুল ট্রাফিকে উপেক্ষার মতো নয়। তাই গুরুত্বপূর্ণ ডেটায় ওপরের স্তরে শক্ত যাচাই লাগে: TLS (পর্ব ৪৮), বা ফাইলের SHA-256 মেলানো।
একটি ব্যবহারিক টীকা: আধুনিক NIC নিজেই checksum হিসাব করে (checksum offload). তাই প্রেরকের নিজের যন্ত্রে tcpdump বা Wireshark-এ বাইরে যাওয়া segment-এ “incorrect checksum” দেখা যেতে পারে — capture হয়েছে NIC হিসাব বসানোর আগে। এটি সাধারণত ত্রুটি নয় (পর্ব ৫৫)।
TCP-র ACK মানে “application পেয়েছে” নয়
TCP-র ACK বলে, ডেটা প্রাপকের TCP buffer-এ পৌঁছেছে। প্রাপকের application সেটি পড়েছে, প্রক্রিয়া করেছে, ডিস্কে লিখেছে — এর কিছুই ACK জানায় না। আরও সূক্ষ্ম: প্রেরকের send() সফল হওয়া মানে শুধু ডেটা নিজের kernel-এর buffer-এ কপি হয়েছে — তখনো এক byte-ও হয়তো তার ছাড়েনি। ধরুন একটি পেমেন্ট সার্ভার অনুরোধ পেল, TCP ACK গেল, তারপর ডেটাবেসে লেখার আগেই সার্ভার বন্ধ হয়ে গেল। TCP-র দৃষ্টিতে সব ঠিক, অথচ টাকার হিসাব হারাল। এটিই end-to-end যুক্তির শেষ ধাপ: সত্যিকারের নিশ্চয়তা দিতে পারে শুধু application নিজে — HTTP response-এর মতো application-স্তরের উত্তর, message queue-র “প্রক্রিয়া শেষে ack”, আর আবার পাঠানো অনুরোধ যাতে দুবার কাটা না হয় সেজন্য idempotency key.
আসল সংযোগের ভেতরে উঁকি — TCP_INFO
Linux প্রতিটি TCP socket-এর ভেতরের অবস্থা getsockopt(..., TCP_INFO, ...) দিয়ে দেয় — struct tcp_info-এ RTT, RTO, MSS, retransmission-এর গণনা আর অনেক কিছু (ss -ti কমান্ড একই তথ্য দেখায়)। নিচের প্রোগ্রাম 127.0.0.1-এ একটি সংযোগ বানায়, client-এ TCP_NODELAY চালু করে, ১০০ byte-এর ৫০টি request-reply আদান-প্রদান করে, তারপর client socket-এর tcp_info পড়ে। Kernel-এর সংস্করণভেদে struct-এর আকার আলাদা হতে পারে, তাই প্রতিটি ঘর ছাপার আগে HAS() দেখে নেয় kernel ততদূর পর্যন্ত ভরেছে কি না (socket প্রোগ্রামিং বিস্তারিত পর্ব ৪১-এ)।
#include <stdio.h>
#include <stddef.h>
#include <string.h>
#include <arpa/inet.h>
#include <netinet/tcp.h>
#include <sys/socket.h>
/* field f is usable only if the kernel filled at least that many bytes */
#define HAS(f) (tl >= offsetof(struct tcp_info, f) + sizeof ti.f)
int main(void) {
struct sockaddr_in a = {0}; socklen_t len = sizeof a;
a.sin_family = AF_INET;
a.sin_addr.s_addr = htonl(INADDR_LOOPBACK); /* 127.0.0.1, port 0 */
int ls = socket(AF_INET, SOCK_STREAM, 0), one = 1, c, s;
if (bind(ls, (struct sockaddr *)&a, sizeof a) || listen(ls, 1) ||
getsockname(ls, (struct sockaddr *)&a, &len)) { perror("listen"); return 1; }
c = socket(AF_INET, SOCK_STREAM, 0);
if (connect(c, (struct sockaddr *)&a, sizeof a) || (s = accept(ls, NULL, NULL)) < 0) return 1;
setsockopt(c, IPPROTO_TCP, TCP_NODELAY, &one, sizeof one); /* Nagle off */
char buf[4096]; memset(buf, 'x', sizeof buf);
for (int i = 0; i < 50; i++) /* 50 small request/replies */
if (send(c, buf, 100, 0) != 100 || recv(s, buf, sizeof buf, 0) <= 0 ||
send(s, buf, 100, 0) != 100 || recv(c, buf, sizeof buf, 0) <= 0) return 1;
struct tcp_info ti = {0}; socklen_t tl = sizeof ti; int nd = 0; socklen_t nl = sizeof nd;
if (getsockopt(c, IPPROTO_TCP, TCP_INFO, &ti, &tl)) { perror("TCP_INFO"); return 1; }
getsockopt(c, IPPROTO_TCP, TCP_NODELAY, &nd, &nl);
printf("TCP_NODELAY=%d tcp_info bytes filled=%u\n", nd, (unsigned)tl);
if (HAS(tcpi_advmss)) printf("pmtu=%u advmss=%u snd_mss=%u rcv_mss=%u (bytes)\n",
ti.tcpi_pmtu, ti.tcpi_advmss, ti.tcpi_snd_mss, ti.tcpi_rcv_mss);
if (HAS(tcpi_rttvar)) printf("rtt=%u us rttvar=%u us\n", ti.tcpi_rtt, ti.tcpi_rttvar);
if (HAS(tcpi_ato)) printf("rto=%u us ato (delayed-ACK timeout)=%u us\n", ti.tcpi_rto, ti.tcpi_ato);
if (HAS(tcpi_total_retrans)) printf("retransmits now=%u total=%u\n",
(unsigned)ti.tcpi_retransmits, ti.tcpi_total_retrans);
return 0;
}একটি আসল রানের আউটপুট:
TCP_NODELAY=1 tcp_info bytes filled=104
pmtu=65535 advmss=65483 snd_mss=22016 rcv_mss=536 (bytes)
rtt=18 us rttvar=4 us
rto=201000 us ato (delayed-ACK timeout)=40000 us
retransmits now=0 total=0
TCP_NODELAY=1: option বসেছে।104হলো glibc-রstruct tcp_info-এর আকার — kernel যতটুকু জায়গা দেওয়া হয়েছে ততটুকুই ভরে।- MSS-এর সারি: loopback-এর MTU বিশাল (এখানে
pmtu=65535), তাইadvmss= 65535 − 20 (IP) − 20 (TCP) − 12 (timestamps) = 65483 — Ethernet-এর ১৪৬০-এর প্রায় ৪৫ গুণ।snd_mssছোট (22016) — সম্ভাব্য ব্যাখ্যা: Linux পাঠানোর MSS-কে অপর পক্ষের ঘোষিত সবচেয়ে বড় window-এর অর্ধেকে সীমিত রাখে, আর শুরুতে সেই window ছোট (পর্ব ৩১)।rcv_mss=536অপর পক্ষের segment-আকারের অনুমান; আমাদের segment মাত্র ১০০ byte, তাই শুরুর অনুমান ৫৩৬ রয়ে গেছে — delayed ACK-এ “পূর্ণ segment” চিনতে এটি লাগে। rttমাত্র কয়েক দশ µs: loopback-এ propagation শূন্য — পুরোটাই kernel-এর প্রক্রিয়া (পর্ব ০৫)। প্রতিবার চালালে rtt আর rttvar বদলাবে।rto=201000 us≈ ২০১ ms: RTT ০.০২ ms হলেও RTO ২০০ ms-এর নিচে নামে না — Linux-এর সর্বনিম্ন মান (RFC মানলে ১ সেকেন্ড)। Loopback-এ একটি segment হারালে timeout পর্যন্ত RTT-র প্রায় দশ হাজার গুণ অপেক্ষা!ato=40000 us: delayed ACK-এর timeout এখন ৪০ ms — অংশ ৩-এর মান, নিজের চোখে।retransmits0: কিছু হারায়নি। আসল সংযোগেss -tiএকই তথ্য দেখায় — ধীরগতির কারণ খোঁজার প্রথম জায়গা।
আরও গভীরে: Linux-এ কয়েক লক্ষ সংযোগের প্রতিটিতে আলাদা timer চালানো ব্যয়বহুল, তাই kernel প্রতি socket-এ একটি retransmission timer রাখে আর ACK এলে সেটিকেই পিছিয়ে দেয়; RFC 6298-ও প্রতি সংযোগে একটি timer-ই সুপারিশ করে। ডেটা সেন্টারে RTT কয়েক দশ µs, তাই ২০০ ms-এর সর্বনিম্ন RTO সেখানে বিশাল — অনেক সার্ভার একসাথে একটি client-কে উত্তর দিলে switch-এর buffer উপচে loss হয় (“incast”), আর প্রতিটি loss-এ ২০০ ms থেমে থাকে; গবেষণায় µs-স্তরের RTO-র প্রস্তাব এসেছে, আর Linux-এ route ধরে rto_min কমানো যায়। QUIC (পর্ব ৩৩) packet নম্বর কখনো পুনরাবৃত্তি করে না — আবার পাঠানো ডেটা নতুন নম্বর পায় — তাই Karn-এর দ্ব্যর্থতাই সেখানে নেই।
পরীক্ষা ও ইন্টারভিউতে এই বিষয়ে প্রায়ই যা জিজ্ঞেস করা হয়:
- seq = 2001, ৭০০ byte ডেটা; প্রাপক সব ঠিক পেলে ACK কত? — 2701 (পরের প্রত্যাশিত byte); পরের segment-এর seq-ও 2701.
- RTO কীভাবে হিসাব হয়? — RFC 6298-এর ক্রমে আগে RTTVAR = ¾·RTTVAR + ¼·|SRTT − R| (পুরোনো SRTT দিয়ে), তারপর SRTT = ⅞·SRTT + ⅛·R, RTO = SRTT + 4·RTTVAR; RFC 6298-এ ন্যূনতম ১ s, timeout-এ দ্বিগুণ।
- Karn-এর algorithm কী সমস্যা সমাধান করে? — আবার পাঠানো segment-এর ACK কোন কপির তা অজানা, তাই সেই RTT নমুনা বাদ, আর backoff করা RTO রাখা।
- Fast retransmit কখন, আর কেন ৩টি dup ACK? — একই ACK-এর ৩টি নকল এলে timeout-এর আগেই আবার পাঠানো; ১–২টি প্রায়ই নিছক reordering.
- Nagle আর delayed ACK একসাথে কেন সমস্যা? — প্রেরক ACK-এর অপেক্ষায় ছোট ডেটা আটকায়, প্রাপক ডেটার অপেক্ষায় ACK আটকায় — timer ফুরানো পর্যন্ত (৪০–২০০ ms) দেরি; সমাধান একবারে লেখা বা TCP_NODELAY.
৯ · AI-এর যুগে TCP-র অঙ্ক আর টিউনিং
“seq/ack বের করুন” বা “RTO হিসাব করুন” — AI-এর উত্তর আসে মুহূর্তে, সুন্দর টেবিলসহ। কিন্তু ভুলের ধরন চেনা: off-by-one, ভুল ক্রম, একটি OS-এর মানকে standard বলে চালানো, আর টিউনিংয়ে পুরো সার্ভারের kernel সেটিং বদলানোর পরামর্শ।
| AI-এর সাধারণ ভুল | সঠিক কথা | কীভাবে যাচাই করবেন |
|---|---|---|
| ACK নম্বর = শেষ পাওয়া byte (যেমন 1500) | পরের প্রত্যাশিত byte (1501) | “শেষ ACK = প্রথম seq + মোট byte” দিয়ে মেলান |
| TCP প্রতিটি packet-এর নম্বর দেয় | নম্বর byte-এর; খালি ACK নম্বর খরচ করে না, SYN/FIN একটি করে করে | Wireshark-এ relative seq আর “Len” দেখুন |
| “RFC অনুযায়ী ন্যূনতম RTO ২০০ ms” | RFC 6298-এ ১ s; ২০০ ms Linux-এর মান | RFC 6298 পড়ুন; ss -ti-তে rto দেখুন |
| “৩টি ACK এলে fast retransmit” | ৩টি duplicate ACK (একই নম্বরের মোট ৪টি) | RFC 5681; উপরের simulator |
| ভুল RFC নম্বর (যেমন SACK-কে RFC 2581 বলা — যা আসলে পুরোনো congestion control-এর RFC) | SACK: RFC 2018; DSACK: RFC 2883; RTO: RFC 6298 | rfc-editor.org-এ নম্বর খুলে শিরোনাম মেলান |
| “ধীর? সব socket-এ TCP_NODELAY দিন, retries বাড়ান” | আগে মাপুন; NODELAY ছোট packet বাড়াতে পারে, retries বাড়ালে মৃত সংযোগ বহুক্ষণ ঝুলে থাকে | tcpdump-এ সময়রেখা দেখুন, পরিবর্তনের আগে-পরে মাপুন |
অঙ্ক বা টিউনিংয়ের প্রশ্নে AI-কে এমনভাবে জিজ্ঞেস করুন যাতে সে অনুমান আর উৎস খুলে বলতে বাধ্য হয়:
AI-এর উত্তর মানার আগে — চেকলিস্ট
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন। এগুলো protocol-এর নকশার পেছনের কারণ বোঝার জন্য।
প্র ০১TCP নম্বর দেয় byte-কে, segment-কে নয় — এই নকশার লাভ কী, দাম কী? আর QUIC কেন উল্টো পথে গেল?
১৯৭৪ সালের Cerf-Kahn প্রবন্ধেই byte-ভিত্তিক নম্বরের ধারণা ছিল, আর তার পেছনে ছিল একটি বাস্তব সমস্যা: তখনকার নেটওয়ার্কগুলোর packet-এর আকার আলাদা, আর মাঝপথের gateway-কে packet ভাঙতে হতে পারত। নম্বর byte-এর হলে ডেটা যেভাবেই কাটা বা জোড়া হোক, প্রতিটি byte-এর পরিচয় অপরিবর্তিত থাকে।
লাভ: প্রেরক আবার পাঠানোর সময় segment-এর সীমানা বদলাতে পারে। ধরুন ১০০ byte-এর তিনটি ছোট segment হারাল; আবার পাঠানোর সময় সেগুলো একটি ৩০০ byte-এর segment-এ জুড়ে দেওয়া যায় (Linux এটি করে), কারণ ACK-এর ভাষা byte-এর — প্রাপকের কাছে “কোন segment” প্রশ্নটিই নেই। Window-ও byte-এ মাপা (পর্ব ৩১) — “আর ৪,০০০ byte জায়গা আছে”, segment যত বড়ই হোক।
দাম: প্রথমত ৩২ bit-এর নম্বর byte গুনতে গুনতে দ্রুত ফুরায় — ১০ Gbps-এ প্রায় সাড়ে তিন সেকেন্ডে ঘুরে আসে (অনুশীলন ৪), তাই timestamps-ভিত্তিক PAWS লাগে। দ্বিতীয়ত আবার পাঠানো segment-এর নম্বর মূলটির হুবহু, তাই ACK কোন কপির তা বোঝা যায় না — এখান থেকেই Karn-এর সমস্যা, আর অকারণ retransmission ধরতে DSACK, Eifel, F-RTO-র মতো বাড়তি কৌশল।
QUIC-এর পথ: ২০২১ সালে মান হওয়া QUIC (RFC 9000, পর্ব ৩৩) দুটি জিনিস আলাদা করেছে। প্রতিটি packet পায় একটি নতুন, কখনো পুনরাবৃত্ত না হওয়া packet number — আবার পাঠানো ডেটাও নতুন নম্বরের packet-এ যায়। আর ডেটার অবস্থান আলাদা থাকে stream offset-এ, যা TCP-র byte নম্বরের মতো। ফলে প্রতিটি ACK দ্ব্যর্থহীন: RTT প্রতিবার নিখুঁত মাপা যায়, Karn-এর নিয়মের দরকারই নেই। ACK-এ অনেকগুলো range থাকে — চার block-এর সীমা ছাড়া SACK-এর মতো।
মূল উপলব্ধি: একটি নম্বর দিয়ে দুটি কাজ — “ডেটা কোথায়” আর “কোন পাঠানো” — করাতে গিয়েই TCP-র অনেক জটিলতা। চল্লিশ বছরের অভিজ্ঞতায় নতুন protocol এই দুই পরিচয় আলাদা করেছে। নকশায় একটি ঘর দিয়ে দুটি প্রশ্নের উত্তর দিতে চাইলে সাবধান হোন।
প্র ০২১৯৮০-র দশকে ভুল timeout কীভাবে ইন্টারনেটকে প্রায় থামিয়ে দিয়েছিল, আর Jacobson-এর সূত্র কী বদলাল?
১৯৮৬ সালের অক্টোবরে ইন্টারনেটে একটি অদ্ভুত ঘটনা ঘটে। Van Jacobson-এর ১৯৮৮ সালের বিখ্যাত প্রবন্ধ “Congestion Avoidance and Control”-এর বর্ণনা অনুযায়ী, Lawrence Berkeley Laboratory থেকে UC Berkeley — মাত্র কয়েকশো গজ দূরের দুই প্রতিষ্ঠান — এর মধ্যে throughput ৩২ kbps থেকে নেমে আসে ৪০ bps-এ, প্রায় হাজার ভাগের এক ভাগে। একে বলা হয় congestion collapse: link-গুলো পুরো ব্যস্ত, কিন্তু কাজের ডেটা প্রায় কিছুই পৌঁছাচ্ছে না।
কারণের একটি বড় অংশ ছিল timeout. RFC 793-এর পুরোনো নিয়মে RTO = β′ × SRTT, β′ প্রায় ২. ভিড় বাড়লে queue-তে delay বাড়ে, আর RTT-র ওঠানামা হয় বিশাল। গড়ের দ্বিগুণে timeout রাখলে অনেক segment পথে থাকতেই timer ফুরিয়ে যেত। প্রেরকেরা আবার পাঠাত; সেই নকল packet ভিড় আরও বাড়াত; delay আরও বাড়ত; আরও timeout. Link ভরে থাকত একই ডেটার একাধিক কপিতে।
Jacobson-এর সংশোধনের কয়েকটি অংশ ছিল। RTT-র গড়ের সাথে বিচ্যুতিও মাপা — RTO = SRTT + 4·RTTVAR — যাতে RTT যত ছড়ানো, অপেক্ষা তত বেশি। বারবার timeout-এ RTO-র exponential backoff. আর congestion window ও slow start (পর্ব ৩২)। প্রায় একই সময়ে Phil Karn — যিনি amateur radio-র packet নেটওয়ার্কে কাজ করতেন, যেখানে loss খুব বেশি — দেখান যে আবার পাঠানো segment-এর নমুনা নিলে অনুমান একেবারে ভুল দিকে যেতে পারে; ১৯৮৭ সালে Karn আর Partridge-এর প্রবন্ধে আসে আজকের Karn-এর algorithm. এগুলো BSD Unix-এর TCP-তে চালু হয়ে দ্রুত ছড়িয়ে পড়ে, আর ২০০০ সালে RFC 2988 (পরে ২০১১ সালে RFC 6298) এগুলোকে মান হিসেবে লেখে।
মূল উপলব্ধি: নির্ভরযোগ্যতার কৌশল — “না এলে আবার পাঠানো” — ভুল মাপে চললে নিজেই বিপর্যয় ঘটায়। একটি timeout শুধু একটি সংযোগের ব্যাপার নয়; লক্ষ লক্ষ সংযোগ একই ভুল নিয়ম মানলে পুরো নেটওয়ার্কের আচরণ বদলে যায়। পরিমাপ-নির্ভর, সংযত আবার-চেষ্টা — এটিই ইন্টারনেটকে টিকিয়ে রেখেছে।
প্র ০৩RFC বলে ন্যূনতম RTO ১ সেকেন্ড, Linux রাখে ২০০ ms — কোনটি ঠিক? মোবাইল নেটওয়ার্ক আর ডেটা সেন্টারে উত্তর কি একই?
দুটিই একটি trade-off-এর দুই বিন্দু। ন্যূনতম RTO ছোট হলে সত্যিকারের loss-এ অপেক্ষা কম; বড় হলে অকারণ timeout কম। RFC 6298 সাবধানী দিকটি বেছেছে — এর পেছনে ছিল Allman আর Paxson-এর ১৯৯৯ সালের পরিমাপ-ভিত্তিক গবেষণা, যেখানে দেখা যায় ন্যূনতম মান খুব ছোট হলে বাস্তবের ইন্টারনেটে অকারণ timeout অনেক বাড়ে। একটি কারণ delayed ACK: প্রাপক নিয়মমতো কয়েকশো ms পর্যন্ত ACK আটকে রাখতে পারে, আর RTO তার চেয়ে ছোট হলে প্রেরক অকারণে আবার পাঠাবে।
Linux বেছেছে দ্রুত দিকটি: ২০০ ms, সাথে timestamps (প্রতিটি ACK থেকে নিখুঁত নমুনা), F-RTO আর DSACK দিয়ে অকারণ timeout ধরে ক্ষতি ফিরিয়ে নেওয়ার ব্যবস্থা, আর RACK-TLP. অর্থাৎ ভুল কম করার বদলে ভুল দ্রুত শোধরানোর কৌশল।
মোবাইল নেটওয়ার্ক: বাংলাদেশে বহু মানুষের ইন্টারনেট মোবাইল ডেটায়। সেখানে handover, বেতারের নিজস্ব আবার-পাঠানো আর scheduler-এর কারণে RTT হঠাৎ কয়েকশো ms লাফ দিতে পারে, তারপর আবার নেমে আসে। এমন লাফ RTTVAR ধরার আগেই RTO ফুরিয়ে দেয় — অকারণ timeout, আর অকারণে গতি কমানো। ছোট ন্যূনতম RTO এখানে ঝুঁকি বাড়ায়; F-RTO মূলত এমন পরিবেশের কথা ভেবেই তৈরি।
ডেটা সেন্টার: উল্টো চিত্র। RTT কয়েক দশ µs, তাই ২০০ ms-ও RTT-র হাজার গুণ। অনেক সার্ভার একসাথে একটি client-কে উত্তর দিলে switch-এর ছোট buffer উপচে পড়ে (“incast”), আর প্রতিটি loss-এ পুরো request ২০০ ms থেমে থাকে। ২০০৯ সালের দিকে গবেষকেরা দেখান, µs-স্তরের RTO এই সমস্যা অনেক কমায়; বাস্তবে অপারেটররা route ধরে rto_min কমান, বা আলাদা congestion control ব্যবহার করেন।
মূল উপলব্ধি: “সঠিক” timeout নির্ভর করে পরিবেশের ওপর। একটি মান সব নেটওয়ার্কে ভালো হবে — এমন আশা করা যায় না। তাই মানগুলো প্রায়ই নিরাপদ ন্যূনতম বলে, আর বাস্তবায়ন পরিমাপ আর ভুল-শোধরানোর কৌশল দিয়ে আরও আক্রমণাত্মক হয়।
প্র ০৪Nagle আর delayed ACK — দুটি ভালো নিয়ম মিলে কেন খারাপ ফল দেয়? সিস্টেম নকশায় এর শিক্ষা কী?
দুটি নিয়মই একই সমস্যার সমাধান — অসংখ্য ক্ষুদ্র packet. Delayed ACK-এর ধারণা David Clark-এর ১৯৮২ সালের RFC 813-এ পাওয়া যায়: প্রাপক একটু অপেক্ষা করলে কয়েকটি ACK একটিতে মেলে, বা ACK উত্তরের পিঠে চড়ে। Nagle-এর নিয়ম ১৯৮৪ সালে RFC 896-এ, প্রেরকের দিক থেকে: ACK না আসা পর্যন্ত ছোট টুকরো জমিয়ে রাখা। ১৯৮৯ সালের RFC 1122 দুটিকেই host-এর সুপারিশকৃত (SHOULD) আচরণ হিসেবে লেখে।
আলাদাভাবে দুটিই যুক্তিসংগত। সমস্যা হয় যখন দুটি একই সংযোগের দুই প্রান্তে একসাথে কাজ করে, আর application-এর ধরন হয় “লেখা-লেখা-তারপর পড়া”: প্রেরক ACK-এর অপেক্ষায়, প্রাপক ডেটার অপেক্ষায়। প্রত্যেকে নিজের দিক থেকে ঠিক কাজই করছে, কিন্তু মিলিত ফল একটি সাময়িক অচলাবস্থা, যা ভাঙে শুধু timer ফুরালে। প্রতিটি request-এ ৪০ থেকে ২০০ ms — ওয়েব API বা ডেটাবেসে প্রতি সেকেন্ডে হাজার request হলে বিপুল ক্ষতি। এটি এতই পরিচিত যে Go-র মতো ভাষা TCP সংযোগে ডিফল্টভাবে TCP_NODELAY চালু রাখে, আর অনেক RPC ও ডেটাবেস library-ও তাই করে।
সমাধানগুলোর দিকে তাকালে আরেকটি শিক্ষা মেলে। সবচেয়ে ভালো সমাধান প্রায়ই protocol-এর নয়, application-এর: পুরো request একবারে লেখা। কারণ application জানে কখন “বার্তা শেষ”; TCP জানে না, সে শুধু ফিতা দেখে। Linux-এর TCP_CORK এই জ্ঞানটি application-কে প্রকাশ করার সুযোগ দেয় — “এখন জমিয়ে রাখা, সংকেত পেলে একসাথে ছাড়া”।
মূল উপলব্ধি: দুটি আলাদা optimization, দুটি আলাদা জায়গায়, আলাদা সময়ে নকশা করা — একসাথে চললে এমন আচরণ দেখা দেয় যা কোনোটির নকশাকারী ভাবেননি। সিস্টেম প্রকৌশলে একে বলে feature interaction. অস্বাভাবিক, স্থির সংখ্যার দেরি (ঠিক ৪০ বা ২০০ ms) দেখলে কোনো timer-কে সন্দেহ করুন।
প্র ০৫TCP তো নির্ভরযোগ্য — তাহলে পেমেন্ট বা ব্যাংকিং অ্যাপ নিজে আবার নিশ্চিতকরণ চায় কেন? End-to-end যুক্তি এখানে কী বলে?
১৯৮৪ সালে Saltzer, Reed আর Clark-এর প্রবন্ধ “End-to-End Arguments in System Design” একটি উদাহরণ দেয়: একটি ফাইল এক কম্পিউটার থেকে আরেকটিতে “সাবধানে” পাঠানো। পথে ডিস্ক থেকে পড়ার ভুল, মেমরির ভুল, নেটওয়ার্কের ভুল, প্রাপকের ডিস্কে লেখার ভুল — যেকোনো জায়গায় হতে পারে। নেটওয়ার্ককে যত নিখুঁতই করা হোক, শেষে দুই প্রান্তে ফাইলের checksum মিলিয়ে না দেখলে নিশ্চিত হওয়া যায় না। নেটওয়ার্কের নির্ভরযোগ্যতা তাই গতি বাড়ানোর কৌশল, পূর্ণ নিশ্চয়তা নয়।
TCP-র ক্ষেত্রে এই যুক্তি আরও এক ধাপ ওপরে যায়। TCP-র ACK মানে “প্রাপকের TCP buffer-এ পৌঁছেছে”। একটি পেমেন্ট অনুরোধ ভাবুন: client অনুরোধ পাঠাল, সার্ভারের kernel ACK দিল, তারপর সার্ভারের প্রক্রিয়া ক্র্যাশ করল — ডেটাবেসে কিছুই লেখা হয়নি। Client-এর দিকে TCP-র দৃষ্টিতে সব ঠিক। উল্টো ঘটনাও সম্ভব: সার্ভার টাকা কেটে উত্তর পাঠাল, কিন্তু উত্তর আসার আগেই মোবাইলের সংযোগ কেটে গেল। Client জানে না লেনদেন হয়েছে কি না। আবার চাপলে দুবার কাটা যেতে পারে।
তাই বাস্তবের সিস্টেম application-স্তরে নিজের নিশ্চয়তা যোগ করে: (১) প্রক্রিয়া শেষ হওয়ার পর উত্তর — HTTP response, লেনদেনের রসিদ নম্বর; (২) idempotency key — প্রতিটি অনুরোধে একটি অনন্য পরিচয়, যাতে একই অনুরোধ আবার এলে সার্ভার দ্বিতীয়বার টাকা না কাটে, আগের ফলই ফেরত দেয়; (৩) message queue-তে consumer বার্তা প্রক্রিয়া শেষ করে তবেই ack দেয়, না দিলে বার্তা আবার আসে। SMS-এ লেনদেনের নিশ্চিতকরণও একরকম end-to-end রসিদ।
মূল উপলব্ধি: নির্ভরযোগ্যতা কোনো একটি স্তরের সম্পত্তি নয়। প্রতিটি স্তর শুধু নিজের সীমানা পর্যন্ত নিশ্চয়তা দেয় — TCP দেয় “buffer পর্যন্ত”। যেখানে ভুলের দাম বেশি, সেখানে শেষ নিশ্চয়তা দিতে হয় সেই প্রান্তকে যে আসলে কাজটি করে।
অনুশীলন
খাতা-কলম আর ক্যালকুলেটর নিয়ে বসুন — পরীক্ষার ধাঁচের অঙ্ক, পুরো সমাধানসহ।
-
Seq আর ACK (হাতে হিসাব): Host B, Host A-র কাছ থেকে byte 2400 পর্যন্ত সব পেয়েছে। এবার A পরপর তিনটি segment পাঠাল — ৫০০, ৭০০ আর ৩০০ byte. (ক) তিনটির sequence number কত? (খ) সব ক্রমমতো পৌঁছালে আর B প্রতিটির পর ACK পাঠালে ACK নম্বরগুলো কত? (গ) দ্বিতীয় segment হারালে প্রথম আর তৃতীয়টির পর B কোন ACK পাঠাবে, আর SACK চালু থাকলে block কী হবে? (ঘ) হারানোটি আবার পৌঁছালে B-র ACK কত?
(ক) পরের প্রত্যাশিত byte 2401, তাই প্রথম segment-এর seq = 2401 (byte 2401–2900). দ্বিতীয়: 2401 + 500 = 2901 (2901–3600). তৃতীয়: 2901 + 700 = 3601 (3601–3900).
(খ) ACK = পরের প্রত্যাশিত byte: 2901, 3601, 3901. যাচাই: 2401 + (৫০০ + ৭০০ + ৩০০) = 2401 + 1500 = 3901.
(গ) প্রথমটির পর ACK 2901. তৃতীয়টি ক্রমের বাইরে — B সেটি buffer-এ রাখে আর সঙ্গে সঙ্গে আবার ACK 2901 পাঠায় (duplicate ACK). SACK থাকলে সাথে block 3601-3901 (left edge 3601, right edge শেষ byte 3900-এর পরের নম্বর)।
(ঘ) 2901–3600 আসতেই ফাঁক পূরণ, buffer-এর 3601–3900-ও যুক্ত হয়: ACK 3901. খেয়াল করুন, ACK এক লাফে দুটি segment পেরিয়ে গেল — cumulative ACK.
-
RTO-র অঙ্ক: একটি সংযোগে এখন EstimatedRTT (SRTT) = ১০০ ms আর DevRTT (RTTVAR) = ১০ ms. পরপর তিনটি নমুনা এলো — ১২০, ৮০ আর ১৪০ ms. α = ০.১২৫, β = ০.২৫ ধরে প্রতিটির পর SRTT, RTTVAR আর RTO = SRTT + 4·RTTVAR বের করুন (RFC 6298-এর ক্রমে)। তারপর RFC-র ন্যূনতম নিয়ম মানলে RTO কত, আর পরপর দুটি timeout হলে?
নমুনা ১২০: RTTVAR = ০.৭৫ × ১০ + ০.২৫ × |১০০ − ১২০| = ৭.৫ + ৫ = ১২.৫; SRTT = ০.৮৭৫ × ১০০ + ০.১২৫ × ১২০ = ৮৭.৫ + ১৫ = ১০২.৫; RTO = ১০২.৫ + ৫০ = ১৫২.৫ ms.
নমুনা ৮০: RTTVAR = ০.৭৫ × ১২.৫ + ০.২৫ × |১০২.৫ − ৮০| = ৯.৩৭৫ + ৫.৬২৫ = ১৫; SRTT = ০.৮৭৫ × ১০২.৫ + ১০ = ৮৯.৬৮৭৫ + ১০ = ৯৯.৬৯; RTO = ৯৯.৬৯ + ৬০ = ১৫৯.৬৯ ms.
নমুনা ১৪০: RTTVAR = ০.৭৫ × ১৫ + ০.২৫ × |৯৯.৬৮৭৫ − ১৪০| = ১১.২৫ + ১০.০৭৮ = ২১.৩৩; SRTT = ০.৮৭৫ × ৯৯.৬৮৭৫ + ১৭.৫ = ৮৭.২২৭ + ১৭.৫ = ১০৪.৭৩; RTO = ১০৪.৭৩ + ৮৫.৩১ = ১৯০.০৪ ms.
পাঠ্যবইয়ের ক্রমে (আগে EstimatedRTT, তারপর নতুন মান দিয়ে DevRTT) উত্তর হয় RTO ১৫০, ১৫৫ আর ১৮১.৪৮ ms — SRTT একই, শুধু DevRTT আলাদা। প্রশ্নে যে ক্রম দেওয়া, সেটি মানুন।
ন্যূনতম নিয়ম: তিনটি মানই ১ সেকেন্ডের কম, তাই RFC 6298 মানলে RTO = ১ s; প্রথম timeout-এর পর ২ s, দ্বিতীয়টির পর ৪ s. (Linux-ধাঁচে প্রায় ১০৪.৭৩ + max(৮৫.৩১, ২০০) ≈ ৩০৫ ms, তারপর দ্বিগুণ হয়ে প্রায় ৬১০ আর ১২২০ ms.)
-
আউটপুট অনুমান করুন: অংশ ৭-এর simulator-এর শেষ দুই লাইন (তিনটি run-এর তালিকা) মুছে লিখলেন
run("lose 2001, with SACK", [2001], true);— অর্থাৎ শুধু তৃতীয় segment (byte 2001–3000) হারাবে, SACK চালু। চালানোর আগে লিখুন: কোন সময়ে fast retransmit হবে, SACK block কী কী দেখা যাবে, আর শেষ ACK কখন প্রেরকে পৌঁছাবে। তারপর চালিয়ে মেলান।== lose 2001, with SACK t=2 rcv seq=1 -> ACK 1001 t=2 send seq=2001 LOST t=3 rcv seq=1001 -> ACK 2001 t=5 rcv seq=3001 -> ACK 2001 SACK 3001-4001 t=6 rcv seq=4001 -> ACK 2001 SACK 3001-5001 t=7 rcv seq=5001 -> ACK 2001 SACK 3001-6001 t=9 3rd dup ACK 2001 -> fast retransmit 2001 t=11 rcv seq=2001 -> ACK 6001 ACK 6001 reaches sender at t=13, 7 transmissionsযুক্তি: 2001 যায় t = ২-এ, তাই প্রথম রানের চেয়ে সব এক একক পরে। প্রথম দুটি ACK (1001, 2001) নতুন; তারপর 3001, 4001, 5001 ক্রমের বাইরে — তিনটি dup ACK 2001, আর একটিমাত্র block যা বড় হতে থাকে। Dup ACK প্রেরকে পৌঁছায় t = ৭, ৮, ৯-এ, তাই t = ৯-এ fast retransmit; ACK 6001 প্রেরকে t = ১৩-এ, মোট ৭ বার পাঠানো। একটিমাত্র loss, তাই SACK ছাড়াও সময় একই হতো। (t = ২-এ “rcv” লাইন আগে, কারণ প্রোগ্রাম প্রতিটি মুহূর্তে আগে পৌঁছানো ঘটনা দেখে, তারপর পাঠায়।)
-
Sequence নম্বর কখন ঘুরে আসে: TCP-র sequence number ৩২ bit. (ক) নম্বর ঘুরে না এসে একটি সংযোগে সর্বোচ্চ কত byte পাঠানো যায়? (খ) ১, ১০ আর ১০০ Gbps-এ পুরো পরিসর শেষ হতে কত সময় লাগে? (গ) RFC 793 একটি segment নেটওয়ার্কে সর্বোচ্চ ২ মিনিট বাঁচতে পারে ধরে (MSL). এ থেকে কী সমস্যা, আর সমাধান কী? (ঘ) Linux-এ চলমান TCP সংযোগগুলোর rtt, rto আর retransmission দেখার কমান্ডটি লিখুন।
(ক) ২৩২ = ৪,২৯৪,৯৬৭,২৯৬ byte — প্রায় ৪.২৯ GB (ঠিক ৪ GiB). এর পর নম্বর আবার ০ থেকে।
(খ) bit-এ: ৪,২৯৪,৯৬৭,২৯৬ × ৮ = ৩৪,৩৫৯,৭৩৮,৩৬৮ bit. ১ Gbps-এ ≈ ৩৪.৩৬ s; ১০ Gbps-এ ≈ ৩.৪৪ s; ১০০ Gbps-এ ≈ ০.৩৪ s.
(গ) ১ Gbps-এও ৩৪ সেকেন্ডে নম্বর ঘুরে আসে — MSL-এর (১২০ s) অনেক কম। দেরিতে আটকে থাকা পুরোনো segment-এর নম্বর নতুন চক্রের বৈধ নম্বরের সাথে মিলে যেতে পারে, আর প্রাপক পুরোনো ডেটা নতুন ভেবে নিতে পারে (পর্ব ১০-এর একই সমস্যা)। সমাধান RFC 7323-এর timestamps option আর PAWS (Protection Against Wrapped Sequences): প্রতিটি segment-এ প্রেরকের ঘড়ির মান থাকে; মান পুরোনো হলে segment বাতিল, sequence নম্বর মিলুক বা না মিলুক।
(ঘ)
ss -ti—-tমানে TCP,-iমানে ভেতরের তথ্য (rtt, rto, mss, cwnd, retrans ইত্যাদি)। নির্দিষ্ট port-এর জন্য, যেমনss -ti '( dport = :443 )'. পুরোনোnetstat-এ এই তথ্য নেই।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমএকটি segment হারানো থেকে ফাঁক পূরণ পর্যন্ত ঘটনাগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
- প্রেরক পরপর কয়েকটি segment পাঠায়, একটি পথে হারায়
- প্রাপক পরের segment-গুলো buffer-এ রাখে আর প্রতিবার একই ACK আবার পাঠায়
- প্রেরকের কাছে একই ACK-এর তৃতীয় duplicate পৌঁছায়
- প্রেরক timer-এর অপেক্ষা না করে হারানো segment আবার পাঠায়
- প্রাপক ফাঁক পূরণ করে buffer-এর ডেটাসহ সব application-কে দেয়, ACK এক লাফে এগোয়
একটি TCP segment-এর sequence number 3001, আর তাতে ৫০০ byte ডেটা। প্রাপক এর আগের সব ডেটা পেয়ে এটিও ঠিকঠাক পেলে কোন ACK নম্বর পাঠাবে?
- 3001
- 3500
- 3501
- 3002
প্রাপক byte 1–1000 পেয়েছে, 1001–2000 পথে হারিয়েছে, তারপর 2001–3000 পৌঁছাল। এই শেষ segment পেয়ে প্রাপক কোন ACK পাঠাবে (SACK ছাড়া)?
- ACK 3001
- ACK 1001
- ACK 2001
- কোনো ACK পাঠাবে না, ফাঁক পূরণের অপেক্ষা করবে
RFC 6298-এর নিয়মে কোনো এক মুহূর্তে SRTT = ১০০ ms আর RTTVAR = ২০ ms. ন্যূনতম-মানের নিয়ম বাদ দিয়ে শুধু সূত্র অনুযায়ী RTO কত?
- ১২০ ms
- ১৪০ ms
- ২০০ ms
- ১৮০ ms
Karn-এর algorithm কী বলে?
- আবার পাঠানো segment-এর RTT নমুনা বাদ দিতে হবে, আর backoff করা RTO রেখে দিতে হবে
- ৩টি duplicate ACK এলে segment আবার পাঠাতে হবে
- অস্বীকৃত ডেটা থাকলে ছোট segment জমিয়ে রাখতে হবে
- প্রতিটি timeout-এ RTO তিন গুণ করতে হবে
একটি client প্রতিটি request দুটি ছোট write()-এ পাঠায় (header, তারপর body), আর server পুরো request পেলে তবেই উত্তর দেয়। প্রতিটি request-এ প্রায় ২০০ ms অকারণ দেরি হচ্ছে। সবচেয়ে সম্ভাব্য কারণ আর ভালো সমাধান কোনটি?
- Packet loss; সমাধান
tcp_retries2বাড়ানো - Nagle আর delayed ACK-এর মিথস্ক্রিয়া; সমাধান request একবারে লেখা বা
TCP_NODELAY - RTO খুব বড়; সমাধান RTO ১০ ms-এ নামানো
- DNS ধীর; সমাধান অন্য DNS সার্ভার
প্রাপক পাঠাল “ACK 1001, SACK 4001-6001, 2001-3001”. এ থেকে প্রেরক কী বুঝবে?
- 1001-এর পরের সব ডেটা হারিয়েছে
- শুধু 3001–4000 হারিয়েছে
- byte 6001 পৌঁছেছে, তাই সব শেষ
- 1000 পর্যন্ত সব এসেছে; ফাঁক 1001–2000 আর 3001–4000
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- IP হারায়, নকল করে, ক্রম বদলায়, নষ্ট করে; TCP দুই প্রান্তে sequence নম্বর, ACK, checksum, timer দিয়ে নিখুঁত byte-stream বানায়।
- seq = প্রথম ডেটা byte, ACK = পরের প্রত্যাশিত byte (cumulative); শেষ ACK = প্রথম seq + মোট byte. ACK কমাতে piggybacking আর delayed ACK (প্রতি দুটি পূর্ণ segment-এ, ৫০০ ms-এর মধ্যে)।
- RTO = SRTT + 4·RTTVAR (α = ⅛, β = ¼); RFC-তে শুরু আর ন্যূনতম ১ s, Linux-এ ন্যূনতম ২০০ ms; আবার পাঠানো segment-এর নমুনা বাদ (Karn), timeout-এ RTO দ্বিগুণ।
- ৩টি duplicate ACK → fast retransmit; SACK block জানায় কী পৌঁছেছে, তাই এক RTT-তে কয়েকটি ফাঁক পূরণ। TCP = cumulative ACK (GBN) + buffer আর SACK (SR).
- Nagle + delayed ACK = ৪০–২০০ ms দেরি — একবারে লেখা বা TCP_NODELAY; আর TCP-র ACK মানে buffer পর্যন্ত — শেষ নিশ্চয়তা application-এর।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ৩১ · TCP flow control — sliding window আর receive windowপরবর্তী পর্বপ্রাপক কীভাবে প্রেরকের গতি সামলায় — byte-ভিত্তিক window.
- পর্ব ৩২ · TCP congestion control — slow start, AIMD, CUBIC আর BBRএরপরLoss ধরা পড়লে প্রেরক কতটা গতি কমায় — fast recovery আর cwnd.
- পর্ব ২৯ · TCP সংযোগ — three-way handshake, state আর বন্ধ করাআগের পর্বISN, SYN আর FIN-এর নম্বর, MSS আর SACK-permitted option কোথা থেকে আসে।
- পর্ব ১০ · Framing আর ARQভিত্তিStop-and-wait, Go-Back-N আর Selective Repeat — simulator আর efficiency-র সূত্র।
- কম্পিউটার নেটওয়ার্ক · কোর্স হোম৬২টি পর্বপুরো কোর্সের রোডম্যাপ — কোন পর্ব কখন।
- অপারেটিং সিস্টেম · IPCপাশের ট্র্যাকSocket আর kernel buffer — send() সফল মানে আসলে কী।