MQTT vs HTTP:物聯網通訊協定怎麼選?
物聯網裝置需要與雲端通訊,選擇適合的通訊協定直接影響系統效能、電池壽命和維護成本。本文深入比較 MQTT 與 HTTP 兩種主流協定,幫助你做出最佳選擇。
協定概述
HTTP(HyperText Transfer Protocol)
HTTP 是 Web 的基礎協定,採用請求-回應模式:
客戶端 ──請求──> 伺服器
客戶端 <──回應── 伺服器
特點:
- 無狀態協定
- 請求-回應模式
- 文字型標頭(HTTP/1.1)
- 廣泛支援與成熟工具鏈
MQTT(Message Queuing Telemetry Transport)
MQTT 是專為物聯網設計的輕量級發布-訂閱協定:
發布者 ──發布訊息──> Broker ──推送──> 訂閱者
特點:
- 發布/訂閱模式
- 極小封包開銷
- 持久連線
- QoS(服務品質)保證
效能比較
頻寬消耗
| 項目 | MQTT | HTTP |
|---|---|---|
| 標頭大小 | 2-5 bytes | 700+ bytes |
| 連線建立 | 一次 | 每次請求 |
| Keep-alive | 極小 | N/A |
對於頻繁發送小量資料的場景,MQTT 的頻寬優勢非常明顯:
// MQTT 發送感測器資料(ESP32)
void send_mqtt_data(float temperature, float humidity) {
char payload[64];
snprintf(payload, sizeof(payload),
"{\"temp\":%.1f,\"hum\":%.1f}", temperature, humidity);
// 實際傳輸:2 bytes 固定標頭 + topic長度 + payload
// 總計約 30-50 bytes
mqtt_client_publish(client, "sensors/living-room", payload, 0, 0);
}
// HTTP 發送相同資料
void send_http_data(float temperature, float humidity) {
char body[64];
snprintf(body, sizeof(body),
"{\"temp\":%.1f,\"hum\":%.1f}", temperature, humidity);
// HTTP 請求包含:
// - 請求行:POST /api/sensors HTTP/1.1
// - Host, Content-Type, Content-Length 等標頭
// - 空行 + body
// 總計約 300-500 bytes
http_client_post(client, "/api/sensors", body);
}
延遲比較
MQTT 的持久連線大幅降低延遲:
| 場景 | MQTT | HTTP |
|---|---|---|
| 首次連線 | ~100ms | ~100ms |
| 後續訊息 | ~10ms | ~100ms |
| 伺服器推送 | 即時 | 需輪詢 |
電力消耗
對於電池供電裝置,MQTT 更省電:
HTTP 每次請求流程:
1. TCP 握手(3 次往返)
2. TLS 握手(2-4 次往返)
3. HTTP 請求/回應
4. 連線關閉
MQTT 持久連線:
1. 初始連線建立(同 HTTP)
2. 後續只需發送小封包
3. Keep-alive 維持連線
實測數據(ESP32 + WiFi):
| 操作 | MQTT | HTTP |
|---|---|---|
| 發送一筆資料 | 15 mAs | 80 mAs |
| 每分鐘發送一次 | 900 mAs/hr | 4800 mAs/hr |
功能比較
訊息品質保證(QoS)
MQTT 提供三種 QoS 等級:
// QoS 0:最多一次(Fire and Forget)
// 不保證送達,最低開銷
mqtt_publish(client, topic, payload, QOS_0, false);
// QoS 1:至少一次
// 確保送達,可能重複
mqtt_publish(client, topic, payload, QOS_1, false);
// QoS 2:剛好一次
// 確保送達且不重複,開銷最大
mqtt_publish(client, topic, payload, QOS_2, false);
HTTP 需要應用層自行實作重試機制。
伺服器推送
MQTT 原生支援伺服器推送:
// 訂閱主題,當有新訊息時自動接收
void mqtt_message_handler(char *topic, char *payload) {
if (strcmp(topic, "commands/device-001") == 0) {
handle_command(payload);
}
}
mqtt_subscribe(client, "commands/device-001", QOS_1, mqtt_message_handler);
HTTP 需要使用以下方式實現:
- 輪詢(Polling):定時請求,浪費頻寬
- 長輪詢(Long Polling):佔用連線
- WebSocket:額外複雜度
離線訊息
MQTT 支援離線訊息緩存:
// 設定 Clean Session = false,Broker 會保留離線訊息
mqtt_connect_options opts = {
.client_id = "device-001",
.clean_session = false, // 保留會話狀態
};
// 使用 Retained Message,新訂閱者會收到最後一則訊息
mqtt_publish(client, "status/device-001", "online", QOS_1, true);
實作範例
ESP32 MQTT 客戶端
#include "mqtt_client.h"
static esp_mqtt_client_handle_t mqtt_client;
// MQTT 事件處理
static void mqtt_event_handler(void *args, esp_event_base_t base,
int32_t event_id, void *event_data) {
esp_mqtt_event_handle_t event = event_data;
switch (event->event_id) {
case MQTT_EVENT_CONNECTED:
ESP_LOGI(TAG, "MQTT 已連線");
// 訂閱命令主題
esp_mqtt_client_subscribe(mqtt_client, "commands/#", 1);
break;
case MQTT_EVENT_DATA:
ESP_LOGI(TAG, "收到訊息: %.*s", event->data_len, event->data);
process_message(event->topic, event->data);
break;
case MQTT_EVENT_DISCONNECTED:
ESP_LOGW(TAG, "MQTT 斷線,自動重連中...");
break;
}
}
void mqtt_init(void) {
esp_mqtt_client_config_t config = {
.broker.address.uri = "mqtts://broker.example.com",
.credentials.client_id = "esp32-device-001",
.session.keepalive = 60,
};
mqtt_client = esp_mqtt_client_init(&config);
esp_mqtt_client_register_event(mqtt_client, ESP_EVENT_ANY_ID,
mqtt_event_handler, NULL);
esp_mqtt_client_start(mqtt_client);
}
// 發送感測器資料
void publish_sensor_data(float temp, float humidity) {
char payload[128];
snprintf(payload, sizeof(payload),
"{\"temperature\":%.2f,\"humidity\":%.2f,\"timestamp\":%lld}",
temp, humidity, esp_timer_get_time() / 1000);
esp_mqtt_client_publish(mqtt_client, "sensors/esp32-001",
payload, 0, 1, 0);
}
Node.js MQTT Broker 端
const mqtt = require('mqtt');
const client = mqtt.connect('mqtt://broker.example.com');
// 訂閱所有感測器資料
client.on('connect', () => {
client.subscribe('sensors/#', (err) => {
if (!err) console.log('已訂閱 sensors 主題');
});
});
// 處理收到的訊息
client.on('message', (topic, message) => {
const data = JSON.parse(message.toString());
console.log(`[${topic}] 溫度: ${data.temperature}°C`);
// 儲存到資料庫
saveToDatabase(topic, data);
// 檢查是否需要告警
if (data.temperature > 30) {
client.publish('alerts/high-temp', JSON.stringify({
device: topic.split('/')[1],
temperature: data.temperature,
}));
}
});
選擇建議
選擇 MQTT 的情況
- 頻繁小量資料傳輸:感測器每秒或每分鐘上報
- 電池供電裝置:需要最小化無線傳輸
- 需要即時推送:遠端控制、告警通知
- 不穩定網路:QoS 保證訊息送達
- 大量裝置:萬級裝置同時連線
選擇 HTTP 的情況
- 偶發性資料上傳:一天幾次的回報
- 大量資料傳輸:韌體更新、日誌上傳
- 與 Web 服務整合:使用現有 REST API
- 簡單實作:不需要額外部署 Broker
- 防火牆限制:只開放 80/443 port
混合架構
許多系統會同時使用兩種協定:
┌─────────────────────────────────────────────────────┐
│ 雲端服務 │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ MQTT Broker │ │ REST API │ │
│ │ (即時資料) │ │ (查詢/設定) │ │
│ └──────┬──────┘ └──────┬──────┘ │
└─────────┼───────────────────────┼───────────────────┘
│ │
MQTT │ HTTP │
▼ ▼
┌─────────────────────────────────────────────────────┐
│ 物聯網裝置 │
│ - 即時感測資料 → MQTT │
│ - 韌體更新 → HTTP │
│ - 設定變更 → HTTP │
└─────────────────────────────────────────────────────┘
結論
MQTT 和 HTTP 各有優勢,選擇時應考慮:
| 考量點 | MQTT 更適合 | HTTP 更適合 |
|---|---|---|
| 傳輸頻率 | 高頻 | 低頻 |
| 資料大小 | 小量 | 大量 |
| 即時性 | 需要 | 不需要 |
| 電力預算 | 受限 | 充足 |
| 基礎設施 | 可部署 Broker | 使用現有服務 |
如果你正在規劃物聯網專案,歡迎聯繫我們討論最適合的通訊架構。