9.12. שקעי UDP¶
תעבורת UDP ב-Python נשלחת ומתקבלת באמצעות שתי מתודות על שקע דאטגרם: sendto() כדי לשגר דאטגרם אל יעד נבחר, ו-recvfrom() כדי לקבל דאטגרם ולגלות מהיכן הגיע. כל קריאה מעבירה הודעה אחת עצמאית; אין מצב חיבור.
9.12.1. שליחת דאטגרם¶
שליחת ה-UDP הפשוטה ביותר היא שורת Python אחת מעל בנאי שקע:
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 בוחר פורט מקור ארעי; הסקריפט אינו חייב לקשר דבר.
שליחת אותו מטען ליעדים רבים היא פשוט לולאה – השקע ניתן לשימוש חוזר בין שליחות, ואין חיבור להקים:
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. קבלת דאטגרם¶
כדי לקבל דאטגרמים, על השקע לתפוס פורט ידוע שבו השולחים ישתמשו כיעד שלהם. זוהי הקריאה 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 במצלמה“ – בכל ממשק Wi-Fi או Ethernet שמכניס מנות, פורט 9000 שייך לשקע זה.
הארגומנט 1024 של recvfrom() הוא המספר המרבי של בייטים לקריאה אל החוצץ (buffer) המוחזר. דאטגרמים של UDP מעל גודל זה יקוצצו; בחר את הערך כך שיתאים לדאטגרם הגדול ביותר שהאפליקציה מצפה לו.
recvfrom() מחזירה (data, src): הבייטים שהתקבלו, וכתובת השולח. כתובת השולח היא זו שאליה יש להשיב, מה שמקל על כתיבת פרוטוקול קטן של בקשה/תשובה:
while True:
request, src = s.recvfrom(1024)
if request == b"ping":
s.sendto(b"pong", src)
כברירת מחדל recvfrom() חוסמת עד שמגיע דאטגרם. הדפוסים להפיכתה ללא-חוסמת – פסקי זמן, שקעים לא-חוסמים, 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()מציבה מועד אחרון על קריאת הקבלה. אם לא מגיעה תשובה בתוך שתי שניות, הקריאה מעלהOSErrorבמקום לחסום לנצח – דרך טבעית לזהות מנה שאבדה.בלוק ה-
withסוגר את השקע אוטומטית.
9.12.4. מגבלות גודל דאטגרם¶
דאטגרמים של UDP יכולים להגיע עד כ-64 KB תיאורטית, אך המגבלה המעשית קטנה בהרבה. לכל קישור בנתיב שבין השולח למקבל יש יחידת שידור מרבית (MTU) – הבלוק הבודד הגדול ביותר של בייטים שאותו קישור יכול לשאת בפריים אחד. גם Ethernet וגם Wi-Fi מגבילים זאת לכ-1500 בייטים, וכמעט כל נתיב באינטרנט מתחקה בסופו של דבר אחר מגבלה זו במקום כלשהו.
כאשר דאטגרם חורג מה-MTU של קישור שעליו לחצות, שכבת הרשת מפצלת אותו לפרגמנטים קטנים יותר ומרכיבה אותם מחדש ביעד. UDP עצמו לעולם אינו רואה את הפיצול, אך לפרגמנטים יש כמה תכונות לא נוחות:
אם פרגמנט בודד אובד, כל הדאטגרם נזרק במקבל – אין שידור חוזר לכל פרגמנט. הסתברות האובדן גדלה עם מספר הפרגמנטים.
כמה רשתות וחומות אש זורקות מנות מפוצלות לחלוטין, ומתייחסות אליהן כחשודות.
ההרכבה מחדש עולה בזיכרון אצל המקבל, שעל מיקרו-בקר נמצא במחסור.
הכלל המעשי במצלמה: שמור הודעות UDP הרבה מתחת ל-1500 בייטים. כ-1400 בייטים משאירים מקום לכותרות ה-IP וה-UDP, לכל תקורת מנהור שהנתיב מוסיף, ולשינויים קטנים ב-MTU בין קישורי Ethernet, Wi-Fi ו-VPN. אפליקציות שצריכות לשלוח יותר מכך צריכות או לחלק את הנתונים בשכבת האפליקציה או לעבור ל-TCP, שמטפל בפיצול ובהרכבה מחדש אוטומטית.
9.12.5. מלכודות נפוצות¶
שכחה ש-UDP יכול לאבד מנות. קוד שעובד באופן מושלם ברשת מקומית שקטה לעיתים נכשל בדרכים עדינות ברשת עמוסה או רחבה יותר. תמיד תכנן עבור האפשרות שההודעה לא הגיעה.
המקבל אינו מקושר לפני שהשולח שולח. דאטגרם שנשלח לפורט שאיש אינו מאזין לו נזרק בשקט. הפעל תחילה את המקבל.
שליחת דאטגרם גדול מה-MTU של הנתיב. ראה את הסעיף הקודם – שמור הודעות מתחת ל-~1400 בייטים.
הדפוסים שלמעלה מכסים כמעט כל שימוש UDP שהמצלמה נזקקת לו. העמוד הבא עושה את המקבילה עבור TCP.