TCP Dramasi: Nagle amaki, dangasa Mijoz va 40ms lik sukunat
Muhammadqodir Alijonov
Tasavvur qiling, siz va do'stingiz Telegramda yozishyapsiz. Har bir "Salom" yoki "Qalaysan" degan xabarga alohida-alohida "O'qidim" (seen) deb javob qaytarish g'alati, to'g'rimi? Odatda biz hammasini o'qib, bitta qilib javob yozamiz.
Internetning asosiy protokoli bo'lmish TCP da ham xuddi shunday "aqlli dangasalik" va "o'ta tejamkorlik" bor. Agar bular noto'g'ri uchrashib qolsa, tizimingiz sababsiz sekinlashib qoladi.
Keling, bu dramani ikki qahramon misolida ko'rib chiqamiz.
1. Qabul qiluvchi (Mijoz): "Shoshmay turaylik-chi..." (Delayed ACK)
TCP qoidasiga ko'ra, qabul qiluvchi (Receiver) har bir kelgan paket uchun "Oldim" (ACK - Acknowledgment) deb tasdiq yuborishi kerak. Lekin operatsion tizim (Windows, Linux, macOS) o'ylaydi:
"Hozir shugina 'Oldim' degan gap uchun alohida konvert (paket) jo'natsam, internetni band qilaman va protsessorni charchataman. Undan ko'ra ozgina kutay. Balki dastur javob qaytarar? O'sha javobga 'Oldim' degan belgini qo'shib yuboraman (Piggybacking). Yoki yana bitta paket kelib qolar, ikkalisiga bitta 'Oldim' deyman."
Bu "kutish vaqti" odatda 40ms dan 500ms gacha bo'ladi. Bu β Delayed ACK (Kechiktirilgan Tasdiq). Maqsad β tarmoqni tejash.
2. Yuboruvchi (Server): "Marshrutka to'lmaguncha yurmaydi" (Nagle Algoritmi)
Endi ikkinchi tomon β Server haqida gaplashamiz. U yerda Nagle Algoritmi ishlaydi. Uning mantig'i bizdagi "Damas" yoki avtobus haydovchilariga o'xshaydi.
Haydovchi (Nagle) aytadi: "Salonda bor-yo'g'i 2 ta odam bor. Men benzin sarflab yurmayman. Salon to'lmaguncha yoki dispetcher ruxsat bermaguncha (ACK kelmaguncha) joyimdan jilmayman!"
π "Salon" qancha? (Buffer Size / MSS)
Buffer size "bufer" yoki "paket sig'imi" TCP da MSS (Maximum Segment Size) deb ataladi.
- Internetda odatda MTU (Maximum Transmission Unit) 1500 bayt bo'ladi.
- Sarlavhalarni (Headers) olib tashlasak, sof ma'lumot uchun ~1460 bayt joy qoladi.
Demak, Nagle algoritmi shunday ishlaydi:
- Agar menda 1460 bayt (MSS) ma'lumot yig'ilsa β darrov jo'nataman (Marshrutka to'ldi).
- Agar ma'lumot kam bo'lsa (masalan, bor-yo'g'i 100 baytlik Header) β kutib turaman. Qachongacha? Qachonki oldingi yuborganimga "Yetib bordi" (ACK) degan xabar kelsa.
3. Fojia: O'lik Quchoq (The Deadly Embrace) π
Mana endi fojia yuz beradi. Tasavvur qiling, Serverda (Java dastur) mijozga yuborish uchun ma'lumot bor, lekin u ikki qismga bo'lingan: Header (kichik) va Body (katta).
- Server: Kichkina
Headerni yuboradi. (Birinchi paketda muammo yo'q, darrov ketadi). - Server: Endi
Bodyni yuborishga tayyorlanadi. LEKIN Nagle algoritmi uni to'xtatadi: "To'xta! Bu ma'lumot oz (yoki to'liq emas), oldingi yuborganHeaderga hali ACK kelmadi. Kutamiz!" - Mijoz (Delayed ACK):
Headerni qabul qildi. Lekin "Darrov ACK yubormayman, balki yana narsa kelar? 40ms kutaman" deydi.
Natija:
- Server: "ACK kelyaptimi?" (Jimlik)
- Mijoz: "Davomi kelyaptimi?" (Jimlik)
- Ikkalasi ham 40ms davomida bir-biriga qarab jim o'tiraveradi.
40ms o'tgach, Mijozning sabri tugab, majburan ACK yuboradi. Server uyg'onadi va qolganini jo'natadi. Bu 40ms β yuqori tezlikdagi tizimlar uchun halokatli kechikishdir.
4. Java misolida yechim βοΈ
Keling, kodga o'tamiz. Ko'pchilik Java dasturchilar bilmasdan quyidagi xatoni qilishadi (Write-Write antipattern):
β Yomon usul (Delay tuzog'iga tushish)
Socket socket = new Socket("example.com", 80);
OutputStream out = socket.getOutputStream();
String header = "HEAD / HTTP/1.1\r\n\r\n"; // Kichkina paket
String body = "...juda ko'p ma'lumot...";
// 1. Birinchi yozish (kichik) -> darrov ketadi
out.write(header.getBytes());
// 2. Ikkinchi yozish -> NAGLE USHLAB QOLADI!
// Chunki birinchi paketga hali ACK kelmadi.
out.write(body.getBytes());
β 1-Yechim: TCP_NODELAY (Nagleni o'chirish)
Biz Serverga aytamiz: "Haydovchi aka, kutmang! Bitta odam bo'lsa ham haydayvering, benzin mendan!" Buning uchun TCP_NODELAY opsiyasini yoqamiz.
Socket socket = new Socket("example.com", 80);
// MUHIM: Nagle algoritmini o'chiramiz.
// Ma'lumot bufer to'lishini kutmasdan darrov ketadi.
socket.setTcpNoDelay(true);
OutputStream out = socket.getOutputStream();
out.write(header.getBytes());
out.write(body.getBytes()); // Endi bu ham darrov ketadi!
Bu usul real-time o'yinlar va tezkor APIlar uchun juda yaxshi.
β 2-Yechim: Buferlash (BufferedOutputStream)
Agar siz tarmoqni tejamoqchi bo'lsangiz va Nagle turaversin desangiz, unda ma'lumotni o'zingiz yig'ib (paketni to'ldirib), keyin OS ga bering.
Socket socket = new Socket("example.com", 80);
// Nagle yoniq qolishi mumkin, lekin biz bufer ishlatamiz
OutputStream out = socket.getOutputStream();
BufferedOutputStream bufOut = new BufferedOutputStream(out);
// Ma'lumotlar tarmoqqa emas, RAMdagi buferga yoziladi
bufOut.write(header.getBytes());
bufOut.write(body.getBytes());
// "flush()" chaqirilganda hammasi bitta katta paket bo'lib ketadi
bufOut.flush();
Bu usulda Server OS ga bitta katta bo'lak beradi (MSS dan katta bo'lsa ham), Nagle uni ushlab o'tirmaydi, chunki paket "to'la".
Xulosa
- Delayed ACK: Mijozning "keyinroq javob beraman" deyishi (40ms kutish).
- Nagle: Serverning "paket to'lmasa, yoki ACK kelmasa yubormayman" deyishi.
- Muammo: Ikkalasi bir vaqtda ishlasa β 40ms lik "O'lik sukunat" paydo bo'ladi.
- Yechim: Javada
socket.setTcpNoDelay(true)qiling yoki ma'lumotlarniBufferedOutputStreamga yig'ib, birdaniga uzating.