13-bo‘lim
HTTP/1.1 dan HTTP/3 gacha
Doimiy ulanish, quvurlash va navbat blokirovkasi, HTTP/2 multiplekslash va HPACK, HTTP/3 nima uchun UDP ustida ishlaydi.
Ushbu bo‘lim mundarijasi
HTTP ning asosiy g'oyasi 1996-yildan beri o'zgarmagan: matnli so'rov, matnli javob. O'zgargani - uni qanday yetkazish.
Har versiya oldingisining aniq bir muammosini yechgan.
HTTP/1.0 muammosi: har so'rovga yangi ulanish #
Dastlabki HTTP da har fayl uchun yangi TCP ulanish ochilardi. Har ulanish esa - uch qadamli qo'l berish, ya'ni qo'shimcha kutish.
import socket
import threading
PORT = 9700
SOROVLAR = 4
tinglovchi = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
tinglovchi.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
tinglovchi.bind(("127.0.0.1", PORT))
tinglovchi.listen(8)
hisob = {"ulanish": 0, "sorov": 0}
def server():
while hisob["sorov"] < SOROVLAR * 2:
ulanish, _ = tinglovchi.accept()
hisob["ulanish"] += 1
while True:
malumot = ulanish.recv(4096)
if not malumot:
break
hisob["sorov"] += 1
tana = b"ok"
javob = (b"HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\n" + tana)
ulanish.sendall(javob)
if b"Connection: close" in malumot:
break
ulanish.close()
oqim = threading.Thread(target=server, daemon=True)
oqim.start()
def sorov_yubor(s, yopilsinmi):
ulanish = "close" if yopilsinmi else "keep-alive"
s.sendall(f"GET / HTTP/1.1\r\nHost: x\r\n"
f"Connection: {ulanish}\r\n\r\n".encode())
s.recv(4096)
print("--- har so'rov uchun yangi ulanish (HTTP/1.0 uslubi)")
oldingi = hisob["ulanish"]
for i in range(SOROVLAR):
m = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
m.connect(("127.0.0.1", PORT))
sorov_yubor(m, True)
m.close()
print(" so'rovlar:", SOROVLAR, "| ulanishlar:",
hisob["ulanish"] - oldingi)
print("--- bitta ulanish qayta ishlatiladi (keep-alive)")
oldingi = hisob["ulanish"]
m = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
m.connect(("127.0.0.1", PORT))
for i in range(SOROVLAR - 1):
sorov_yubor(m, False)
sorov_yubor(m, True)
m.close()
print(" so'rovlar:", SOROVLAR, "| ulanishlar:",
hisob["ulanish"] - oldingi)
oqim.join()
tinglovchi.close()
print()
print("tejalgan qo'l berishlar:", SOROVLAR - 1)
--- har so'rov uchun yangi ulanish (HTTP/1.0 uslubi)
so'rovlar: 4 | ulanishlar: 4
--- bitta ulanish qayta ishlatiladi (keep-alive)
so'rovlar: 4 | ulanishlar: 1
tejalgan qo'l berishlar: 3
To'rt so'rov - to'rt ulanish o'rniga bitta. HTTP/1.1 da doimiy ulanish (keep-alive) standart holatda yoqilgan.
Toshkentdan Frankfurtgacha bir tomonlama kechikish taxminan 60 ms bo'lsin.
| Nima | Necha marta | Vaqt |
|---|---|---|
| TCP qo'l berish | 1 marta borib-kelish | 120 ms |
| TLS qo'l berish | yana 1-2 marta | 120-240 ms |
Ya'ni har yangi ulanish uchun kamida 240 ms. Sahifada 30 ta resurs bo'lsa va har biri yangi ulanish talab qilsa - 7 soniyadan ortiq faqat kutish.
Aynan shuning uchun ulanishni qayta ishlatish shunchalik muhim.
HTTP/1.1 muammosi: navbat blokirovkasi #
Doimiy ulanish yaxshi, lekin unda so'rovlar navbat bilan bajariladi. Bitta katta fayl butun navbatni to'xtatib qo'yadi.
RESURSLAR = [
("index.html", 1),
("katta.jpg", 6), # sekin yuklanadi
("uslub.css", 1),
("skript.js", 1),
]
def navbat_bilan(nom):
"""HTTP/1.1: bitta ulanishda birin-ketin."""
print(f" {nom}:")
vaqt = 0
for fayl, ogirlik in RESURSLAR:
vaqt += ogirlik
print(f" t={vaqt:<3} {fayl} tayyor")
return vaqt
def parallel(nom):
"""HTTP/2: bitta ulanishda aralashtirib (multiplekslash)."""
print(f" {nom}:")
qolgan = {f: o for f, o in RESURSLAR}
vaqt = 0
while qolgan:
vaqt += 1
for fayl in list(qolgan):
qolgan[fayl] -= 1
if qolgan[fayl] == 0:
del qolgan[fayl]
print(f" t={vaqt:<3} {fayl} tayyor")
return vaqt
print("HTTP/1.1 - bitta ulanishda navbat:")
a = navbat_bilan("ketma-ket")
print()
print("HTTP/2 - bitta ulanishda multiplekslash:")
b = parallel("aralash")
print()
print("ketma-ket jami vaqt:", a)
print("multiplekslashda :", b)
print("kichik fayllar kutmadimi:", b < a)
HTTP/1.1 - bitta ulanishda navbat:
ketma-ket:
t=1 index.html tayyor
t=7 katta.jpg tayyor
t=8 uslub.css tayyor
t=9 skript.js tayyor
HTTP/2 - bitta ulanishda multiplekslash:
aralash:
t=1 index.html tayyor
t=1 uslub.css tayyor
t=1 skript.js tayyor
t=6 katta.jpg tayyor
ketma-ket jami vaqt: 9
multiplekslashda : 6
kichik fayllar kutmadimi: True
Farqni ko'ring: birinchi holatda uslub.css sakkizinchi
qadamda tayyor bo'ldi, ikkinchisida esa birinchi qadamda.
Sahifa ancha oldin chizila boshlaydi.
HTTP/1.1 davrida brauzerlar bu muammoni chetlab o'tish uchun har domenga 6 tagacha parallel ulanish ochardi.
Bu ham yetmagani uchun ishlab chiquvchilar qo'shimcha usullarni o'ylab topishgan:
| Usul | Nima qilingan |
|---|---|
| Domen bo'lish | Rasm boshqa quyi domendan berilgan |
| Sprayt | O'nlab kichik rasm bitta faylga yig'ilgan |
| Fayl birlashtirish | Barcha CSS bitta faylga qo'shilgan |
HTTP/2 da bularning hammasi keraksiz - hatto zararli bo'lib qoldi: bitta katta fayl o'zgarsa, butun kesh qayta yuklanadi.
Eski qo'llanmalarni o'qiyotganda buni yodda tuting.
HTTP/2: ikkilik va multiplekslash #
| O'zgarish | Nima berdi |
|---|---|
| Ikkilik format | Tahlil qilish tez va aniq |
| Oqimlar (stream) | Bitta ulanishda parallel so'rovlar |
| HPACK | Sarlavhalarni siqish |
| Server push | Server so'ralmagan faylni oldindan beradi |
Oxirgisi amalda o'zini oqlamadi va bugun deyarli ishlatilmaydi - brauzerlar uni o'chirib qo'yishdi.
Eng katta foyda esa sarlavhalarni siqishdan keldi:
SARLAVHALAR = [
("host", "edu.softromeda.com"),
("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"),
("accept", "text/html,application/xhtml+xml"),
("accept-language", "uz-UZ,uz;q=0.9,ru;q=0.8"),
("cookie", "sessiya=abc123; mavzu=qorong"),
]
def matn_olchami(sarlavhalar):
return sum(len(n) + len(q) + 4 for n, q in sarlavhalar)
class Jadval:
"""HPACK ning eng sodda modeli: ko'rilgan sarlavha raqamlanadi."""
def __init__(self):
self.raqamlar = {}
def kodla(self, sarlavhalar):
olcham = 0
yangi = 0
for juft in sarlavhalar:
if juft in self.raqamlar:
olcham += 1 # faqat indeks - bitta bayt
else:
self.raqamlar[juft] = len(self.raqamlar) + 1
olcham += len(juft[0]) + len(juft[1]) + 2
yangi += 1
return olcham, yangi
xom = matn_olchami(SARLAVHALAR)
jadval = Jadval()
print("HTTP/1.1 - har so'rovda to'liq matn:")
for i in range(1, 4):
print(f" {i}-so'rov: {xom} bayt")
print()
print("HTTP/2 - HPACK bilan:")
for i in range(1, 4):
olcham, yangi = jadval.kodla(SARLAVHALAR)
print(f" {i}-so'rov: {olcham} bayt (yangi yozuv: {yangi})")
print()
print("uchta so'rov uchun jami:")
print(" HTTP/1.1:", xom * 3, "bayt")
jadval2 = Jadval()
jami = sum(jadval2.kodla(SARLAVHALAR)[0] for _ in range(3))
print(" HTTP/2 :", jami, "bayt")
print(" tejaldi :", f"{100 - jami * 100 // (xom * 3)}%")
HTTP/1.1 - har so'rovda to'liq matn:
1-so'rov: 202 bayt
2-so'rov: 202 bayt
3-so'rov: 202 bayt
HTTP/2 - HPACK bilan:
1-so'rov: 192 bayt (yangi yozuv: 5)
2-so'rov: 5 bayt (yangi yozuv: 0)
3-so'rov: 5 bayt (yangi yozuv: 0)
uchta so'rov uchun jami:
HTTP/1.1: 606 bayt
HTTP/2 : 202 bayt
tejaldi : 67%
Ikkinchi so'rovdan boshlab sarlavhalar 5 baytga tushdi. Sahifada 50 ta resurs bo'lsa, tejalgan hajm sezilarli bo'ladi.
Yuqoridagi model g'oyani ko'rsatadi, lekin haqiqiy HPACK murakkabroq:
- 61 ta eng ko'p uchraydigan sarlavha oldindan jadvalda
turadi (
:method: GET,content-typeva hokazo); - qiymatlar Huffman kodlash bilan ham siqiladi;
- jadval hajmi cheklangan va eski yozuvlar chiqib ketadi.
HTTP/3 da esa HPACK o'rniga QPACK ishlatiladi - sababi quyida.
HTTP/2 ning qolgan muammosi #
HTTP/2 ilova darajasidagi navbatni yo'qotdi. Lekin pastda hali ham TCP turibdi va TCP ham tartibni saqlaydi.
Bitta paket yo'qolsa, TCP butun oqimni to'xtatib, uni qayta so'raydi. Boshqa oqimlardagi tayyor ma'lumotlar ham kutib turadi - garchi ular butunlay boshqa faylga tegishli bo'lsa ham.
Ya'ni navbat blokirovkasi ilovadan transportga ko'chdi.
HTTP/3 va QUIC #
Yechim radikal bo'ldi: TCP dan butunlay voz kechish.
| Xususiyat | HTTP/2 (TCP) | HTTP/3 (QUIC) |
|---|---|---|
| Transport | TCP | UDP ustidagi QUIC |
| Shifrlash | Alohida TLS qatlami | Ichiga qurilgan |
| Ulanish o'rnatish | TCP + TLS = 2-3 marta borib-kelish | 1 marta |
| Paket yo'qolsa | Hamma oqim kutadi | Faqat o'sha oqim |
| Tarmoq almashsa | Ulanish uziladi | Ulanish saqlanadi |
Oxirgi qator kundalik hayotda sezilarli: telefoningiz Wi-Fi dan mobil tarmoqqa o'tganda IP manzil o'zgaradi. TCP uchun bu yangi ulanish degani, QUIC esa ulanishni IP manzil bilan emas, alohida identifikator bilan tanigani uchun uzilmaydi.
TCP ni o'zgartirish - deyarli imkonsiz ish.
U operatsion tizim yadrosida turadi va dunyodagi har bir marshrutizator, firewall va NAT qurilmasi uni ma'lum bir tarzda kutadi. Yangi TCP imkoniyati qo'shilsa, oradagi qurilmalar uni tushunmay, paketni tashlab yuborishi mumkin.
UDP esa oddiy: oradagi qurilmalar uchun bu shunchaki datagramma. Butun mantiq ilova darajasida quriladi va brauzer yangilanishi bilan yangilanadi.
Shuning uchun QUIC ni "UDP ustidagi yangi TCP" deb atashadi.
"HTTP/3 bor ekan, qolganlari kerak emas" degan xulosa noto'g'ri.
| Holat | Nima bo'ladi |
|---|---|
| Ba'zi tarmoqlarda UDP bloklangan | HTTP/3 ishlamaydi |
| Eski mijozlar | Faqat HTTP/1.1 biladi |
| Ichki xizmatlar | Ko'pincha HTTP/1.1 yetarli |
Shuning uchun serverlar odatda uchalasini ham qo'llab- quvvatlaydi va mijoz bilan kelishib oladi (ALPN orqali).
Mijoz HTTP/3 ni sinab ko'radi, ishlamasa HTTP/2 ga, u ham bo'lmasa HTTP/1.1 ga tushadi.
- Keep-alive misolini ishga tushiring.
SOROVLARni 10 ga oshiring - tejalgan qo'l berishlar soni qancha?- Navbat blokirovkasi misolida
katta.jpgog'irligini 20 ga oshiring. - Resurslar tartibini o'zgartiring - ketma-ket usulda natija qanday o'zgardi?
- HPACK misolini ishga tushiring va so'rovlar sonini 10 ga oshiring.
SARLAVHALARga yana uchta sarlavha qo'shing.curl -I --http2 https://...bilan sayt HTTP/2 ni qo'llab-quvvatlashini tekshiring.- Brauzer ishlab chiquvchi panelida "Protocol" ustunini yoqing va ko'ring.
- HTTP/2 va HTTP/3 dagi navbat blokirovkasi farqini yozing.
- Nima uchun QUIC UDP ustiga qurilganini uch qatorda tushuntiring.
Xulosa #
- HTTP ning g'oyasi o'zgarmagan - o'zgargani yetkazish usuli.
- HTTP/1.0 da har so'rovga yangi ulanish kerak edi.
- HTTP/1.1 doimiy ulanish (keep-alive) ni standart qildi.
- Lekin so'rovlar navbat bilan bajarilardi - navbat blokirovkasi.
- Brauzerlar buni 6 ta parallel ulanish bilan chetlab o'tardi.
- HTTP/2 ikkilik format, oqimlar va HPACK ni olib keldi.
- Multiplekslash tufayli kichik fayllar katta faylni kutmaydi.
- HPACK takrorlanuvchi sarlavhalarni bir necha baytga tushiradi.
- HTTP/2 da navbat blokirovkasi TCP darajasida qoldi.
- HTTP/3 QUIC ustida ishlaydi: UDP, ichiga qurilgan shifrlash, tarmoq almashsa uzilmaydi.
Keyingi bo'limda holatsiz protokol ustida holat qanday saqlanishini - cookie va sessiyani ko'ramiz.
O‘qish tarixini saqlamoqchimisiz?
Tizimga kirsangiz, tugatgan bo‘limlaringiz saqlanadi va qoldirgan joyingizdan davom etasiz.
Xatolik topdingizmi?
Imlo xatosi, ishlamaydigan kod yoki noto‘g‘ri ma‘lumotni ko‘rsangiz - bizga xabar bering. Har bir xabar administrator tomonidan ko‘rib chiqiladi.