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 หนึ่งตัวหรือมากกว่าที่ camera สามารถเชื่อมต่อได้
example.com -> 93.184.216.34
ชื่อเดียวมักแก้ไขได้เป็นหลายที่อยู่ (สำหรับการ load balancing, redundancy ทางภูมิศาสตร์, เวอร์ชัน IPv4 และ IPv6 ของบริการเดียวกัน) ที่อยู่ใดก็ได้ แอปพลิเคชันเลือกหนึ่งตัวและลองใช้ โดยใช้ตัวถัดไปหากล้มเหลว
9.15.2. วิธีการค้นหา¶
เมื่อ camera ถาม example.com:
camera ส่ง UDP datagram ขนาดเล็ก (ใช่ UDP -- ดู UDP -- ส่งแพ็กเก็ตแล้วหวังว่าจะดีที่สุด) ไปยัง DNS server ที่กำหนดค่าไว้ ที่อยู่ของ DNS server มาจาก DHCP exchange เดียวกับที่ให้ IP ของ camera เอง
DNS server อาจมีคำตอบ cached อยู่แล้ว (มีการถามเมื่อไม่นานมานี้) ถ้าเป็นเช่นนั้น จะตอบกลับทันที
ถ้าไม่มี DNS server จะ walk ลำดับชั้น DNS ทั่วโลก: ถาม root server เกี่ยวกับ
.com, ถาม server เหล่านั้นเกี่ยวกับexample.com, ถาม server เหล่านั้น เกี่ยวกับชื่อ การ walk ต้นไม้ทั้งหมดถูกซ่อนจาก camera; camera เห็นแค่หนึ่ง query และหนึ่ง replyDNS server แคชผลลัพธ์สำหรับครั้งถัดไปและส่ง reply กลับไปยัง camera เป็น UDP datagram อีกตัว
การแลกเปลี่ยนทั้งหมดมักใช้เวลาไม่กี่มิลลิวินาทีบน cache ที่ warm ไปจนถึงประมาณหนึ่งร้อยมิลลิวินาทีบน cache ที่ cold
9.15.3. Python interface¶
ฟังก์ชัน getaddrinfo() ทำการค้นหาและส่งคืนที่อยู่ที่พร้อมส่งให้ socket constructor:
import socket
addr = socket.getaddrinfo("example.com", 80)[0][-1]
print(addr)
# ('93.184.216.34', 80)
signature คือ getaddrinfo(host, port) ค่าที่ส่งคืนคือ list ของ 5-tuple (หนึ่งต่อที่อยู่ที่แก้ไขได้); [0] เลือกตัวแรกและ [-1] เลือกฟิลด์สุดท้าย ซึ่งคือ tuple (ip, port) ที่ส่งให้ socket ได้โดยตรง:
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")
...
โค้ด camera ส่วนใหญ่ที่คุยกับ remote server เริ่มต้นด้วยบรรทัดนั้นบรรทัดเดียว helper ของ asyncio (asyncio.open_connection()) ทำการค้นหาภายในหากส่งชื่อแทน IP ตัวเลข ดังนั้นโค้ด async มักไม่เรียก getaddrinfo() โดยตรง
9.15.4. สิ่งที่อาจผิดพลาด¶
ไม่สามารถเชื่อมต่อ DNS server ได้ หาก Wi-Fi เพิ่งขึ้นมาและลิงก์ไม่เสถียร การเรียก
getaddrinfo()ครั้งแรกอาจ timeoutOSErrorจะถูก raise; ลองใหม่เมื่อลิงก์เสถียรแล้วชื่อไม่มีอยู่ การพิมพ์ผิดหรือชื่อที่ล้าสมัยจะ raise
OSErrorหลังจาก DNS server ส่งคืน "no such name" รหัสข้อผิดพลาดแยกความแตกต่างจาก "DNS server ไม่สามารถเข้าถึงได้" แต่สำหรับแอป camera ส่วนใหญ่ นโยบาย retry/give-up เหมือนกันที่อยู่ที่ส่งคืนไม่ทำงาน DNS อาจส่งคืนที่อยู่ที่ไม่ได้ host บริการนั้นอีกแล้ว วิธีแก้คือ fallback ไปยัง entry ถัดไปในรายการผลลัพธ์ของ
getaddrinfo()หรือค้นหาชื่อใหม่ในภายหลัง (DNS cache น่าจะอัปเดตแล้ว)Captive portals เครือข่าย Wi-Fi บางแห่งดักจับ DNS และส่งคืน IP ของ captive-portal page สำหรับ ทุกอย่าง camera จะดูเหมือนเชื่อมต่อได้ แต่ข้อมูลที่ได้รับกลับมาจะไม่ตรงกับสิ่งที่บริการจริงจะส่ง ไม่ใช่เรื่องปกติในสภาพแวดล้อมที่ใช้งานจริง แต่เกิดขึ้นได้บน Wi-Fi ของงานประชุมและเครือข่ายที่คล้ายกัน
9.15.5. ชื่อของ camera เอง¶
หน้าที่ผ่านมาค้นหาชื่อของอุปกรณ์ อื่นๆ camera ยังมีชื่อของตัวเองที่โฆษณาไปยังเครือข่ายท้องถิ่นเมื่อขอที่อยู่ ค่าเริ่มต้นคือ identifier ทั่วไปที่แตกต่างตามบอร์ด; network.hostname() แทนที่ด้วยสิ่งที่เครือข่ายที่เหลือจะรู้จัก:
import network
network.hostname("kitchen-cam")
# ... then bring the link up as usual ...
ตั้ง hostname ก่อน เปิด interface ขึ้นมา เพื่อให้ชื่อออกไปเป็นส่วนหนึ่งของการขอที่อยู่เริ่มต้นของ camera แทนที่จะเป็นหลังจากนั้น
สองสิ่งเกิดขึ้นเมื่อ camera เข้าร่วมเครือข่าย ประการแรก router ที่บ้านส่วนใหญ่ลงทะเบียน hostname ที่ให้ที่อยู่ไว้ใน lookup ท้องถิ่นของตัวเอง ดังนั้นอุปกรณ์อื่นสามารถเข้าถึง cam ได้ในชื่อ kitchen-cam โดยไม่ต้องรู้ที่อยู่ตัวเลขที่ router กำหนด (เครือข่ายองค์กรอาจหรืออาจไม่ทำเช่นนี้ ขึ้นอยู่กับ router) ประการที่สอง cam เอง รัน mDNS responder จากกล่อง ดังนั้นชื่อเดียวกันก็เข้าถึงได้ในชื่อ kitchen-cam.local บนเครือข่ายที่ไคลเอนต์เข้าใจ mDNS ซึ่งระบบปฏิบัติการ desktop สมัยใหม่ส่วนใหญ่รองรับ
Note
ส่ง ชื่อ host เปล่าๆ ให้ network.hostname() -- แค่ "kitchen-cam" ไม่มี suffix .local รูปแบบ .local คือสิ่งที่ mDNS เพิ่มเข้ามาตอนค้นหา การใส่มันเข้าไปใน hostname จะทำให้ cam โฆษณา kitchen-cam.local เป็น hostname ธรรมดา ซึ่งไม่ใช่สิ่งที่ทั้งสองฝั่งของการค้นหาคาดหวัง
9.15.6. เมื่อชื่อไม่เพียงพอ¶
บางสถานการณ์ที่ DNS ไม่ช่วย:
การค้นพบบนเครือข่ายท้องถิ่น DNS มาตรฐานตอบคำถามเกี่ยวกับชื่อที่ลงทะเบียนในไดเรกทอรีทั่วโลก มันไม่รู้เรื่องอุปกรณ์บน segment ท้องถิ่น Multicast DNS (mDNS) คือระบบที่เติมเต็มช่องว่างนั้น ทุกอุปกรณ์ที่เข้าร่วมเข้าร่วม multicast group พิเศษบนเครือข่ายท้องถิ่นและฟัง query; เมื่ออุปกรณ์ถามชื่อที่ลงท้ายด้วย
.localอุปกรณ์ที่เป็นเจ้าของชื่อนั้นตอบกลับโดยตรง ไม่มีเซิร์ฟเวอร์กลาง ไม่ต้องกำหนดค่า DNS Bonjour บนอุปกรณ์ Apple, Avahi บน Linux และ Windows 10+ ต่างใช้โปรโตคอลเดียวกัน ซึ่งเป็นเหตุที่ชื่อkitchen-cam.localที่ตั้งขึ้นในส่วนก่อนหน้า resolve ได้บนเครือข่ายที่บ้านโดยไม่ต้องกำหนดค่าพิเศษฝั่ง cam ของการแลกเปลี่ยนนั้นคือ responder และกำลังทำงานอยู่แล้ว สิ่งที่ cam ไม่มีคือ resolver ซึ่งเป็นอีกครึ่งหนึ่งที่จะช่วยให้ script ถาม network ว่า "
printer.localอยู่ที่ไหน?" และได้คำตอบกลับ โค้ด mDNS ที่รวมมาเป็น responder เท่านั้น (อุปกรณ์ embedded มักเป็น สิ่งที่ถูกค้นพบ ไม่ใช่สิ่งที่ค้นพบ) เมื่อการค้นพบต้องไหลในทิศทางตรงข้าม UDP broadcast (ดู UDP -- ส่งแพ็กเก็ตแล้วหวังว่าจะดีที่สุด) เป็นคำตอบที่ง่ายกว่าสำหรับกรณี local-segment หรือ module Python ล้วนๆ เช่น cbrand/micropython-mdns เพิ่ม resolver เต็มรูปแบบชื่อ IPv6
getaddrinfo()ส่งคืนผลลัพธ์ทั้ง IPv4 และ IPv6 หากมีทั้งสอง เลือก family ที่ socket ของแอปพลิเคชันใช้ได้
สำหรับโค้ดฝั่ง camera ส่วนใหญ่ getaddrinfo คือหนึ่งบรรทัดที่ด้านบนของฟังก์ชันใดๆ ที่เปิดการเชื่อมต่อเครือข่าย ตัวอย่างในส่วนอื่นของ section นี้ที่รันกับ "192.168.1.20" จะทำงานได้เหมือนกันกับชื่อสาธารณะเช่น "api.example.com" -- แค่ resolve ก่อนเท่านั้น