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.

🕑 9 daqiqa o‘qish 📄 939 so‘z 👁 1 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. HTTP/1.0 muammosi: har so'rovga yangi ulanish
  2. HTTP/1.1 muammosi: navbat blokirovkasi
  3. HTTP/2: ikkilik va multiplekslash
  4. HTTP/2 ning qolgan muammosi
  5. HTTP/3 va QUIC
  6. Xulosa

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.

Python
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)
Natija
--- 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.

Tejalgan vaqtni hisoblab ko'ring

Toshkentdan Frankfurtgacha bir tomonlama kechikish taxminan 60 ms bo'lsin.

NimaNecha martaVaqt
TCP qo'l berish1 marta borib-kelish120 ms
TLS qo'l berishyana 1-2 marta120-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.

Python
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)
Natija
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.

Brauzerlarning eski hiylasi

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:

UsulNima qilingan
Domen bo'lishRasm boshqa quyi domendan berilgan
SpraytO'nlab kichik rasm bitta faylga yig'ilgan
Fayl birlashtirishBarcha 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'zgarishNima berdi
Ikkilik formatTahlil qilish tez va aniq
Oqimlar (stream)Bitta ulanishda parallel so'rovlar
HPACKSarlavhalarni siqish
Server pushServer 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:

Python
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)}%")
Natija
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.

Bu haqiqiy HPACK emas

Yuqoridagi model g'oyani ko'rsatadi, lekin haqiqiy HPACK murakkabroq:

  • 61 ta eng ko'p uchraydigan sarlavha oldindan jadvalda turadi (:method: GET, content-type va 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.

XususiyatHTTP/2 (TCP)HTTP/3 (QUIC)
TransportTCPUDP ustidagi QUIC
ShifrlashAlohida TLS qatlamiIchiga qurilgan
Ulanish o'rnatishTCP + TLS = 2-3 marta borib-kelish1 marta
Paket yo'qolsaHamma oqim kutadiFaqat o'sha oqim
Tarmoq almashsaUlanish uziladiUlanish 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.

Nima uchun yangi protokol TCP emas, UDP ustiga qurildi?

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.

Eski versiyalarni o'chirish shart emas

"HTTP/3 bor ekan, qolganlari kerak emas" degan xulosa noto'g'ri.

HolatNima bo'ladi
Ba'zi tarmoqlarda UDP bloklanganHTTP/3 ishlamaydi
Eski mijozlarFaqat HTTP/1.1 biladi
Ichki xizmatlarKo'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.

Amaliy topshiriq
  1. Keep-alive misolini ishga tushiring.
  2. SOROVLAR ni 10 ga oshiring - tejalgan qo'l berishlar soni qancha?
  3. Navbat blokirovkasi misolida katta.jpg og'irligini 20 ga oshiring.
  4. Resurslar tartibini o'zgartiring - ketma-ket usulda natija qanday o'zgardi?
  5. HPACK misolini ishga tushiring va so'rovlar sonini 10 ga oshiring.
  6. SARLAVHALAR ga yana uchta sarlavha qo'shing.
  7. curl -I --http2 https://... bilan sayt HTTP/2 ni qo'llab-quvvatlashini tekshiring.
  8. Brauzer ishlab chiquvchi panelida "Protocol" ustunini yoqing va ko'ring.
  9. HTTP/2 va HTTP/3 dagi navbat blokirovkasi farqini yozing.
  10. 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.

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.