9.15. שמות ו-DNS¶
כל עמוד עד כה השתמש בכתובות IP מספריות – 192.168.1.20 וכדומה. יישומים אמיתיים כמעט לעולם אינם עושים זאת. שרתים נושאים שם כמו example.com או api.example.com, והיישום מחפש את השם בזמן ריצה כדי למצוא IP שאליו ישלח חבילות. החיפוש הזה הוא Domain Name System, או DNS.
9.15.1. לְמַה שם מתורגם¶
שם הוא רק תווית. example.com אינו נושא בעצמו שום מידע IP – יש לחפש אותו, ממש כפי שמספר טלפון מחופש בספר טלפונים. תשתית ה-DNS היא ספר הטלפונים המבוזר של האינטרנט, ותוצאת החיפוש היא כתובת IP אחת או יותר שאליהן המצלמה יכולה להתחבר.
example.com -> 93.184.216.34
שם בודד מתורגם לעיתים קרובות למספר כתובות (לצורך איזון עומסים, יתירות גאוגרפית, גרסאות IPv4 וגם IPv6 של אותו שירות). כל אחת מהן עובדת; היישום בוחר אחת ומנסה אותה, ונסוג לבאה אם זו נכשלת.
9.15.2. כיצד מתבצע החיפוש¶
כאשר המצלמה מבקשת את example.com:
המצלמה שולחת דאטהגרם UDP קטן (כן, UDP – ראו UDP – שלח חבילה, קווה לטוב ביותר) לשרת ה-DNS המוגדר שלה. כתובת שרת ה-DNS הגיעה מאותו חליפין DHCP שהעניק למצלמה את ה-IP שלה.
ייתכן ששרת ה-DNS כבר מחזיק את התשובה במטמון (נשאל לאחרונה). אם כן, הוא משיב מיד.
אם לא, שרת ה-DNS צועד במורד היררכיית ה-DNS הגלובלית: שואל את שרתי השורש (root) על
.com, שואל את אותם שרתים עלexample.com, שואל את אותם שרתים על השם. כל ההליכה בעץ מוסתרת מהמצלמה; המצלמה רואה שאילתה אחת ותגובה אחת.שרת ה-DNS שומר את התוצאה במטמון לפעם הבאה ושולח את התגובה בחזרה למצלמה כדאטהגרם UDP נוסף.
כל החליפין נמשך בדרך כלל כמה אלפיות שנייה במטמון חם, ועד כמאה או יותר במטמון קר.
9.15.3. ממשק ה-Python¶
הפונקציה getaddrinfo() מבצעת את החיפוש ומחזירה כתובת מוכנה למסירה לבנאי שקע (socket constructor):
import socket
addr = socket.getaddrinfo("example.com", 80)[0][-1]
print(addr)
# ('93.184.216.34', 80)
החתימה היא getaddrinfo(host, port). ערך ההחזרה הוא רשימה של חמישיות (tuple אחד לכל כתובת שתורגמה); ה-[0] בוחר את הראשונה וה-[-1] בוחר את השדה האחרון, שהוא ה-tuple (ip, port) שניתן למסור ישירות לשקע:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect(socket.getaddrinfo("example.com", 80)[0][-1])
s.send(b"GET / HTTP/1.0\r\nHost: example.com\r\n\r\n")
...
רוב קוד המצלמה שמתקשר עם שרת מרוחק מתחיל באותה שורה אחת. עוזרי ה-asyncio (asyncio.open_connection()) מבצעים את החיפוש באופן פנימי אם מועבר שם במקום IP מספרי, ולכן קוד אסינכרוני בדרך כלל אינו קורא ל-getaddrinfo() ישירות.
9.15.4. מה יכול להשתבש¶
אין שרת DNS נגיש. אם ה-Wi-Fi רק עלה והקישור לא יציב, הקריאה הראשונה ל-
getaddrinfo()עשויה לפקוע בזמן. מועליתOSError; נסו שוב ברגע שהקישור יציב.השם אינו קיים. שגיאת הקלדה או שם מיושן מעלים
OSErrorלאחר ששרת ה-DNS מחזיר ”אין שם כזה“. קוד השגיאה מבדיל בין זה לבין ”שרת DNS לא נגיש“, אך עבור רוב יישומי המצלמה מדיניות הניסיון-מחדש/ויתור זהה.הכתובת שהוחזרה אינה עובדת. DNS עשוי להחזיר כתובת שכבר אינה מארחת את השירות. הפתרון הוא לסגת לרשומה הבאה ברשימת התוצאות של
getaddrinfo(), או לחפש את השם שוב מאוחר יותר (סביר שמטמון ה-DNS יתעדכן עד אז).שערים שבויים (Captive portals). רשתות Wi-Fi מסוימות מיירטות DNS ומחזירות את ה-IP של דף השער השבוי עבור הכול. נראה שהמצלמה מתחברת, אך הנתונים שהיא מקבלת בחזרה לא יתאימו למה שהשירות האמיתי היה שולח. לא נפוץ בסביבות מותקנות, אבל דבר שקורה ב-Wi-Fi של כנסים וברשתות דומות.
9.15.5. השם של המצלמה עצמה¶
העמודים עד כה חיפשו שמות של התקנים אחרים. גם למצלמה יש שם משלה שאותו היא מפרסמת לרשת המקומית כשהיא מבקשת כתובת. ברירת המחדל היא מזהה גנרי המשתנה לפי הלוח; network.hostname() דורסת אותו במשהו ששאר הרשת תזהה:
import network
network.hostname("kitchen-cam")
# ... then bring the link up as usual ...
הגדירו את שם המארח (hostname) לפני הפעלת הממשק, כדי שהשם ייצא כחלק מבקשת הכתובת הראשונית של המצלמה ולא אחריה.
שני דברים קורים כעת לאחר שהמצלמה הצטרפה לרשת. ראשית, רוב הנתבים הביתיים רושמים את שמות המארח שהם מקצים להם כתובות לתוך החיפוש המקומי שלהם עצמם, כך שהתקנים אחרים יכולים להגיע למצלמה בתור kitchen-cam – מבלי שיצטרכו לדעת את הכתובת המספרית שהנתב הקצה במקרה. (רשתות ארגוניות עשויות לכבד זאת או לא; ההתנהגות תלויה בנתב.) שנית, המצלמה עצמה מריצה מגיב mDNS מן הקופסה, כך שאותו שם נגיש גם בתור kitchen-cam.local בכל רשת שהלקוחות שלה מבינים mDNS – וזה נכון לרוב מערכות ההפעלה השולחניות המודרניות.
הערה
העבירו את שם המארח החשוף אל network.hostname() – רק "kitchen-cam", ללא סיומת .local. צורת ה-.local היא מה ש-mDNS מוסיף בזמן החיפוש; הטמעתה בתוך שם המארח גורמת למצלמה לפרסם את kitchen-cam.local כשם מארח רגיל, וזה לא מה ששני צדי החיפוש מצפים לו.
9.15.6. כששמות אינם מספיקים¶
מספר מצבים שבהם DNS אינו מסייע:
גילוי ברשת המקומית. DNS סטנדרטי עונה על שאלות בנוגע לשמות הרשומים בספרייה הגלובלית; הוא אינו יודע דבר על התקנים במקטע המקומי. Multicast DNS (mDNS) היא המערכת הממלאת את הפער הזה. כל התקן משתתף מצטרף לקבוצת multicast מיוחדת ברשת המקומית ומאזין לשאילתות; כאשר התקן מבקש שם המסתיים ב-
.local, ההתקן שבבעלותו אותו שם עונה ישירות. ללא שרת מרכזי, ללא תצורת DNS. Bonjour בהתקני Apple, Avahi ב-Linux, ו-Windows 10 ומעלה – כולם דוברים את אותו פרוטוקול – ולכן השםkitchen-cam.localשהוגדר בסעיף הקודם נפתר ברשת ביתית מבלי שהוגדר דבר נוסף.צד המצלמה בחליפין הזה הוא המגיב (responder), והוא כבר פועל. מה שאין למצלמה הוא פותר (resolver) – המחצית השנייה, שתאפשר לסקריפט לשאול את הרשת ”איפה
printer.local?“ ולקבל תשובה בחזרה. קוד ה-mDNS המצורף הוא מגיב-בלבד (התקנים מוטמעים הם בדרך כלל הדבר שמחפשים, ולא הדבר שמחפש). כאשר הגילוי צריך לזרום בכיוון ההפוך, שידור UDP (ראו UDP – שלח חבילה, קווה לטוב ביותר) הוא התשובה הפשוטה יותר עבור מקרה המקטע-המקומי, או שמודול ב-Python טהור כגון cbrand/micropython-mdns מוסיף פותר מלא.שמות IPv6.
getaddrinfo()מחזירה תוצאות גם IPv4 וגם IPv6 אם שתיהן זמינות. בחרו את המשפחה שבה השקע של היישום יכול להשתמש.
עבור רוב הקוד בצד המצלמה, getaddrinfo הוא שורה אחת בראש כל פונקציה שפותחת חיבור רשת. הדוגמאות במקומות אחרים בסעיף זה שפעלו מול "192.168.1.20" היו עובדות כולן באותו אופן מול שם ציבורי כגון "api.example.com" – רק תרגמו תחילה.