9.12. UDP sockets¶
ทราฟฟิก UDP ใน Python ส่งและรับด้วยสองเมธอดบน datagram socket ได้แก่ sendto() สำหรับส่ง datagram ไปยังปลายทางที่เลือก และ recvfrom() สำหรับรับ datagram และทราบแหล่งที่มา การเรียกแต่ละครั้งเคลื่อนย้ายข้อความที่สมบูรณ์ในตัวเอง ไม่มีสถานะการเชื่อมต่อ
9.12.1. การส่ง datagram¶
การส่ง UDP ที่ง่ายที่สุดคือโค้ด Python หนึ่งบรรทัดบน socket constructor:
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.sendto(b"hello", ("192.168.1.20", 9000))
s.close()
นั่นส่ง b"hello" ไปยังพอร์ต 9000 บน 192.168.1.20 แล้วออกไป MicroPython เลือก source port ชั่วคราว สคริปต์ไม่ต้อง bind อะไรทั้งนั้น
การส่ง payload เดียวกันไปยังหลายปลายทางเป็นแค่ลูป -- socket สามารถใช้ซ้ำระหว่างการส่ง และไม่มีการเชื่อมต่อที่ต้องตั้งค่า:
targets = [
("192.168.1.20", 9000),
("192.168.1.21", 9000),
("192.168.1.22", 9000),
]
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
for addr in targets:
s.sendto(b"hello", addr)
9.12.2. การรับ datagram¶
เพื่อรับ datagrams socket ต้องจองพอร์ตที่รู้จักซึ่งผู้ส่งจะใช้เป็นปลายทาง นั่นคือการเรียก bind()
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind(("0.0.0.0", 9000))
while True:
data, src = s.recvfrom(1024)
print("from", src, "got", data)
ที่อยู่ "0.0.0.0" หมายถึง "ทุกอินเทอร์เฟซ IPv4 บน camera" -- ไม่ว่าจะเป็นอินเทอร์เฟซ Wi-Fi หรือ Ethernet ที่นำแพ็กเก็ตเข้ามา พอร์ต 9000 เป็นของ socket นี้
อาร์กิวเมนต์ 1024 ของ recvfrom() คือจำนวนไบต์สูงสุดที่อ่านลงในบัฟเฟอร์ที่คืนค่า UDP datagrams ที่มีขนาดเกินกว่านี้จะถูก ตัดทอน เลือกค่าให้ตรงกับขนาด datagram ที่ใหญ่ที่สุดที่แอปพลิเคชันคาดไว้
recvfrom() คืนค่า (data, src) ได้แก่ ไบต์ที่รับมาและที่อยู่ของผู้ส่ง ที่อยู่ของผู้ส่งคือสิ่งที่จะตอบกลับ ทำให้ง่ายต่อการเขียนโปรโตคอล request/response ขนาดเล็ก:
while True:
request, src = s.recvfrom(1024)
if request == b"ping":
s.sendto(b"pong", src)
โดยค่าเริ่มต้น recvfrom() จะ บล็อก จนกว่า datagram จะมาถึง รูปแบบสำหรับทำให้ไม่บล็อก ได้แก่ timeouts, non-blocking sockets, asyncio อธิบายไว้ที่ Sockets กับ asyncio
9.12.3. คำขอและการตอบกลับ¶
สคริปต์สั้นสองรายการ รายการหนึ่งส่งคำขอ อีกรายการรับและตอบกลับ
ฝั่งรับ:
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind(("0.0.0.0", 9000))
while True:
req, src = s.recvfrom(64)
print("got", req, "from", src)
s.sendto(b"ack: " + req, src)
ฝั่งส่ง:
import socket
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
s.settimeout(2.0) # 2 s reply window
s.sendto(b"ping", ("192.168.1.20", 9000))
try:
reply, _ = s.recvfrom(64)
print("reply:", reply)
except OSError:
print("no reply in 2 s -- packet lost?")
สิ่งที่น่าสังเกตสองสามอย่างในฝั่งส่ง:
settimeout()กำหนดเวลาหมดอายุสำหรับการเรียก receive หากไม่มีการตอบกลับภายในสองวินาที การเรียกจะ raiseOSErrorแทนที่จะบล็อกตลอดไป -- เป็นวิธีธรรมชาติในการตรวจจับแพ็กเก็ตที่สูญหายบล็อก
withปิด socket โดยอัตโนมัติ
9.12.4. ขีดจำกัดขนาด datagram¶
UDP datagrams สามารถมีขนาดได้ถึงประมาณ 64 KB ในทางทฤษฎี แต่ขีดจำกัดในทางปฏิบัติมีขนาดเล็กกว่ามาก ทุกลิงก์ในเส้นทางระหว่างผู้ส่งและผู้รับมี Maximum Transmission Unit (MTU) -- บล็อกไบต์เดียวที่ใหญ่ที่สุดที่ลิงก์นั้นสามารถส่งได้ในหนึ่งเฟรม Ethernet และ Wi-Fi จำกัดอยู่ที่ประมาณ 1500 ไบต์ และเส้นทางอินเทอร์เน็ตแทบทุกเส้นทางสืบย้อนไปยังขีดจำกัดนั้น
เมื่อ datagram เกิน MTU ของลิงก์ที่ต้องข้าม ชั้น network จะแบ่งมันออกเป็น fragments ขนาดเล็กและรวมกลับที่ปลายทาง UDP เองไม่เคยเห็นการแบ่ง แต่ fragments มีคุณสมบัติที่ไม่สะดวกหลายประการ
หาก fragment ใดหายไป datagram ทั้งหมดจะถูกทิ้งที่ผู้รับ -- ไม่มีการส่งซ้ำต่อ fragment ความน่าจะเป็นของการสูญเสียเพิ่มขึ้นตามจำนวน fragment
บางเครือข่ายและไฟร์วอลล์ทิ้งแพ็กเก็ตที่ถูก fragment ทั้งหมด โดยถือว่าน่าสงสัย
การ re-assembly ใช้หน่วยความจำที่ผู้รับ ซึ่งบน microcontroller มีจำกัด
กฎในทางปฏิบัติบน camera คือ ให้ข้อความ UDP อยู่ต่ำกว่า 1500 ไบต์อย่างดี ประมาณ 1400 ไบต์เหลือพื้นที่สำหรับส่วนหัว IP และ UDP, overhead ของ tunneling ที่เส้นทางเพิ่ม และความแตกต่างเล็กน้อยใน MTU ระหว่าง Ethernet, Wi-Fi และลิงก์ VPN แอปพลิเคชันที่ต้องส่งข้อมูลมากกว่านั้นควรแบ่งข้อมูลที่ชั้นแอปพลิเคชัน หรือเปลี่ยนไปใช้ TCP ซึ่งจัดการการแบ่งและ re-assembly โดยอัตโนมัติ
9.12.5. ปัญหาที่พบบ่อย¶
ลืมว่า UDP สามารถสูญเสียแพ็กเก็ต โค้ดที่ทำงานได้อย่างสมบูรณ์บนเครือข่ายท้องถิ่นที่เงียบสงบ บางครั้งล้มเหลวในรูปแบบละเอียดบนเครือข่ายที่มีทราฟฟิกมากขึ้นหรือกว้างขึ้น ออกแบบสำหรับความเป็นไปได้ที่ข้อความไม่ถึงเสมอ
ฝั่งรับไม่ได้ bind ก่อนที่ฝั่งส่งจะส่ง datagram ที่ส่งไปยังพอร์ตที่ไม่มีใครฟังอยู่จะถูกทิ้งอย่างเงียบๆ เริ่ม receiver ก่อน
ส่ง datagram ที่ใหญ่กว่า MTU ของเส้นทาง ดูหัวข้อก่อนหน้า -- ให้ข้อความอยู่ต่ำกว่า ~1400 ไบต์
รูปแบบข้างต้นครอบคลุมการใช้งาน UDP เกือบทุกกรณีที่ camera ต้องการ หน้าถัดไปทำสิ่งเทียบเท่าสำหรับ TCP