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: يسأل خوادم الجذر عن
.com، ويسأل تلك الخوادم عنexample.com، ويسأل تلك الخوادم عن الاسم. ويظل جوب الشجرة بأكمله مخفيًا عن الكاميرا؛ فالكاميرا ترى استعلامًا واحدًا وردًّا واحدًا.يخزّن خادم DNS النتيجة مؤقتًا للمرة القادمة ويرسل الرد إلى الكاميرا كمخطط بيانات UDP آخر.
يستغرق التبادل بأكمله عادةً بضعة أجزاء من الألف من الثانية مع ذاكرة تخزين مؤقت دافئة، وحتى مئة أو نحوها مع ذاكرة باردة.
9.15.3. واجهة Python¶
تنفّذ الدالة getaddrinfo() البحث وتُعيد عنوانًا جاهزًا لتسليمه إلى مُنشئ مقبس:
import socket
addr = socket.getaddrinfo("example.com", 80)[0][-1]
print(addr)
# ('93.184.216.34', 80)
التوقيع هو getaddrinfo(host, port). والقيمة المُعادة هي قائمة من صفوف خماسية (واحد لكل عنوان مُترجَم)؛ و[0] يختار الأول و[-1] يختار الحقل الأخير، وهو الصف (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 المؤقتة قد حُدّثت بحلول ذلك الوقت).البوابات الأسيرة. بعض شبكات Wi-Fi تعترض DNS وتُعيد عنوان IP لصفحة البوابة الأسيرة لـ كل شيء. وستبدو الكاميرا متصلة لكن البيانات التي تستردّها لن تطابق ما كانت سترسله الخدمة الفعلية. وهذا ليس شائعًا في البيئات المنشورة، لكنه أمر يحدث على شبكات Wi-Fi في المؤتمرات وما شابهها من الشبكات.
9.15.5. اسم الكاميرا نفسها¶
بحثت الصفحات حتى الآن عن أسماء أجهزة أخرى. وللكاميرا أيضًا اسم خاص بها تعلن عنه للشبكة المحلية عندما تطلب عنوانًا. والافتراضي هو معرّف عام يتفاوت حسب اللوحة؛ وnetwork.hostname() يتجاوزه بشيء سيتعرّف عليه بقية أجزاء الشبكة:
import network
network.hostname("kitchen-cam")
# ... then bring the link up as usual ...
اضبط اسم المضيف قبل تشغيل الواجهة، حتى يخرج الاسم كجزء من طلب العنوان الأولي للكاميرا بدلًا من خروجه بعده.
أمران يحدثان الآن بمجرد انضمام الكاميرا إلى الشبكة. أولًا، تسجّل معظم أجهزة التوجيه المنزلية أسماء المضيفين التي تسلّم لها عناوين في بحثها المحلي الخاص، حتى تستطيع الأجهزة الأخرى الوصول إلى الكاميرا باسم kitchen-cam -- دون الحاجة إلى معرفة العنوان الرقمي الذي صادف أن خصّصه جهاز التوجيه. (قد تحترم شبكات المؤسسات هذا أو لا تحترمه؛ فالسلوك متروك لجهاز التوجيه.) ثانيًا، تشغّل الكاميرا نفسها مستجيب mDNS جاهزًا للعمل، فيكون الاسم ذاته قابلًا للوصول أيضًا باسم kitchen-cam.local على أي شبكة يفهم عملاؤها mDNS -- وهو ما تفعله معظم أنظمة تشغيل سطح المكتب الحديثة.
ملاحظة
مرّر اسم المضيف المجرّد إلى network.hostname() -- مجرد "kitchen-cam"، دون لاحقة .local. فصيغة .local هي ما يضيفه mDNS وقت البحث؛ وإدراجها داخل اسم المضيف يجعل الكاميرا تعلن عن kitchen-cam.local كاسم مضيف عادي، وهو ليس ما يتوقعه أي طرف من طرفي البحث.
9.15.6. عندما لا تكفي الأسماء¶
بضع حالات لا يساعد فيها DNS:
الاكتشاف على الشبكة المحلية. يجيب DNS القياسي عن أسئلة حول الأسماء المسجّلة في الدليل العالمي؛ وهو لا يعرف شيئًا عن الأجهزة الموجودة على المقطع المحلي. وDNS متعدد البث (mDNS) هو النظام الذي يملأ تلك الفجوة. ينضم كل جهاز مشارك إلى مجموعة بث متعدد خاصة على الشبكة المحلية ويستمع للاستعلامات؛ وعندما يطلب جهاز اسمًا ينتهي بـ
.local، يجيب مباشرةً أيّ جهاز يملك ذلك الاسم. لا خادم مركزي، ولا تهيئة DNS. وBonjour على أجهزة Apple، وAvahi على Linux، وWindows 10 وما بعده كلها تتحدث البروتوكول ذاته -- ولهذا يُترجَم الاسمkitchen-cam.localالذي أعدّه القسم السابق على شبكة منزلية دون أي تهيئة إضافية.جانب الكاميرا في ذلك التبادل هو المستجيب، وهو يعمل بالفعل. وما لا تملكه الكاميرا هو مُحلِّل -- النصف الآخر، الذي يتيح لبرنامج نصي أن يسأل الشبكة "أين
printer.local؟" ويستردّ إجابة. وشيفرة mDNS المضمّنة هي مستجيب فقط (فالأجهزة المدمجة تكون عادةً الشيء الذي يُعثَر عليه، لا الشيء الذي يقوم بالعثور). وعندما يتعين أن يتدفق الاكتشاف في الاتجاه الآخر، يكون البث عبر UDP (انظر UDP -- أرسل حزمة وتمنّ الأفضل) هو الإجابة الأبسط لحالة المقطع المحلي، أو تضيف وحدة Python خالصة مثل cbrand/micropython-mdns مُحلِّلًا كاملًا.أسماء IPv6. تُعيد
getaddrinfo()نتائج IPv4 وIPv6 معًا إن كان كلاهما متاحًا. اختر العائلة التي يستطيع مقبس التطبيق استخدامها.
بالنسبة لمعظم الشيفرة على جانب الكاميرا، يكون getaddrinfo سطرًا واحدًا في أعلى أي دالة تفتح اتصال شبكة. والأمثلة في مكان آخر من هذا القسم التي عملت مقابل "192.168.1.20" ستعمل كلها بالطريقة ذاتها مقابل اسم عام مثل "api.example.com" -- ما عليك سوى الترجمة أولًا.