9.18. MQTT ทีละไบต์¶
ณ จุดนี้ cam มีทุกส่วนที่จำเป็นสำหรับการสื่อสารกับบริการจริงบนอินเทอร์เน็ตสาธารณะ: TCP socket, TLS สำหรับการเข้ารหัส, DNS สำหรับระบุชื่อปลายทาง, และ asyncio เพื่อให้สคริปต์เดิมทำงานอื่นได้ขณะที่การเชื่อมต่อเปิดอยู่ MQTT คือโปรโตคอลแบบ wire ตัวแรกที่นำทั้งหมดนั้นมารวมกันเป็นสิ่งที่ผลิตภัณฑ์ที่ใช้งานจริงนำไปใช้
หน้านี้ครอบคลุมโปรโตคอลเอง ได้แก่ รูปแบบบน wire, บทบาทของผู้เข้าร่วมแต่ละฝ่าย และการแลกเปลี่ยนในการออกแบบ อย่างตรงไปตรงมา เพื่อให้ mqtt client ที่รวมมาด้วยดูเหมือนเป็นการห่อหุ้มสิ่งที่รู้อยู่แล้วอย่างชัดเจน แทนที่จะเป็นการก้าวกระโดดที่ต้องเชื่อใจ
9.18.1. Pub/sub เทียบกับ request/response¶
HTTP ซึ่งเป็นโปรโตคอลที่โปรเจกต์ cam ส่วนใหญ่เลือกใช้ก่อน เป็นแบบ request/response ไคลเอนต์ขอทรัพยากรเฉพาะจากเซิร์ฟเวอร์เฉพาะ และเซิร์ฟเวอร์ก็ตอบกลับ การแลกเปลี่ยนทุกครั้งเป็นแบบหนึ่งต่อหนึ่ง และทั้งสองฝั่งรู้ที่อยู่ของกันและกันล่วงหน้า
MQTT เป็นแบบ publish/subscribe ไคลเอนต์เชื่อมต่อกับบุคคลที่สามตรงกลางที่เรียกว่า broker publisher ส่งข้อความไปยัง topic ที่ตั้งชื่อไว้โดยไม่จำเป็นต้องรู้หรือสนใจว่ามีใครฟังอยู่ subscriber บอก broker ว่าต้องการ topic ใด และจะได้รับทุกข้อความที่ publish ไปยัง topic เหล่านั้นในภายหลัง broker คือตัวกระจาย: การ publish หนึ่งครั้งบน yard-cam/motion จะถึงทุกอุปกรณ์ที่ subscribe yard-cam/motion ไม่ว่าจะมีศูนย์ หนึ่ง หรือห้าสิบอุปกรณ์
สามสิ่งที่ตามมาจากการเปลี่ยนแปลงรูปแบบนี้:
การแยกออกจากกัน Publisher ไม่จำเป็นต้องรู้ว่ามี subscriber อยู่ subscriber สามารถเข้าออกได้โดยที่ publisher ไม่สังเกต การเพิ่ม dashboard ที่สองคือโค้ดหนึ่งบรรทัดบน dashboard ใหม่ โดยที่ cam ไม่ต้องเปลี่ยน
การกระจายแบบ fan-out broker จัดการการซ้ำทุกครั้ง ดังนั้น cam จึงส่งหนึ่งแพ็กเก็ตโดยไม่คำนึงถึงจำนวนอุปกรณ์ที่อ่าน นั่นคือกรณีการใช้งานที่ MQTT สร้างขึ้นมาเพื่อรองรับ
ความไม่สมมาตร broker กลายเป็นส่วนหนึ่งของโครงสร้างพื้นฐานที่จำเป็น หากไม่มี broker โปรโตคอลก็ไม่ทำงาน สำหรับโปรเจกต์ที่บ้าน มักจะใช้ broker สาธารณะฟรี (
test.mosquitto.org,broker.hivemq.com) หรือ broker เล็กๆ ที่คุณรันเอง
9.18.2. Topics¶
Topic คือสตริงที่คั่นด้วยเครื่องหมายทับ แนวทางปฏิบัติคือทั่วไปที่สุดทางซ้าย เฉพาะเจาะจงที่สุดทางขวา:
yard-cam/motion
yard-cam/temperature
workshop-cam/motion
workshop-cam/temperature/sensor-3
Wildcard สองตัวใช้ใน subscription (ไม่ใช่ใน publish):
+ตรงกับระดับเดียว+/motionsubscribe ไปยัง topic motion ของทุก cam;yard-cam/+subscribe ไปยัง sub-topic ทุกตัวของ yard-cam#ตรงกับระดับท้ายหนึ่งระดับหรือมากกว่าyard-cam/#subscribe ไปยังyard-cam/motion,yard-cam/temperature,yard-cam/temperature/sensor-3, และสิ่งอื่นใดใต้yard-cam/ต้องปรากฏที่ท้าย subscription
สตริง topic มีความแตกต่างระหว่างตัวพิมพ์ใหญ่และเล็ก ตามสเปค $ นำหน้าหมายถึง topic ภายใน broker ($SYS/...) ที่ publisher ไม่ควรเขียนถึง
9.18.3. รูปแบบแพ็กเก็ต¶
MQTT ทำงานบน TCP แพ็กเก็ตควบคุมทุกตัวเริ่มต้นด้วย fixed header หนึ่งไบต์ ตามด้วยฟิลด์ Remaining Length ที่มีความยาวแปรผัน จากนั้นเป็น variable header เฉพาะชนิดแพ็กเก็ต แล้วจึงตามด้วย payload รูปแบบภายนอกเดียวกันครอบคลุมทุกคำสั่ง ได้แก่ CONNECT, PUBLISH, SUBSCRIBE, PUBACK, DISCONNECT และอื่นๆ ซึ่งเป็นเหตุที่ MQTT client สามารถเขียนได้ในโค้ดไม่กี่ร้อยบรรทัด
fixed header คือหนึ่งไบต์:
บิต 7..4 คือ ประเภทแพ็กเก็ตควบคุม
0x3คือ PUBLISH (ดังนั้นไบต์แรกมักขึ้นต้นด้วย0x3?)0x1คือ CONNECT,0x2คือ CONNACK,0x8คือ SUBSCRIBE,0xCคือ PINGREQ,0xEคือ DISCONNECT เป็นต้นบิต 3..0 คือ flag เฉพาะชนิดแพ็กเก็ต สำหรับ PUBLISH flag เหล่านี้เข้ารหัส DUP retransmit flag, ระดับ QoS (2 บิต) และ RETAIN flag
Remaining Length คือจำนวนเต็มที่มีความยาวแปรผัน 1 ถึง 4 ไบต์ที่นับทุกไบต์หลังจากตัวมันเอง บิตบนสุดของแต่ละไบต์คือ continuation marker -- 1 หมายถึง "มีไบต์ความยาวอีกตัวตามมา", 0 หมายถึง "นี่คือตัวสุดท้าย" ความยาวที่น้อยกว่า 128 บรรจุในหนึ่งไบต์ payload ขนาดใหญ่ขึ้นใช้มากกว่านั้น ความยาวสูงสุดที่เข้ารหัสได้คือ 256 MiB
สำหรับ PUBLISH แล้ว variable header คือชื่อ topic ซึ่งได้แก่ ความยาว 2 ไบต์ตามด้วยไบต์ UTF-8 จากนั้นตามด้วย packet identifier 2 ไบต์ที่มีอยู่เฉพาะเมื่อ QoS เป็น 1 หรือ 2 ไบต์ที่เหลือคือ payload ซึ่งโปรโตคอลมองว่าเป็นไบต์ทึบแสง
PUBLISH แบบ QoS-0 ขั้นต่ำของ ok ไปยัง a/b คือ:
30 07 00 03 'a' '/' 'b' 'o' 'k'
30-- PUBLISH, flag ทั้งหมดเป็นศูนย์07-- ตามด้วย 7 ไบต์00 03-- ความยาว topic คือ 3'a' '/' 'b'-- topic'o' 'k'-- payload
เก้าไบต์บน wire และข้อความก็ส่งถึง subscriber ทุกคนที่รับฟัง a/b บน broker
9.18.4. ระดับ QoS¶
Quality-of-Service ควบคุมว่า broker (และไคลเอนต์) ทำงานหนักแค่ไหนเพื่อให้แน่ใจในการส่ง สามระดับคือ:
QoS 0 -- อย่างมากครั้งเดียว ยิงแล้วลืม แพ็กเก็ต PUBLISH ถูกส่งและไม่มีการยืนยัน หาก TCP ส่งได้ broker จะส่งต่อ หากการเชื่อมต่อหลุดระหว่างการส่ง ข้อความก็หายไป ข้อมูลเทเลเมตรีจาก sensor ส่วนใหญ่ไม่มีปัญหากับ QoS 0 -- การอ่านอุณหภูมิที่หายไปหนึ่งครั้งในสตรีมที่ส่งทุก 30 วินาทีไม่สำคัญ
QoS 1 -- อย่างน้อยครั้งเดียว publisher ใส่ packet identifier และรอ PUBACK หากไม่ได้รับ PUBACK ก่อนหมดเวลา publisher จะส่งซ้ำโดยตั้ง DUP flag broker อาจส่งข้อความเดียวกันสองครั้งให้ subscriber ในระดับเดียวกัน subscriber จึงต้องพร้อมรับมือกับการส่งซ้ำ
QoS 2 -- ครั้งเดียวพอดี การ handshake สี่ขั้นตอน (PUBREC / PUBREL / PUBCOMP) ทำให้แน่ใจว่าข้อความส่งถึงครั้งเดียวพอดี แม้จะมีการเชื่อมต่อใหม่ ใช้ทรัพยากรมากสำหรับ round-trip และ state ของ broker แอป cam เพียงไม่กี่ตัวที่ต้องการ
mqtt client ที่รวมมาด้วยรองรับ QoS 0 และ QoS 1; QoS 2 จะ raise error หากขอใช้ สำหรับ cam ที่รายงานค่า sensor QoS 0 มักเป็นคำตอบที่ถูกต้องเสมอ
9.18.5. ข้อความที่เก็บไว้และ last will¶
มีสองฟีเจอร์ที่ควรรู้เพราะจะเปลี่ยนสิ่งที่ broker จดจำเกี่ยวกับ topic ของคุณ
RETAIN หาก PUBLISH มี RETAIN flag ตั้งไว้ broker จะเก็บข้อความและส่งต่อให้ subscriber ในอนาคต ทุกคนทันทีที่พวกเขา subscribe นั่นคือวิธีที่ MQTT จัดการกับคำถาม "ค่าปัจจุบันคืออะไร?" sensor publish การอ่านค่าล่าสุดแบบ retained และ dashboard ที่ subscribe สิบนาทีต่อมาก็ยังได้รับค่าล่าสุดแทนที่จะรอการ publish ครั้งถัดไป การ publish ซ้ำด้วย topic เดียวกันจะเขียนทับค่า retained; การ publish payload ว่างจะลบค่านั้น
Last will เมื่อไคลเอนต์เชื่อมต่อ สามารถให้ broker ว่า "พินัยกรรมสุดท้าย": topic, payload, QoS และ retain flag หากไคลเอนต์นั้นตัดการเชื่อมต่อ อย่างไม่สมบูรณ์ -- TCP RESET, ไฟดับ, เครือข่ายหลุดโดยไม่มีแพ็กเก็ต DISCONNECT -- broker จะ publish will ในนามของไคลเอนต์ subscriber จะเห็นมันเป็นการแจ้งเตือนจาก cam ว่าออฟไลน์แล้ว cam เองไม่ส่ง will นั้น broker ส่ง เพราะตอนนั้น cam ไม่อยู่แล้ว
9.18.6. Keepalive และการเชื่อมต่อใหม่¶
CONNECT บรรจุช่วงเวลา keepalive เป็นวินาที หากไคลเอนต์เงียบนานเท่านั้น broker จะถือว่าตายแล้ว เพื่อป้องกันสิ่งนี้ ไคลเอนต์จะส่ง PINGREQ เป็นระยะ (หนึ่งไบต์: 0xC0) และได้รับ PINGRESP กลับมา (0xD0) ซึ่งเป็น heartbeat ที่เล็กและถูกที่สุดที่โปรโตคอลรองรับได้ แอป cam ส่วนใหญ่ตั้ง keepalive ที่ 30 หรือ 60 วินาที
หาก TCP เชื่อมต่อขาด ทั้งสองฝั่งจะสังเกตเห็นและเชื่อมต่อใหม่ตั้งแต่ต้น subscription ที่ทำก่อนหลุดจะสูญหายเว้นแต่ไคลเอนต์ใช้ persistent session ในการเชื่อมต่อ สำหรับแอป cam ง่ายๆ รูปแบบ resubscribe-on-reconnect สั้นกว่าและดีพอๆ กัน
ข้อมูลนี้เพียงพอสำหรับการอ่านสเปค MQTT หรือเขียน client เองบน socket.socket client ที่รวมมาใน mqtt ทำสิ่งนั้นพอดี บวกกับ API ที่สมเหตุสมผลสำหรับโค้ดแอปพลิเคชัน