နားလည်ထားရမယ့် အချက်
UDP ကို 'TCP ရဲ့ အားနည်းတဲ့ ပုံစံ' လို့ မမြင်ဘဲ 'IP ပေါ်မှာ port number ဖြည့်ပေးထားတဲ့ အလွှာပါးပါး တစ်ခု' လို့ မြင်ရင် ပိုမှန်တယ်။ UDP က connection မထူထောင်ဘူး၊ handshake မလုပ်ဘူး၊ sequence number မထားဘူး၊ retransmit မလုပ်ဘူး၊ ordering မလုပ်ဘူး၊ flow control (သို့) congestion control လည်း မလုပ်ဘူး။ လုပ်ပေးတာက source port, destination port, length နဲ့ checksum ပဲ ဖြစ်ပြီး — ဒါက datagram တစ်ခုကို host တစ်ခုပေါ်က မှန်ကန်တဲ့ process ဆီ ပို့ပေးဖို့ လုံလောက်တဲ့ အနည်းဆုံး ဖြစ်တယ်။
ဒီ 'မလုပ်တာ' တွေက အားနည်းချက် မဟုတ်တာက အကြောင်းရှိတယ်။ TCP ရဲ့ အာမခံချက်တွေ အားလုံးဟာ အချိန်နဲ့ ဝယ်ထားတာ ဖြစ်တယ်။ segment တစ်ခု ပျောက်ရင် TCP က ပြန်ပို့တယ်၊ ဒါပေမယ့် အဲဒီအတွင်းမှာ နောက်က ရောက်ပြီးသား data တွေကို application ဆီ မတင်ဘဲ buffer ထဲ ထိန်းထားတယ် — ဒါကို head-of-line blocking လို့ ခေါ်တယ်။ voice call တစ်ခုမှာ 200 ms အရင်က audio frame တစ်ခုကို ပြန်ရဖို့ ကျန်တဲ့ အသံအားလုံးကို ခဏရပ်ထားတာဟာ တကယ့် ဆိုးရွားမှု ဖြစ်တယ် — frame တစ်ခုကို လွှတ်ပစ်လိုက်တာက အများကြီး ကောင်းတယ်။ game တစ်ခုမှာလည်း ဟောင်းနေပြီးသား position update တစ်ခုက အသုံးမဝင်တော့ဘူး၊ နောက်တစ်ခုက ဒီအချိန်မှာ ရောက်နေပြီ။ DNS query တစ်ခုက datagram တစ်ခုနဲ့ ပြီးလို့ handshake သုံးဆင့် ပေးဆပ်ဖို့ မဆီလျော်ဘူး။
အရေးကြီးတဲ့ အချက်တစ်ခုက UDP ကို ရွေးလိုက်တာဟာ ယုံကြည်စိတ်ချရမှုကို စွန့်လွှတ်လိုက်တာ မဟုတ်ဘဲ၊ ဘယ်လို ယုံကြည်စိတ်ချရမလဲ ဆိုတာကို ကိုယ်တိုင် ဆုံးဖြတ်ခွင့် ရလိုက်တာ ဖြစ်တယ်။ QUIC ဟာ အတိအကျ ဒီအတိုင်း လုပ်တာ ဖြစ်တယ် — UDP ပေါ်မှာ တည်ဆောက်ပြီး stream တစ်ခုချင်းစီအတွက် သီးခြား ordering နဲ့ retransmit ကို user space မှာ ပြန်တည်ဆောက်ထားတယ်၊ ဒါကြောင့် stream တစ်ခုမှာ packet ပျောက်တာက ကျန် stream တွေကို မပိတ်ဆို့ဘူး။ UDP ကို ရွေးရတာက kernel TCP stack ကို မထိဘဲ ဒီလို ဒီဇိုင်းအသစ်ကို deploy လုပ်နိုင်လို့ ဖြစ်တယ်။
UDP HEADER VERSUS TCP HEADER, FIELD BY FIELD
--------------------------------------------
UDP HEADER (8 bytes) TCP HEADER (20 bytes min)
0 31 0 31
+----------+----------+ +-------------+-------------+
| src port | dst port | | src port | dst port |
+----------+----------+ +-------------+-------------+
| length | checksum | | sequence number |
+----------+----------+ +---------------------------+
| acknowledgement number |
that is the ENTIRE header. +------+------+-------------+
no seq, no ack, no window, | off | flag | window |
no state, no retransmit, +------+------+-------------+
no ordering, no handshake. | checksum | urgent ptr |
+-------------+-------------+လက်တွေ့ scenario နဲ့ ချိတ်ကြည့်မယ်
video-call feature တစ်ခုကို ဒီဇိုင်းဆွဲနေတယ် ဆိုပါစို့။ စဉ်းစားရမယ့် မေးခွန်းက 'ယုံကြည်စိတ်ချရမှု လိုသလား' မဟုတ်ဘဲ 'data တစ်ခုဟာ ဘယ်လောက်ကြာရင် အသုံးမဝင်တော့ဘူးလဲ' ဆိုတာ ဖြစ်တယ်။ audio frame တစ်ခုက 20 ms စာ အသံကို ကိုယ်စားပြုပြီး player ဟာ jitter buffer တစ်ခုနဲ့ 60 ms လောက် ရှေ့ကို စောင့်နိုင်တယ်။ frame တစ်ခု ပျောက်သွားရင် retransmit လုပ်ဖို့ RTT တစ်ခေါက် ကြာမယ် — RTT က 120 ms ဆိုရင် ပြန်ရောက်လာတဲ့ frame ဟာ ဖွင့်ချိန် ကျော်လွန်ပြီးသား ဖြစ်လို့ လုံးဝ အသုံးမဝင်တော့ဘူး။ ဒါကြောင့် UDP ပေါ်မှာ ပို့ပြီး ပျောက်တဲ့ frame ကို codec ရဲ့ packet-loss concealment နဲ့ ဖုံးဖိထားတာ မှန်ကန်တဲ့ ဒီဇိုင်း ဖြစ်တယ်။
ဒါပေမယ့် feature တစ်ခုတည်းထဲမှာတောင် လိုအပ်ချက် ကွဲပြားနိုင်တယ် ဆိုတာ သတိထားပါ။ 'call စတင်ပါ' ဆိုတဲ့ signalling message၊ chat message၊ ပြီးတော့ ပါဝင်သူစာရင်း တို့က တစ်ခုမှ မပျောက်စေချင်တာ ဖြစ်လို့ TCP (သို့) TLS ပေါ်မှာ ပို့သင့်တယ်။ လက်တွေ့ application အများစုက နှစ်ခုစလုံး သုံးတယ် — control plane အတွက် TCP၊ media အတွက် UDP။ နောက်တစ်ချက်က UDP သုံးမယ်ဆိုရင် congestion control ကို ကိုယ်တိုင် တာဝန်ယူရမယ် ဆိုတာ ဖြစ်တယ်။ ကွန်ရက် ကျပ်နေချိန်မှာ ဘာမှ မထိန်းဘဲ ဆက်ပို့နေတဲ့ application ဟာ ကိုယ့်ကိုယ်ကိုယ်ရော အခြားသူတွေရဲ့ traffic ကိုပါ ဖျက်ဆီးပစ်တယ်။
အတူတူ စမ်းရေးကြည့်မယ်
import struct
# ---- Build a UDP datagram: 8-byte header + payload ----------------------
payload = b"hello over udp"
src_port = 49152
dst_port = 5353
length = 8 + len(payload) # UDP length COUNTS the header itself
checksum = 0 # 0 = "not computed", legal in IPv4 UDP
udp_header = struct.pack("!HHHH", src_port, dst_port, length, checksum)
datagram = udp_header + payload
print("UDP header hex :", udp_header.hex())
print("header bytes :", len(udp_header))
print("datagram bytes :", len(datagram))
# ---- Parse it back ------------------------------------------------------
s, d, ln, csum = struct.unpack("!HHHH", datagram[:8])
body = datagram[8:ln]
print("parsed ports : %d -> %d" % (s, d))
print("parsed length : %d (payload %d bytes)" % (ln, ln - 8))
print("parsed payload :", body.decode())
print("checksum : 0x%04x" % csum)
# ---- Header cost compared with TCP -------------------------------------
print()
print("field UDP TCP")
rows = [
("source port", 2, 2),
("destination port", 2, 2),
("length", 2, 0),
("checksum", 2, 2),
("sequence number", 0, 4),
("acknowledgement number", 0, 4),
("offset + flags", 0, 2),
("receive window", 0, 2),
("urgent pointer", 0, 2),
]
for name, u, t in rows:
print("%-24s %5s %5s" % (name, u or "-", t or "-"))
udp_bytes = sum(r[1] for r in rows)
tcp_bytes = sum(r[2] for r in rows)
print("%-24s %5d %5d" % ("TOTAL", udp_bytes, tcp_bytes))
for size in (64, 1200):
u_over = 100.0 * udp_bytes / (udp_bytes + size)
t_over = 100.0 * tcp_bytes / (tcp_bytes + size)
print("overhead on %4d B of data: UDP %.1f%% TCP %.1f%%"
% (size, u_over, t_over))UDP header hex : c00014e900160000
header bytes : 8
datagram bytes : 22
parsed ports : 49152 -> 5353
parsed length : 22 (payload 14 bytes)
parsed payload : hello over udp
checksum : 0x0000
field UDP TCP
source port 2 2
destination port 2 2
length 2 -
checksum 2 2
sequence number - 4
acknowledgement number - 4
offset + flags - 2
receive window - 2
urgent pointer - 2
TOTAL 8 20
overhead on 64 B of data: UDP 11.1% TCP 23.8%
overhead on 1200 B of data: UDP 0.7% TCP 1.6%၅ မိနစ် စမ်းကြည့်
payload ကို 1 byte နဲ့ 1400 bytes အဖြစ် ပြောင်းပြီး overhead ရာခိုင်နှုန်း ဘယ်လောက် ကွာသွားလဲ ကြည့်ပါ။ ပြီးရင် length field ကို လက်နဲ့ မှားပြင်ကြည့်ပြီး parser က payload ကို ဘယ်လို မှားဖြတ်သွားလဲ ဆိုတာ လေ့လာပါ။
သတိလေးတစ်ချက်
UDP က 'ပိုမြန်တယ်' လို့ ယူဆပြီး bulk transfer အတွက် သုံးတာ။ loss ဖြစ်တဲ့ path ပေါ်မှာ ကိုယ်တိုင် retransmit ရေးလိုက်ရင် TCP ကို ပိုညံ့တဲ့ပုံစံနဲ့ ပြန်ရေးနေတာ ဖြစ်သွားတယ်။
datagram တစ်ခုက ကြီးလွန်းရင် IP fragmentation ဖြစ်ပြီး fragment တစ်ခု ပျောက်ရုံနဲ့ datagram တစ်ခုလုံး ဆုံးရှုံးတာကို မေ့လျော့တာ။ payload ကို path MTU အောက် (များသောအားဖြင့် 1200 bytes ဝန်းကျင်) ထားပါ။
RFC 768 - User Datagram Protocol — Computer Networking