9.18. MQTT, từng byte một

Đến đây, camera đã có đủ mọi thứ cần thiết để giao tiếp với một dịch vụ thực sự trên internet: một TCP socket, TLS để bảo vệ kết nối, DNS để tra cứu địa chỉ đối tác, và asyncio để cho phép cùng một tập lệnh thực hiện các công việc khác trong khi kết nối đang mở. MQTT là giao thức dây đầu tiên kết hợp tất cả những thứ đó thành thứ gì đó mà một sản phẩm triển khai thực sự sử dụng.

Trang này đề cập đến bản thân giao thức -- định dạng trên đường truyền, vai trò mà mỗi bên tham gia đóng, và các đánh đổi trong thiết kế của nó -- đủ trung thực để client mqtt đi kèm trông như một lớp bọc hiển nhiên của những gì đã biết thay vì một bước nhảy vọt của niềm tin.

9.18.1. Pub/sub so với request/response

HTTP -- giao thức mà hầu hết các dự án camera tiếp cận đầu tiên -- là request/response. Một client yêu cầu một tài nguyên cụ thể từ một máy chủ cụ thể; máy chủ trả lời. Mỗi trao đổi là một-một, và cả hai đầu đều biết địa chỉ của nhau từ trước.

MQTT là publish/subscribe. Các client kết nối đến một bên thứ ba ở giữa gọi là broker. Một publisher gửi thông điệp đến một topic được đặt tên mà không cần biết hay quan tâm ai đang lắng nghe. Một subscriber thông báo cho broker những topic nó muốn và nhận mọi thông điệp được công bố đến những topic đó. Broker là bộ phân phối: một lần publish trên yard-cam/motion sẽ đến mọi thiết bị đã subscribe yard-cam/motion, dù có không, một hay năm mươi thiết bị như vậy.

Ba điều theo sau từ sự thay đổi mô hình đó:

  • Tách rời. Các publisher không cần biết subscriber tồn tại. Subscriber có thể đến và đi mà publisher không nhận thấy. Thêm một dashboard thứ hai chỉ cần một dòng code trên dashboard mới; camera không thay đổi.

  • Fan-out. Broker xử lý mọi bản sao, nên camera chỉ gửi một gói tin bất kể có bao nhiêu thiết bị đọc nó. Đó là trường hợp sử dụng mà MQTT được xây dựng cho.

  • Bất đối xứng. Broker bây giờ là một thành phần cơ sở hạ tầng bắt buộc -- không có nó, giao thức không hoạt động. Đối với các dự án tại nhà, đây thường là một broker công cộng miễn phí (test.mosquitto.org, broker.hivemq.com) hoặc một broker nhỏ bạn tự chạy.

One cam publishing to a yard-cam/motion topic on a broker while two browser dashboards and one cloud archiver each receive the same message.

9.18.2. Topics

Các topic là các chuỗi được phân tách bằng dấu gạch chéo. Quy ước là tổng quát nhất ở bên trái, cụ thể nhất ở bên phải:

yard-cam/motion
yard-cam/temperature
workshop-cam/motion
workshop-cam/temperature/sensor-3

Hai ký tự đại diện hoạt động trong subscription (không phải trong publish):

  • + khớp với một cấp đơn. +/motion subscribe đến topic motion của mọi camera; yard-cam/+ subscribe đến mọi sub-topic của yard-cam.

  • # khớp với một hoặc nhiều cấp cuối. yard-cam/# subscribe đến yard-cam/motion, yard-cam/temperature, yard-cam/temperature/sensor-3, và bất cứ thứ gì khác dưới yard-cam/. Nó phải xuất hiện ở cuối subscription.

Các chuỗi topic phân biệt chữ hoa chữ thường. Theo spec, $ dẫn đầu đánh dấu các topic nội bộ của broker ($SYS/...) mà các publisher không nên ghi vào.

9.18.3. Định dạng gói tin

MQTT chạy trên TCP. Mỗi gói tin điều khiển bắt đầu bằng một byte fixed header theo sau là trường Remaining Length có độ dài biến đổi, sau đó là variable header dành riêng cho từng loại gói tin, rồi đến payload. Cùng một định dạng bên ngoài bao gồm mọi lệnh -- CONNECT, PUBLISH, SUBSCRIBE, PUBACK, DISCONNECT, và các lệnh khác -- đó là lý do tại sao một MQTT client có thể được viết trong vài trăm dòng.

The byte layout of an MQTT PUBLISH packet showing the fixed-header type and flags byte, the variable-length Remaining Length field, the topic name, the optional packet identifier, and the payload bytes.

Fixed header có một byte:

  • Các bit 7..4 là loại gói tin điều khiển. 0x3 là PUBLISH (vì vậy byte đầu tiên thường bắt đầu bằng 0x3?). 0x1 là CONNECT, 0x2 là CONNACK, 0x8 là SUBSCRIBE, 0xC là PINGREQ, 0xE là DISCONNECT, v.v.

  • Các bit 3..0 là cờ dành riêng cho loại gói tin. Đối với PUBLISH, các cờ mã hóa cờ DUP retransmit, mức QoS (2 bit), và cờ RETAIN.

Remaining Length là một số nguyên có độ dài biến đổi 1 đến 4 byte đếm mọi byte sau chính nó. Bit đầu của mỗi byte là marker tiếp nối -- 1 nghĩa là "còn một byte độ dài nữa", 0 nghĩa là "đây là byte cuối cùng". Độ dài dưới 128 vừa với một byte; các payload lớn hơn dùng nhiều byte hơn. Độ dài mã hóa tối đa là 256 MiB.

Đối với một PUBLISH, variable header là tên topic -- 2 byte độ dài, rồi các byte UTF-8 -- theo sau là 2 byte packet identifier chỉ tồn tại khi QoS là 1 hoặc 2. Các byte còn lại là payload, được giao thức coi như các byte mờ đục.

Một gói tin PUBLISH QoS-0 tối giản của ok đến a/b là:

30 07 00 03 'a' '/' 'b' 'o' 'k'
  • 30 -- PUBLISH, tất cả cờ bằng không.

  • 07 -- 7 byte theo sau.

  • 00 03 -- độ dài topic là 3.

  • 'a' '/' 'b' -- topic.

  • 'o' 'k' -- payload.

Chín byte trên đường truyền và thông điệp đến mọi subscriber của a/b trên broker.

9.18.4. Các mức QoS

Quality-of-Service kiểm soát mức độ nỗ lực mà broker (và client) thực hiện để đảm bảo giao hàng. Ba mức:

QoS 0 -- nhiều nhất một lần. Gửi và quên. Gói tin PUBLISH được gửi đi và không bao giờ được xác nhận. Nếu TCP giao hàng, broker chuyển tiếp. Nếu kết nối bị ngắt trong khi gửi, thông điệp mất. Hầu hết dữ liệu telemetry từ cảm biến là ổn với QoS 0 -- một lần đọc nhiệt độ bị bỏ lỡ trong một luồng phát mỗi 30 giây không quan trọng.

QoS 1 -- ít nhất một lần. Publisher bao gồm một packet identifier và chờ PUBACK. Nếu không có PUBACK đến trước khi hết thời gian, publisher phát lại với cờ DUP được đặt. Broker có thể giao cùng một thông điệp hai lần cho một subscriber ở cùng mức; subscriber phải sẵn sàng xử lý các bản sao.

QoS 2 -- đúng một lần. Bắt tay bốn bước (PUBREC / PUBREL / PUBCOMP) đảm bảo thông điệp đến đúng một lần, ngay cả sau khi kết nối lại. Tốn kém về round-trip và trạng thái broker. Ít ứng dụng camera nào cần nó.

Client mqtt đi kèm triển khai QoS 0 và QoS 1; QoS 2 sẽ báo lỗi nếu bạn yêu cầu. Đối với một camera báo cáo kết quả đọc cảm biến, QoS 0 hầu như luôn là câu trả lời đúng.

9.18.5. Retained message và last will

Hai tính năng đáng biết vì chúng thay đổi những gì broker nhớ về topic của bạn.

RETAIN. Nếu một gói tin PUBLISH có cờ RETAIN được đặt, broker lưu trữ thông điệp và chuyển tiếp đến mọi subscriber trong tương lai ngay khi họ subscribe. Đó là cách MQTT xử lý câu hỏi "giá trị hiện tại là gì?" -- một cảm biến publish kết quả đọc mới nhất với retain, và một dashboard subscribe mười phút sau vẫn nhận được giá trị gần đây nhất thay vì phải chờ lần publish tiếp theo. Publish lại với cùng topic sẽ ghi đè giá trị retained; publish payload rỗng sẽ xóa nó.

Last will. Khi client kết nối, nó có thể cung cấp cho broker một "di chúc": một topic, một payload, một QoS và một cờ retain. Nếu client đó ngắt kết nối không sạch -- TCP RESET, mất điện, mạng bị ngắt mà không có gói tin DISCONNECT -- broker sẽ publish will thay mặt client. Subscriber thấy nó như thông báo của camera rằng nó đã ngoại tuyến. Bản thân camera không bao giờ gửi will; broker làm điều đó, vì lúc đó camera đã biến mất.

9.18.6. Keepalive và kết nối lại

CONNECT mang theo một khoảng thời gian keepalive tính bằng giây. Nếu client im lặng trong khoảng thời gian đó, broker coi nó đã chết. Để ngăn điều đó, client định kỳ gửi PINGREQ (một byte: 0xC0) và nhận lại PINGRESP (0xD0) -- nhịp tim nhỏ nhất và rẻ nhất mà giao thức có thể mang. Hầu hết ứng dụng camera đặt keepalive là 30 hoặc 60 giây.

Nếu kết nối TCP bị ngắt, cả hai phía nhận thấy và kết nối lại từ đầu. Các subscription thực hiện trước khi ngắt kết nối bị mất trừ khi client sử dụng persistent session khi kết nối; đối với các ứng dụng camera đơn giản, mẫu resubscribe-on-reconnect ngắn hơn và cũng tốt như vậy.

Điều này đủ để đọc MQTT spec hoặc tự viết một client qua socket.socket. Client đi kèm trong mqtt làm chính xác điều đó, cùng với một API hợp lý cho code ứng dụng.