Tìm hiểu về định dạng của một số gói tin MQTT

Nếu như ở bài viết trước chúng ta đã tìm hiểu tổng quan về ưu điểm và ứng dụng của MQTT thì ở bài viết này, các bạn sẽ được tìm hiểu sâu hơn về cơ chế truyền nhận gói tin qua giao thức MQTT giữa các Client, và định dạng của một số gói tin MQTT đó. 

Một Client có thể đóng vai trò là một Publisher khi thực hiện publish một gói tin hoặc là một Subscriber khi thực hiện subscribe một Topic. Các Client muốn thực hiện publish hoặc subscribe thì phải thực hiện các bước sau:
• Thiết lập kết nối TCP đến máy chủ có chứa Broker
• Thiết lập kết nối đến Broker bằng cách gửi gói tin CONNECT 
• Đợi gói tin phản hồi CONNACK từ Broker để kiểm tra kết nối được thiết lập thành công hay thất bại
• Nếu kết nối thành công, Client có thể gửi gói tin PUBLISH hoặc SUBSCRIBE đến một Topic tại Broker.
• Khi quá trình truyền nhận kết thúc, Client có thể ngắt kết nối đến Broker bằng cách gửi gói tin DISCONNECT. Nếu Client muốn giữ kết nối đến Broker khi không có bất kỳ hoạt động nào diễn ra trong một khoảng thời gian nhất định thì Client thực hiện gửi gói tin PINGREQ để thông báo Broker rằng kết nối này vẫn được duy trì. Các bạn có thể hình dung về quá trình hoạt động giữa hai Client (một Subscriber, một Publisher) và Broker trong giao thức MQTT qua ví dụ sau:

Các gói tin CONNECT, CONNACK, PUBLISH, SUBSCRIBE, SUBACK ở ví dụ trên được gọi là các gói tin MQTT Control. Thông tin về danh sách các gói tin MQTT hiện có ở version 3.1.1 và mô tả của từng gói tin được thể hiện ở bảng sau:

Tiếp theo mình sẽ đưa ra định dạng chung của gói tin MQTT Control và phân tích cấu trúc của một số gói tin thông dụng như CONNECT, CONNACK, PUBLISH, SUBSCRIBE, SUBACK.

1. Định dạng chung của các gói tin MQTT Control

Trong một gói tin MQTT Control sẽ có tối đa 3 thành phần gồm Fixed Header, Variable Header và Payload. Các gói tin MQTT Control ngoài chứa mã định danh còn chứa một số thông tin khác như tên Topic, Client ID, tên User,…
Trường Fixed Header bắt buộc phải có trong các gói tin MQTT Control, đây là trường phân biệt các gói tin CONNECT, PUBLISH, SUBSCRIBE, PINGREQ, DISCONNECT…
Hai trường Variable Header và Payload là hai trường không bắt buộc, tùy vào gói tin MQTT Control mà có thể có hoặc không 2 trường này. Ví dụ:
– Gói tin CONNACT: Chỉ có trường Fixed Header.
– Gói tin PUBACK: Gồm các trường Fixed Header và Variable Header.
– Gói tin CONNECT: Gồm các trường Fixed Header, Variable Header và Payload.

1.1. Fixed Header
Trường này có tối thiểu 2 byte. Fixed Header chứa MQTT Control Packet type & Flags và Remaining Length. Trường Remaining Length sẽ cho biết tổng số byte của Variable Header và Payload (tối thiểu là 0 Byte và tối đa là 256MByte). Ở đây chúng ta sẽ phân tích trường MQTT Control Packet type & Flags, đây là trường được dùng để phân biệt các gói tin MQTT Control với nhau.

– Trong 8 bits của trường MQTT Control Packet type & Flags này, 4 bits có trọng số cao (MSB) thể hiện Command Type, 4 bits còn lại là các bit Control Flag chứa cờ cụ thể cho từng loại Command Type.
– Mỗi Control Packet sẽ có một Command Type riêng biệt, bảng Command Type của các gói tin MQTT Control các bạn tham khảo ở đây. Tham khảo giá trị các bit trường Control Flag của các gói MQTT Control ở đây.
– Ví dụ: Command Type của gói tin CONNECT có giá trị bằng 0001 và 4 bits control Flag bằng 0000 nên giá trị trường MQTT Control Packet type & Flags sẽ là 0b00010000.

1.2 Variable Header
Đây là trường không bắt buộc có trong các gói tin MQTT Control. Một vài gói tin như CONNECT, PUBLISH, SUBSCRIBE,… sử dụng trường Variable Header này để cung cấp thêm thông tin và chúng khác nhau tùy thuộc vào các gói tin. 

1.3 Payload
Cuối một số gói tin MQTT Control có thể chứa một trường Payload vì trường Payload là tùy chọn và không bắt buộc phải xuất hiện trong tất cả các gói tin MQTT như trường Fixed Header. Trường này thường chứa nội dung dữ liệu được gửi. Ví dụ:
– Ở gói tin CONNECT, Payload sẽ là ClientID, username và password (nếu có).
– Ở gói tin PUBLISH, Payload sẽ là nội dung dữ liệu cần gửi đi.

2. Giới thiệu một số gói tin MQTT Control 
2.1. Gói tin CONNECT 
– Sau khi một kết nối TCP được thiết lập bởi Client với máy chủ chứa Broker, gói đầu tiên được gửi từ Client đến Broker phải là gói CONNECT. Gói CONNECT này sẽ được gửi 1 lần duy nhất trong lần kết nối đó, nếu gói CONNECT được gửi đi lần hai thì Broker sẽ xử lý gói tin này như gói tin lỗi và ngắt kết nối đến Client.
– Ví dụ của gói tin CONNECT:

2.2. Gói tin CONNACK 
– Gói tin CONNACK được Broker phản hồi về Client sau khi Client gửi gói tin CONNECT. Gói tin đầu tiên Broker gửi đến Client sau khi thiết lập kết nối là gói tin CONNACK.
– Nếu Client không nhận được gói tin CONNACT thì Client nên đóng kết nối TCP đã thiết lập với máy chủ.

2.3. Gói tin PUBLISH 
– Khi Client publish một gói tin đến một Topic cụ thể thì gói tin này sẽ được đưa đến Broker và nhiệm vụ của Broker lúc này là publish gói tin trên đến các Client đã đăng ký theo dõi cùng Topic đó.

Giải thích thêm về dupFlag và qos trong Fixed Header của gói tin PUBLISH:
– dupFlag (Cờ Duplicate):
     + Nếu dupFlag có giá trị bằng 0 (hoặc false), điều này chỉ ra rằng đây là lần đầu tiên mà Client hoặc Broker đã gửi gói MQTT PUBLISH này.
     + Nếu dupFlag được đặt thành 1, nó chỉ ra rằng đây có thể là lần gửi lại của gói tin trước đó.
     + dupFlag phải được đặt bằng 0 khi các gói tin có qos bằng 0.
– qos (Quality of Service): là thông số thể hiện mức độ đảm bảo việc truyền nhận các gói tin giữa bên gửi và bên nhận. Ý nghĩa giá trị của các mức QoS như sau:
     + QoS = 0 (at-most-once): Mỗi gói tin được gửi đến đích tối đa một lần.
     + QoS = 1 (at-least-one): Mỗi gói tin được gửi đến đích tối thiểu là một lần.
     + QoS = 2 (exactly-once): Mỗi gói tin sẽ được gửi đến đích duy nhất là một lần.
    + Chi tiết về QoS cũng như mối quan hệ QoS giữa Publisher và Subscriber trong giao thức MQTT xem ở đây.

2.4. SUBSCRIBE packet
– Khi Client thực hiện publish gói tin đến một Topic cụ thể, nếu có bất kỳ Client nào khác đã đăng ký theo dõi (subscribe) Topic đó thì Client đó sẽ nhận được gói tin. Để đăng ký theo dõi một Topic, Client phải gửi gói SUBSCRIBE đến Broker.
– Mỗi Client có thể subscribe một hoặc nhiều Topic khác nhau.

2.5. SUBACK packet
– Gói SUBACK được Broker phản hồi về Client để xác nhận việc Broker đã nhận và xử lý gói SUBSCRIBE.
– Gói SUBACK chứa một danh sách các mã trả về (return codes), chỉ định mức QoS tối đa đã được cấp trong mỗi gói tin SUBSCRIBE.

Trên đây là ví dụ cấu trúc của một số gói tin MQTT thường gặp như CONNECT, CONNACK, PUBLISH, SUBSCRIBE, SUBACK. Nội dung của các gói tin trên có thể thay đổi tùy theo mục đích của người phát triển, để hiểu rõ hơn về cấu trúc của các gói tin đã nêu trên và các gói tin MQTT còn lại như PUBREC, PUBREL, PUBCOMP, UNSUBSCRIBE, UNSUBACK, PINGREQ, PINGRESP, DISCONNECT, các bạn tham khảo ở đây. Trong bài viết tiếp theo mình sẽ dựa vào các nội dung ở trên và hướng dẫn các bạn thực hiện kết nối đến một MQTT Broker, publish một gói tin đến Topic và cách subscribe Topic từ TCP Client bất kỳ.

Chúc các bạn thành công!

TAPIT ARM R&D

[HỌC ONLINE: LẬP TRÌNH VI ĐIỀU KHIỂN STM32, VI XỬ LÝ ARM CORTEX – M]
Xem thêm Tổng hợp các bài hướng dẫn Lập trình vi điều khiển STM32 tại đây.
Xem thêm Tổng hợp hướng dẫn Internet of Things với NodeMCU ESP8266 và ESP32 tại đây.