Back to blog

Giao Thức Truyền Thông IoT: MQTT, CoAP, AMQP & WebSocket

iotmqttprotocolsbackendtutorial
Giao Thức Truyền Thông IoT: MQTT, CoAP, AMQP & WebSocket

Chào mừng bạn đến với Phase 2 của Lộ Trình IoT Patterns & Strategies! Trong Phase 1, chúng ta đã tìm hiểu bức tranh tổng thể — kiến trúc phân tầng, loại thiết bị, lựa chọn kết nối, và message patterns. Giờ là lúc đi sâu vào các giao thức thực sự vận chuyển dữ liệu IoT của bạn.

Chọn đúng giao thức có thể quyết định thành bại của hệ thống IoT. Chọn MQTT khi đáng ra phải dùng CoAP và các cảm biến chạy pin của bạn hết pin sau vài tuần thay vì vài năm. Chọn HTTP khi đáng ra phải dùng MQTT và broker của bạn quá tải với 10.000 thiết bị. Chọn AMQP khi cần pub/sub nhẹ và các thiết bị hạn chế không kham nổi overhead.

Đến cuối bài viết này, bạn sẽ biết chính xác giao thức nào nên dùng, cách thiết lập, và cách bridge các giao thức khi hệ thống cần nhiều hơn một.

Bạn Sẽ Học Được Gì

✅ Thiết lập và cấu hình MQTT broker (Mosquitto) từ đầu
✅ Hiểu các mức QoS của MQTT và khi nào dùng từng mức
✅ Triển khai CoAP cho các thiết bị hạn chế dựa trên UDP
✅ Cấu hình AMQP với RabbitMQ cho enterprise IoT backends
✅ Xây dựng dashboard IoT thời gian thực với WebSocket và MQTT-over-WebSocket
✅ Thiết kế protocol bridges cho hệ thống IoT đa giao thức
✅ Áp dụng framework quyết định để chọn đúng giao thức cho mọi tình huống


Tổng Quan Bối Cảnh Giao Thức

Trước khi đi sâu vào từng giao thức, hãy xem chúng liên quan với nhau như thế nào:

Giao ThứcTransportMô HìnhHeader Tối ThiểuKết NốiPhù Hợp Nhất Cho
MQTTTCPPub/Sub2 bytesPersistentIoT telemetry tổng quát
CoAPUDPRequest/Response4 bytesConnectionlessCảm biến chạy pin
AMQPTCPQueues + Pub/Sub8 bytesPersistentEnterprise IoT backends
WebSocketTCPFull-duplex2 bytesPersistentDashboard trên trình duyệt
HTTPTCPRequest/Response~200+ bytesTheo requestCloud APIs, webhooks

MQTT — Tiêu Chuẩn IoT

MQTT (Message Queuing Telemetry Transport) là giao thức IoT chiếm ưu thế. Được thiết kế năm 1999 bởi Andy Stanford-Clark (IBM) và Arlen Nipper để giám sát đường ống dầu qua liên kết vệ tinh, nó được xây dựng cho mạng không ổn định với băng thông hạn chế — chính xác là những gì IoT cần.

Cách MQTT Hoạt Động

MQTT sử dụng mô hình publish-subscribe với một broker trung tâm:

Các khái niệm chính:

  • Publisher: Gửi message đến một topic (không biết ai đang lắng nghe)
  • Subscriber: Nhận message từ các topic mà nó quan tâm
  • Broker: Định tuyến message từ publishers đến subscribers
  • Topic: Một chuỗi phân cấp như factory/zone-1/temp/sensor-01

Thiết Lập Mosquitto (MQTT Broker)

Mosquitto là MQTT broker mã nguồn mở phổ biến nhất. Hãy thiết lập với Docker:

# docker-compose.yml
services:
  mosquitto:
    image: eclipse-mosquitto:2
    container_name: mqtt-broker
    ports:
      - "1883:1883"   # MQTT
      - "9001:9001"   # WebSocket (cho browser clients)
    volumes:
      - ./mosquitto/config:/mosquitto/config
      - ./mosquitto/data:/mosquitto/data
      - ./mosquitto/log:/mosquitto/log
    restart: unless-stopped
# mosquitto/config/mosquitto.conf
listener 1883
protocol mqtt
 
listener 9001
protocol websockets
 
# Xác thực (chỉ tắt cho development)
allow_anonymous true
 
# Persistence
persistence true
persistence_location /mosquitto/data/
 
# Logging
log_dest file /mosquitto/log/mosquitto.log
log_type all
# Khởi động broker
docker compose up -d
 
# Xác nhận đang chạy
docker compose logs mosquitto
# Output: mosquitto version 2.x.x running

MQTT Topics và Wildcards

Topics là các chuỗi phân cấp được phân tách bằng /. Hãy thiết kế cẩn thận — một cấu trúc topic tốt giống như một database schema tốt.

# Quy ước đặt tên topic: {tổ chức}/{vị trí}/{loại thiết bị}/{id thiết bị}/{loại dữ liệu}
factory/building-a/sensors/temp-042/readings
factory/building-a/sensors/temp-042/status
factory/building-a/actuators/fan-01/commands
factory/building-b/sensors/humidity-003/readings

Wildcards cho phép subscribers khớp nhiều topics:

import mqtt from "mqtt";
 
const client = mqtt.connect("mqtt://localhost:1883");
 
client.on("connect", () => {
  // Single-level wildcard (+): khớp chính xác MỘT cấp
  client.subscribe("factory/building-a/sensors/+/readings");
  // Khớp: factory/building-a/sensors/temp-042/readings ✅
  // Khớp: factory/building-a/sensors/humidity-003/readings ✅
  // Không khớp: factory/building-b/sensors/temp-042/readings ❌
 
  // Multi-level wildcard (#): khớp MỌI THỨ phía dưới
  client.subscribe("factory/building-a/#");
  // Khớp: factory/building-a/sensors/temp-042/readings ✅
  // Khớp: factory/building-a/actuators/fan-01/commands ✅
  // Khớp: factory/building-a/alerts ✅
  // Không khớp: factory/building-b/anything ❌
});
 
client.on("message", (topic, message) => {
  const segments = topic.split("/");
  const building = segments[1];
  const deviceType = segments[2];
  const deviceId = segments[3];
 
  console.log(`[${building}/${deviceId}] ${message.toString()}`);
});

Quy tắc thiết kế topic tốt:

Quy TắcTốtXấu
Dùng phân cấpfactory/zone-1/temp/s01factory_zone1_temp_s01
Không bắt đầu bằng /factory/sensors/.../factory/sensors/...
Giữ các cấp có ý nghĩa{org}/{location}/{type}/{id}a/b/c/d
Tránh dấu cáchbuilding-abuilding a
Dùng chữ thườngfactory/zone-1Factory/Zone-1

Các Mức QoS của MQTT

QoS (Quality of Service) kiểm soát đảm bảo giao hàng. Đây là tính năng sát thủ của MQTT — không có giao thức nhẹ nào khác cung cấp sự linh hoạt này.

Mức QoSĐảm BảoMessagesOverheadTrường Hợp Sử Dụng
QoS 0Tối đa một lần (fire-and-forget)1Thấp nhấtGPS tần suất cao, nhiệt độ môi trường
QoS 1Ít nhất một lần (có thể trùng lặp)2Trung bìnhBản đọc cảm biến, cập nhật trạng thái
QoS 2Chính xác một lần (không mất, không trùng)4Cao nhấtSự kiện thanh toán, lệnh actuator
import mqtt from "mqtt";
 
const client = mqtt.connect("mqtt://localhost:1883", {
  clientId: "sensor-node-042",
  clean: false,         // Session persistent — broker nhớ subscriptions
  reconnectPeriod: 5000,
});
 
client.on("connect", () => {
  // QoS 0: Tọa độ GPS mỗi giây — mất một cái cũng được
  setInterval(() => {
    client.publish("fleet/truck-07/gps", JSON.stringify({
      lat: 37.7749 + Math.random() * 0.01,
      lng: -122.4194 + Math.random() * 0.01,
      speed: 55 + Math.random() * 10,
      ts: Date.now(),
    }), { qos: 0 });
  }, 1000);
 
  // QoS 1: Đọc nhiệt độ mỗi 30s — chúng ta muốn tất cả
  setInterval(() => {
    client.publish("factory/zone-1/temp/s042", JSON.stringify({
      value: 22.5 + Math.random() * 5,
      unit: "celsius",
      ts: Date.now(),
    }), { qos: 1 });
  }, 30000);
 
  // QoS 2: Lệnh actuator — phải thực thi chính xác một lần
  client.publish("factory/zone-1/actuators/valve-01/cmd", JSON.stringify({
    action: "open",
    percentage: 75,
    requestId: crypto.randomUUID(),
  }), { qos: 2 });
});

Retained Messages và Last Will

Hai tính năng MQTT giải quyết các vấn đề IoT phổ biến:

Retained Messages: Broker lưu trữ message cuối cùng trên một topic. Subscribers mới ngay lập tức nhận được nó mà không cần chờ đợi.

// Thiết bị publish trạng thái dưới dạng retained message
client.publish("devices/sensor-042/status", JSON.stringify({
  state: "online",
  firmware: "2.1.0",
  ip: "192.168.1.42",
  uptime: 86400,
}), {
  qos: 1,
  retain: true, // Broker lưu trữ — subscribers mới nhận ngay lập tức
});
 
// Dashboard kết nối 5 phút sau ngay lập tức nhận được trạng thái
// Không cần chờ bản cập nhật trạng thái tiếp theo

Last Will and Testament (LWT): Một message mà broker publish thay mặt thiết bị khi nó ngắt kết nối bất ngờ.

const client = mqtt.connect("mqtt://localhost:1883", {
  clientId: "sensor-042",
  // Last Will: được broker publish nếu client này ngắt kết nối đột ngột
  will: {
    topic: "devices/sensor-042/status",
    payload: JSON.stringify({
      state: "offline",
      lastSeen: Date.now(),
    }),
    qos: 1,
    retain: true, // Ghi đè retained message "online"
  },
});
 
// Khi kết nối, publish trạng thái "online"
client.on("connect", () => {
  client.publish("devices/sensor-042/status", JSON.stringify({
    state: "online",
    lastSeen: Date.now(),
  }), { qos: 1, retain: true });
});
 
// Nếu thiết bị crash hoặc mất mạng:
// 1. TCP keepalive timeout (mặc định 60s)
// 2. Broker publish LWT message
// 3. Dashboard thấy trạng thái "offline"
// 4. Alert service kích hoạt thông báo

MQTT v3.1.1 vs v5.0

MQTT v5.0 (phát hành 2019) bổ sung các tính năng quan trọng:

Tính NăngMQTT 3.1.1MQTT 5.0
Reason codesChỉ thành công/thất bạiMã lỗi chi tiết (quota exceeded, topic invalid, v.v.)
Shared subscriptionsKhông hỗ trợ$share/group/topic — cân bằng tải giữa subscribers
Message expiryKhông hết hạnTTL theo message (ví dụ: alert có hiệu lực 5 phút)
Topic aliasesGửi full topic mỗi lầnGán alias số nguyên ngắn cho topic dài
Request/ResponseWorkaround với hai topicsBuilt-in correlation data và response topic
User propertiesKhông hỗ trợMetadata key-value trên messages (như HTTP headers)
Flow controlBroker kiểm soátClient có thể giới hạn in-flight messages
// Các tính năng MQTT v5.0 trong thực tế
const client = mqtt.connect("mqtt://localhost:1883", {
  protocolVersion: 5,
  clientId: "sensor-042",
});
 
client.on("connect", () => {
  // Message expiry: alert hết hạn sau 5 phút
  client.publish("alerts/high-temp", JSON.stringify({
    device: "sensor-042",
    temp: 45.2,
  }), {
    qos: 1,
    properties: {
      messageExpiryInterval: 300, // 5 phút tính bằng giây
    },
  });
 
  // Request/Response: hỏi thiết bị về config
  client.publish("devices/sensor-042/config/get", "", {
    qos: 1,
    properties: {
      responseTopic: "responses/sensor-042/config",
      correlationData: Buffer.from("req-001"),
    },
  });
 
  // Shared subscription: cân bằng tải giữa 3 processing workers
  client.subscribe("$share/processors/telemetry/#");
  // Ba workers subscribe cùng shared group
  // Mỗi message chỉ đi đến MỘT worker — cân bằng tải tự động
});

CoAP — REST cho Thiết Bị Hạn Chế

CoAP (Constrained Application Protocol, RFC 7252) mang mô hình REST quen thuộc đến các thiết bị quá hạn chế cho HTTP. Nó chạy trên UDP thay vì TCP, lý tưởng cho cảm biến chạy pin.

Cách CoAP Hoạt Động

CoAP ánh xạ trực tiếp các khái niệm HTTP nhưng với overhead tối thiểu:

HTTPCoAPKhác Biệt
GET /temperatureGET /temperatureCùng ngữ nghĩa
TCP + TLS (handshake)UDP + DTLS (nhẹ hơn)Không cần thiết lập kết nối
~200+ byte headers4-byte base headerNhỏ hơn 50 lần
Content-Type: application/jsonOption 12 = 50Options dạng số
Không có built-in observeOBSERVE optionServer push thay đổi

Các Loại Message CoAP

CoAP có bốn loại message cho phép bạn kiểm soát chi tiết độ tin cậy:

LoạiTênHành ViTrường Hợp Sử Dụng
CONConfirmableTruyền lại cho đến khi nhận ACKLệnh, đọc quan trọng
NONNon-confirmableFire-and-forget, không ACKTelemetry định kỳ
ACKAcknowledgmentPhản hồi cho CONXác nhận đã nhận
RSTResetTừ chối messageXử lý lỗi

CoAP với Node.js

import { createServer, request } from "coap";
 
// ── CoAP Server (chạy trên cảm biến/thiết bị) ──
const server = createServer();
 
// Resource nhiệt độ
let currentTemp = 23.5;
 
server.on("request", (req, res) => {
  const path = req.url;
 
  if (path === "/temperature" && req.method === "GET") {
    res.setOption("Content-Format", "application/json");
    res.end(JSON.stringify({
      value: currentTemp,
      unit: "celsius",
      ts: Date.now(),
    }));
  }
 
  if (path === "/led" && req.method === "PUT") {
    const payload = JSON.parse(req.payload.toString());
    console.log(`LED đặt thành: ${payload.state}`);
    res.code = "2.04"; // Changed
    res.end();
  }
 
  if (path === "/temperature" && req.method === "GET" && req.headers.Observe === 0) {
    // Observe: push cập nhật khi giá trị thay đổi
    res.setOption("Content-Format", "application/json");
 
    const interval = setInterval(() => {
      currentTemp += (Math.random() - 0.5) * 0.5;
      res.write(JSON.stringify({
        value: parseFloat(currentTemp.toFixed(1)),
        ts: Date.now(),
      }));
    }, 5000);
 
    res.on("finish", () => clearInterval(interval));
  }
});
 
server.listen(5683, () => {
  console.log("CoAP server đang lắng nghe trên port 5683");
});
// ── CoAP Client (chạy trên gateway hoặc cloud) ──
import { request } from "coap";
 
// GET request đơn giản
const req = request({
  hostname: "sensor-042.local",
  port: 5683,
  pathname: "/temperature",
  method: "GET",
  confirmable: true, // CON message — tin cậy
});
 
req.on("response", (res) => {
  const data = JSON.parse(res.payload.toString());
  console.log(`Nhiệt độ: ${data.value}°C`);
});
 
req.end();
 
// PUT command để điều khiển actuator
const cmdReq = request({
  hostname: "sensor-042.local",
  port: 5683,
  pathname: "/led",
  method: "PUT",
  confirmable: true,
});
 
cmdReq.write(JSON.stringify({ state: "on", brightness: 80 }));
cmdReq.end();
 
// Observe: subscribe thay đổi nhiệt độ
const obsReq = request({
  hostname: "sensor-042.local",
  port: 5683,
  pathname: "/temperature",
  method: "GET",
  observe: true,
});
 
obsReq.on("response", (res) => {
  res.on("data", (chunk: Buffer) => {
    const data = JSON.parse(chunk.toString());
    console.log(`[Observe] Nhiệt độ: ${data.value}°C`);
  });
});
 
obsReq.end();

Block-Wise Transfer

CoAP messages bị giới hạn bởi UDP MTU (~1280 bytes cho 6LoWPAN). Với payload lớn hơn như firmware updates, CoAP sử dụng block-wise transfer — tự động chia dữ liệu thành các blocks:

// Block-wise transfer cho payload lớn (ví dụ: firmware metadata)
// Thư viện CoAP xử lý block negotiation tự động
 
const req = request({
  hostname: "sensor-042.local",
  pathname: "/firmware/info",
  method: "GET",
  // Block2 option: yêu cầu blocks 256-byte
});
 
req.on("response", (res) => {
  // Thư viện tự động ghép lại các blocks
  const firmwareInfo = JSON.parse(res.payload.toString());
  console.log(`Firmware: ${firmwareInfo.version}, Kích thước: ${firmwareInfo.size}`);
});
 
req.end();

Khi Nào Dùng CoAP vs MQTT

Tiêu ChíCoAPMQTT
TransportUDP (connectionless)TCP (persistent connection)
NAT traversalKhó (UDP)Dễ (TCP long-lived)
Device-to-deviceTự nhiên (địa chỉ trực tiếp)Qua broker
Resource discoveryBuilt-in (/.well-known/core)Không built-in
Observe/SubscribeObserve theo resourceSubscribe theo topic
Tuổi thọ pinTốt hơn (không TCP keepalive)Tốt với keepalive dài
MulticastHỗ trợ (UDP multicast)Không hỗ trợ

Quy tắc chung: Dùng CoAP khi thiết bị giao tiếp trực tiếp (device-to-device hoặc device-to-gateway trong mạng nội bộ). Dùng MQTT khi thiết bị cần đến cloud qua NATs và firewalls.


AMQP — Enterprise IoT Messaging

AMQP (Advanced Message Queuing Protocol) nặng hơn MQTT nhưng cung cấp các tính năng mà enterprise IoT backends cần: message queues, routing, transactions, và guaranteed ordering.

Cách AMQP Hoạt Động

AMQP giới thiệu exchanges và queues giữa publishers và consumers:

Các khái niệm AMQP chính:

  • Exchange: Nhận messages và định tuyến chúng đến queues dựa trên quy tắc
  • Queue: Lưu trữ messages cho đến khi consumer xử lý
  • Binding: Quy tắc kết nối exchanges với queues (như topic filters)
  • Consumer: Xử lý messages từ queue, gửi acknowledgment

Tại Sao AMQP cho IoT?

AMQP lấp đầy khoảng trống mà MQTT không bao phủ:

Tính NăngMQTTAMQP
Message persistenceTùy thuộc brokerBuilt-in durable queues
Dead letter queuesKhông built-inHỗ trợ native
Message routingChỉ topic-basedTopic, direct, fanout, headers
Consumer acknowledgmentQoS 1/2Manual ACK với requeue
Message priorityKhông hỗ trợ0-255 mức ưu tiên
Transaction supportKhông hỗ trợMulti-message transactions
Flow controlHạn chếPrefetch theo consumer

Thiết Lập RabbitMQ cho IoT

# docker-compose.yml
services:
  rabbitmq:
    image: rabbitmq:3-management
    container_name: iot-rabbitmq
    ports:
      - "5672:5672"     # AMQP
      - "15672:15672"   # Management UI
      - "1883:1883"     # MQTT plugin (tùy chọn)
    environment:
      RABBITMQ_DEFAULT_USER: iot_admin
      RABBITMQ_DEFAULT_PASS: secure_password
    volumes:
      - ./rabbitmq/enabled_plugins:/etc/rabbitmq/enabled_plugins
      - rabbitmq_data:/var/lib/rabbitmq
    restart: unless-stopped
 
volumes:
  rabbitmq_data:
# rabbitmq/enabled_plugins
[rabbitmq_management, rabbitmq_mqtt, rabbitmq_web_stomp].

AMQP với Node.js (amqplib)

import amqplib from "amqplib";
 
// ── Producer: IoT Gateway gửi telemetry ──
async function publishTelemetry() {
  const connection = await amqplib.connect("amqp://iot_admin:secure_password@localhost");
  const channel = await connection.createChannel();
 
  // Khai báo topic exchange cho dữ liệu IoT
  const exchange = "iot.telemetry";
  await channel.assertExchange(exchange, "topic", { durable: true });
 
  // Publish dữ liệu cảm biến với routing key
  const routingKey = "factory.zone1.temperature";
  const message = {
    deviceId: "sensor-042",
    value: 28.5,
    unit: "celsius",
    timestamp: new Date().toISOString(),
  };
 
  channel.publish(
    exchange,
    routingKey,
    Buffer.from(JSON.stringify(message)),
    {
      persistent: true,        // Sống sót qua broker restart
      contentType: "application/json",
      messageId: crypto.randomUUID(),
      timestamp: Date.now(),
      headers: {
        "x-device-type": "temperature-sensor",
        "x-firmware": "2.1.0",
      },
    }
  );
 
  console.log(`Đã publish: ${routingKey} → ${message.value}°C`);
}
// ── Consumer: Dịch vụ xử lý nhiệt độ ──
async function consumeTemperature() {
  const connection = await amqplib.connect("amqp://iot_admin:secure_password@localhost");
  const channel = await connection.createChannel();
 
  const exchange = "iot.telemetry";
  const queue = "temperature-processor";
 
  await channel.assertExchange(exchange, "topic", { durable: true });
  await channel.assertQueue(queue, {
    durable: true,
    deadLetterExchange: "iot.dlx", // Messages thất bại đến đây
    messageTtl: 3600000,           // TTL 1 giờ
  });
 
  // Bind: chỉ nhận messages nhiệt độ từ bất kỳ zone nào
  await channel.bindQueue(queue, exchange, "factory.*.temperature");
 
  // Prefetch: xử lý 10 messages cùng lúc (flow control)
  await channel.prefetch(10);
 
  channel.consume(queue, (msg) => {
    if (!msg) return;
 
    try {
      const data = JSON.parse(msg.content.toString());
      console.log(`Đang xử lý: ${data.deviceId} → ${data.value}°C`);
 
      // Kiểm tra cảnh báo
      if (data.value > 35) {
        // Publish alert đến routing key khác
        channel.publish(
          exchange,
          "factory.alerts.temperature",
          Buffer.from(JSON.stringify({
            type: "highTemperature",
            device: data.deviceId,
            value: data.value,
            threshold: 35,
            timestamp: new Date().toISOString(),
          })),
          { persistent: true }
        );
      }
 
      channel.ack(msg); // Xác nhận xử lý thành công
    } catch (error) {
      // Từ chối và không requeue — gửi đến dead letter queue
      channel.nack(msg, false, false);
    }
  });
 
  console.log("Temperature processor đang lắng nghe...");
}

Dead Letter Queues cho IoT

Messages thất bại không nên biến mất. AMQP dead letter queues bắt các messages mà:

  • Bị consumer từ chối (nack không requeue)
  • Vượt quá TTL (dữ liệu cảm biến cũ)
  • Tràn queue (back-pressure)
async function setupDeadLetterQueue() {
  const connection = await amqplib.connect("amqp://localhost");
  const channel = await connection.createChannel();
 
  // Dead letter exchange
  await channel.assertExchange("iot.dlx", "topic", { durable: true });
  await channel.assertQueue("iot.failed-messages", { durable: true });
  await channel.bindQueue("iot.failed-messages", "iot.dlx", "#");
 
  // Queue chính với dead letter routing
  await channel.assertQueue("temperature-processor", {
    durable: true,
    deadLetterExchange: "iot.dlx",
    deadLetterRoutingKey: "failed.temperature",
    messageTtl: 3600000, // Messages cũ hơn 1 giờ → DLQ
    maxLength: 100000,   // Queue tràn → DLQ
  });
 
  // Giám sát dead letter queue
  channel.consume("iot.failed-messages", (msg) => {
    if (!msg) return;
    const data = JSON.parse(msg.content.toString());
    const reason = msg.properties.headers?.["x-death"]?.[0]?.reason;
    console.error(`Dead letter [${reason}]: ${JSON.stringify(data)}`);
    // Ghi vào hệ thống giám sát, kích hoạt điều tra
    channel.ack(msg);
  });
}

WebSocket — Dashboard IoT Thời Gian Thực

WebSocket cung cấp giao tiếp full-duplex qua một kết nối TCP duy nhất. Đối với IoT, đây là cầu nối giữa backend và dashboard trên trình duyệt.

WebSocket vs MQTT-over-WebSocket

Bạn có hai lựa chọn cho IoT thời gian thực trên trình duyệt:

Cách Tiếp CậnCách Hoạt ĐộngƯu ĐiểmNhược Điểm
WebSocketWS server tùy chỉnh push dữ liệuToàn quyền kiểm soát, đơn giảnPhải tự xây pub/sub
MQTT-over-WSTrình duyệt kết nối trực tiếp MQTT broker qua WSTận dụng MQTT topics hiện cóTrình duyệt nhận toàn bộ độ phức tạp MQTT

Lựa Chọn 1: WebSocket Server Tùy Chỉnh

import { WebSocketServer, WebSocket } from "ws";
import mqtt from "mqtt";
 
// Bridge MQTT telemetry đến WebSocket clients (dashboards)
const wss = new WebSocketServer({ port: 8080 });
const mqttClient = mqtt.connect("mqtt://localhost:1883");
 
// Theo dõi topics mà mỗi WebSocket client đang xem
const clientSubscriptions = new Map<WebSocket, Set<string>>();
 
mqttClient.on("connect", () => {
  mqttClient.subscribe("factory/#");
  console.log("MQTT bridge đã kết nối");
});
 
mqttClient.on("message", (topic, payload) => {
  const message = JSON.stringify({
    topic,
    data: JSON.parse(payload.toString()),
    receivedAt: new Date().toISOString(),
  });
 
  // Chuyển tiếp đến WebSocket clients quan tâm
  wss.clients.forEach((ws) => {
    if (ws.readyState !== WebSocket.OPEN) return;
 
    const subs = clientSubscriptions.get(ws);
    if (!subs) return;
 
    // Kiểm tra xem có subscription pattern nào khớp topic này
    for (const pattern of subs) {
      if (topicMatches(pattern, topic)) {
        ws.send(message);
        break;
      }
    }
  });
});
 
wss.on("connection", (ws) => {
  clientSubscriptions.set(ws, new Set());
 
  ws.on("message", (raw) => {
    const msg = JSON.parse(raw.toString());
 
    if (msg.type === "subscribe") {
      clientSubscriptions.get(ws)?.add(msg.topic);
      console.log(`Client đã subscribe: ${msg.topic}`);
    }
 
    if (msg.type === "unsubscribe") {
      clientSubscriptions.get(ws)?.delete(msg.topic);
    }
  });
 
  ws.on("close", () => {
    clientSubscriptions.delete(ws);
  });
});
 
// Khớp topic kiểu MQTT đơn giản
function topicMatches(pattern: string, topic: string): boolean {
  const patternParts = pattern.split("/");
  const topicParts = topic.split("/");
 
  for (let i = 0; i < patternParts.length; i++) {
    if (patternParts[i] === "#") return true;
    if (patternParts[i] === "+") continue;
    if (patternParts[i] !== topicParts[i]) return false;
  }
 
  return patternParts.length === topicParts.length;
}

Lựa Chọn 2: MQTT-over-WebSocket (Browser Client)

Hầu hết MQTT brokers hỗ trợ WebSocket transport. Trình duyệt kết nối trực tiếp đến MQTT broker:

// Phía trình duyệt: kết nối đến MQTT broker qua WebSocket
import mqtt from "mqtt";
 
// Kết nối đến Mosquitto's WebSocket port (9001)
const client = mqtt.connect("ws://localhost:9001");
 
client.on("connect", () => {
  console.log("Đã kết nối MQTT broker qua WebSocket");
 
  // Subscribe dữ liệu nhiệt độ cho zone cụ thể
  client.subscribe("factory/zone-1/sensors/+/readings");
});
 
client.on("message", (topic, message) => {
  const data = JSON.parse(message.toString());
  const deviceId = topic.split("/")[3];
 
  // Cập nhật UI dashboard
  updateChart(deviceId, data.value, data.timestamp);
  updateGauge(deviceId, data.value);
 
  // Kiểm tra cảnh báo
  if (data.value > 35) {
    showAlert(`${deviceId}: ${data.value}°C vượt ngưỡng!`);
  }
});
 
function updateChart(deviceId: string, value: number, timestamp: number) {
  // Push data point đến chart library (Chart.js, D3, v.v.)
  console.log(`Cập nhật biểu đồ: ${deviceId} = ${value}°C lúc ${new Date(timestamp)}`);
}
 
function updateGauge(deviceId: string, value: number) {
  console.log(`Cập nhật gauge: ${deviceId} = ${value}°C`);
}
 
function showAlert(message: string) {
  console.warn(`CẢNH BÁO: ${message}`);
}

Protocol Bridging

Hệ thống IoT thực tế sử dụng nhiều giao thức. Một protocol bridge dịch giữa chúng:

Xây Dựng Gateway Đa Giao Thức

import mqtt from "mqtt";
import { createServer as createCoapServer } from "coap";
import amqplib from "amqplib";
 
// ── Gateway IoT đa giao thức ──
class ProtocolBridge {
  private mqttClient: mqtt.MqttClient;
  private amqpChannel: amqplib.Channel | null = null;
 
  constructor() {
    // Outbound: MQTT cho real-time telemetry
    this.mqttClient = mqtt.connect("mqtt://broker.example.com", {
      clientId: "gateway-bridge-01",
    });
  }
 
  async initialize() {
    // Outbound: AMQP cho xử lý tin cậy
    const amqpConn = await amqplib.connect("amqp://localhost");
    this.amqpChannel = await amqpConn.createChannel();
    await this.amqpChannel.assertExchange("iot.ingestion", "topic", {
      durable: true,
    });
 
    // Inbound: CoAP server cho dữ liệu cảm biến nội bộ
    this.startCoapListener();
 
    console.log("Protocol bridge đã khởi tạo");
  }
 
  private startCoapListener() {
    const server = createCoapServer();
 
    server.on("request", (req, res) => {
      if (req.method !== "POST") {
        res.code = "4.05"; // Method Not Allowed
        res.end();
        return;
      }
 
      const data = JSON.parse(req.payload.toString());
      const deviceId = req.url.split("/")[1]; // /sensor-042/telemetry
 
      // Bridge CoAP → MQTT (thời gian thực)
      this.mqttClient.publish(
        `devices/${deviceId}/telemetry`,
        JSON.stringify(data),
        { qos: 1 }
      );
 
      // Bridge CoAP → AMQP (xử lý tin cậy)
      this.amqpChannel?.publish(
        "iot.ingestion",
        `telemetry.${deviceId}`,
        Buffer.from(JSON.stringify({
          ...data,
          gatewayId: "gateway-01",
          bridgedFrom: "coap",
          receivedAt: new Date().toISOString(),
        })),
        { persistent: true }
      );
 
      res.code = "2.01"; // Created
      res.end();
    });
 
    server.listen(5683, () => {
      console.log("CoAP listener trên port 5683");
    });
  }
}
 
const bridge = new ProtocolBridge();
bridge.initialize();

MQTT-to-HTTP Webhook Bridge

Một pattern phổ biến: chuyển tiếp MQTT alerts đến HTTP endpoints (Slack, PagerDuty, custom APIs):

import mqtt from "mqtt";
 
// Bridge MQTT alerts đến HTTP webhooks
const client = mqtt.connect("mqtt://localhost:1883");
 
const webhooks: Record<string, string> = {
  "alerts/temperature": "https://hooks.slack.com/services/xxx",
  "alerts/device-offline": "https://api.pagerduty.com/events",
  "alerts/battery-low": "https://your-api.com/webhooks/battery",
};
 
client.on("connect", () => {
  // Subscribe tất cả alert topics
  client.subscribe("alerts/#");
});
 
client.on("message", async (topic, payload) => {
  const data = JSON.parse(payload.toString());
 
  // Tìm webhook khớp
  for (const [pattern, url] of Object.entries(webhooks)) {
    if (topic.startsWith(pattern.replace("#", ""))) {
      try {
        await fetch(url, {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({
            source: "iot-bridge",
            topic,
            data,
            timestamp: new Date().toISOString(),
          }),
        });
        console.log(`Đã chuyển tiếp ${topic} → ${url}`);
      } catch (error) {
        console.error(`Webhook thất bại cho ${topic}: ${error}`);
      }
    }
  }
});

Framework Quyết Định Lựa Chọn Giao Thức

Sử dụng cây quyết định này để chọn đúng giao thức:

Ma Trận Lựa Chọn Giao Thức

Tình HuốngGiao ThứcLý Do
10.000 cảm biến gửi nhiệt độ mỗi 30sMQTT (QoS 0 hoặc 1)Overhead thấp, persistent connections, broker xử lý routing
Cảm biến đất chạy pin, mạng nội bộCoAPUDP tiết kiệm pin, không overhead kết nối, observe cho thay đổi
Nhà máy cần đảm bảo giao hàngAMQPMessage persistence, dead letter queues, transaction support
Dashboard thời gian thực hiển thị dữ liệu cảm biến trực tiếpMQTT-over-WebSocketTận dụng MQTT topics hiện có, native trên trình duyệt
Firmware OTA updates (10MB+ files)HTTP/HTTPSRange requests, CDN-friendly, resume interrupted downloads
Gửi alerts đến Slack/PagerDutyHTTP webhooksTương thích universal, tích hợp đơn giản
Device-to-device trong sensor meshCoAPĐịa chỉ trực tiếp, hỗ trợ multicast, không cần broker
Tích hợp enterprise đa hệ thốngAMQPExchanges, routing keys, message transformation
Hỗn hợp: devices + dashboard + backendMQTT + AMQP + WS bridgeMQTT cho devices, AMQP cho backend, WS bridge cho dashboards

Ví Dụ Kiến Trúc Hybrid

Hầu hết hệ thống IoT production sử dụng nhiều giao thức:


Những Sai Lầm Phổ Biến Của Người Mới

Sai Lầm 1: Dùng QoS 2 cho Mọi Thứ

// ❌ XẤU: QoS 2 cho telemetry tần suất cao
setInterval(() => {
  client.publish("sensors/gps", JSON.stringify(gpsData), {
    qos: 2, // 4 round-trips mỗi message × 1000 thiết bị × 1 msg/s = broker quá tải
  });
}, 1000);
// ✅ TỐT: Khớp QoS với tầm quan trọng của dữ liệu
// GPS mỗi giây: QoS 0 (mất một cái cũng được)
client.publish("sensors/gps", JSON.stringify(gpsData), { qos: 0 });
 
// Nhiệt độ mỗi 30s: QoS 1 (chúng ta muốn tất cả bản đọc)
client.publish("sensors/temp", JSON.stringify(tempData), { qos: 1 });
 
// Lệnh actuator: QoS 2 (phải thực thi chính xác một lần)
client.publish("actuators/valve/cmd", JSON.stringify(cmd), { qos: 2 });

Sai Lầm 2: Không Có Quy Ước Đặt Tên Topic

// ❌ XẤU: Tên topic không nhất quán, phẳng
client.publish("temp_sensor_42", data);
client.publish("TemperatureSensor/42", data);
client.publish("sensor-temperature-42", data);
// Không thể dùng wildcards hiệu quả, không có phân cấp
// ✅ TỐT: Topics phân cấp nhất quán
client.publish("acme/factory-1/sensors/temp-042/readings", data);
client.publish("acme/factory-1/sensors/humidity-003/readings", data);
client.publish("acme/factory-1/actuators/fan-01/commands", cmd);
// Dễ subscribe: "acme/factory-1/sensors/+/readings"
// Dễ giám sát: "acme/factory-1/#"

Sai Lầm 3: Dùng MQTT Khi CoAP Tốt Hơn

// ❌ XẤU: MQTT cho thiết bị chạy pin device-to-gateway trên mạng nội bộ
// MQTT yêu cầu kết nối TCP (3-way handshake + keepalive packets)
// Trên pin đồng xu, TCP keepalives tốn pin ngay cả khi idle
const client = mqtt.connect("mqtt://gateway.local");
// Keepalive mỗi 60s = ~1440 TCP packets/ngày dù không có dữ liệu
// ✅ TỐT: CoAP cho thiết bị nội bộ, chạy pin
// UDP: gửi một packet, không overhead kết nối, không keepalives
const req = request({
  hostname: "gateway.local",
  port: 5683,
  pathname: "/telemetry",
  method: "POST",
  confirmable: false, // NON: fire-and-forget (ít overhead hơn nữa)
});
req.write(JSON.stringify(sensorData));
req.end();
// 0 packets giữa các lần đọc — pin sống lâu hơn hàng tháng

Sai Lầm 4: Không Dùng Retained Messages cho Trạng Thái

// ❌ XẤU: Dashboard hiển thị "unknown" cho đến bản cập nhật trạng thái tiếp theo
client.publish("devices/sensor-042/status", JSON.stringify({
  state: "online",
}));
// Subscriber mới không thấy gì cho đến lần publish tiếp theo
// ✅ TỐT: Retained message — subscribers mới nhận trạng thái ngay lập tức
client.publish("devices/sensor-042/status", JSON.stringify({
  state: "online",
  firmware: "2.1.0",
  uptime: 86400,
}), { retain: true });
// Dashboard kết nối 5 phút sau ngay lập tức hiển thị "online"

Tóm Tắt và Bài Học Chính

✅ MQTT là giao thức IoT mặc định — pub/sub nhẹ với các mức QoS cho đảm bảo giao hàng linh hoạt
✅ Khớp QoS với tầm quan trọng dữ liệu — QoS 0 cho telemetry, QoS 1 cho readings, QoS 2 cho commands
✅ CoAP lý tưởng cho thiết bị hạn chế — UDP transport, REST semantics, observe pattern, tiết kiệm pin
✅ AMQP xử lý yêu cầu enterprise — durable queues, dead letter routing, transactions, priority
✅ WebSocket kết nối IoT với trình duyệt — dùng MQTT-over-WS hoặc custom bridge cho live dashboards
✅ Thiết kế topic hierarchies cẩn thận — đặt tên nhất quán cho phép wildcard subscriptions mạnh mẽ
✅ Dùng retained messages và LWT — giải quyết vấn đề "trạng thái hiện tại là gì?" một cách thanh lịch
✅ Bridge giao thức khi cần — hầu hết hệ thống production dùng 2-3 giao thức với tầng dịch thuật
✅ MQTT v5.0 thêm shared subscriptions — cân bằng tải native giữa consumers không cần logic ứng dụng


Tiếp Theo

Bây giờ bạn đã hiểu các giao thức, đã đến lúc khám phá nơi xử lý diễn ra — tại edge, trong fog, hay trên cloud.

Tiếp: IOT-4: Edge Computing & Fog Architecture — Tại sao edge computing quan trọng cho IoT, so sánh edge vs fog vs cloud, edge runtime options (AWS Greengrass, Azure IoT Edge, K3s), và offline-first design patterns.


Bài Viết Liên Quan


Đây là Phần 3 của series IoT Patterns & Strategies. Bắt đầu từ đầu hoặc nhảy đến bất kỳ chủ đề nào bạn quan tâm.

📬 Đăng ký nhận bản tin

Nhận những bài viết mới nhất qua email mỗi tuần. Không spam, hủy đăng ký bất cứ lúc nào.

Chúng tôi tôn trọng quyền riêng tư của bạn. Hủy đăng ký bất cứ lúc nào.

💬 Comments

Đăng nhập để bình luận

Chúng tôi sẽ không đăng bất cứ điều gì mà không có sự cho phép của bạn.