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ức | Transport | Mô Hình | Header Tối Thiểu | Kết Nối | Phù Hợp Nhất Cho |
|---|---|---|---|---|---|
| MQTT | TCP | Pub/Sub | 2 bytes | Persistent | IoT telemetry tổng quát |
| CoAP | UDP | Request/Response | 4 bytes | Connectionless | Cảm biến chạy pin |
| AMQP | TCP | Queues + Pub/Sub | 8 bytes | Persistent | Enterprise IoT backends |
| WebSocket | TCP | Full-duplex | 2 bytes | Persistent | Dashboard trên trình duyệt |
| HTTP | TCP | Request/Response | ~200+ bytes | Theo request | Cloud 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 runningMQTT 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/readingsWildcards 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ắc | Tốt | Xấu |
|---|---|---|
| Dùng phân cấp | factory/zone-1/temp/s01 | factory_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ách | building-a | building a |
| Dùng chữ thường | factory/zone-1 | Factory/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ảo | Messages | Overhead | Trường Hợp Sử Dụng |
|---|---|---|---|---|
| QoS 0 | Tối đa một lần (fire-and-forget) | 1 | Thấp nhất | GPS 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) | 2 | Trung bình | Bản đọc cảm biến, cập nhật trạng thái |
| QoS 2 | Chính xác một lần (không mất, không trùng) | 4 | Cao nhất | Sự 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 theoLast 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áoMQTT 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ăng | MQTT 3.1.1 | MQTT 5.0 |
|---|---|---|
| Reason codes | Chỉ thành công/thất bại | Mã lỗi chi tiết (quota exceeded, topic invalid, v.v.) |
| Shared subscriptions | Không hỗ trợ | $share/group/topic — cân bằng tải giữa subscribers |
| Message expiry | Không hết hạn | TTL theo message (ví dụ: alert có hiệu lực 5 phút) |
| Topic aliases | Gửi full topic mỗi lần | Gán alias số nguyên ngắn cho topic dài |
| Request/Response | Workaround với hai topics | Built-in correlation data và response topic |
| User properties | Không hỗ trợ | Metadata key-value trên messages (như HTTP headers) |
| Flow control | Broker kiểm soát | Client 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:
| HTTP | CoAP | Khác Biệt |
|---|---|---|
GET /temperature | GET /temperature | Cù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 headers | 4-byte base header | Nhỏ hơn 50 lần |
Content-Type: application/json | Option 12 = 50 | Options dạng số |
| Không có built-in observe | OBSERVE option | Server 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ại | Tên | Hành Vi | Trường Hợp Sử Dụng |
|---|---|---|---|
| CON | Confirmable | Truyền lại cho đến khi nhận ACK | Lệnh, đọc quan trọng |
| NON | Non-confirmable | Fire-and-forget, không ACK | Telemetry định kỳ |
| ACK | Acknowledgment | Phản hồi cho CON | Xác nhận đã nhận |
| RST | Reset | Từ chối message | Xử 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í | CoAP | MQTT |
|---|---|---|
| Transport | UDP (connectionless) | TCP (persistent connection) |
| NAT traversal | Khó (UDP) | Dễ (TCP long-lived) |
| Device-to-device | Tự nhiên (địa chỉ trực tiếp) | Qua broker |
| Resource discovery | Built-in (/.well-known/core) | Không built-in |
| Observe/Subscribe | Observe theo resource | Subscribe theo topic |
| Tuổi thọ pin | Tốt hơn (không TCP keepalive) | Tốt với keepalive dài |
| Multicast | Hỗ 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ăng | MQTT | AMQP |
|---|---|---|
| Message persistence | Tùy thuộc broker | Built-in durable queues |
| Dead letter queues | Không built-in | Hỗ trợ native |
| Message routing | Chỉ topic-based | Topic, direct, fanout, headers |
| Consumer acknowledgment | QoS 1/2 | Manual ACK với requeue |
| Message priority | Không hỗ trợ | 0-255 mức ưu tiên |
| Transaction support | Không hỗ trợ | Multi-message transactions |
| Flow control | Hạ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 (
nackkhô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ận | Cách Hoạt Động | Ưu Điểm | Nhược Điểm |
|---|---|---|---|
| WebSocket | WS server tùy chỉnh push dữ liệu | Toàn quyền kiểm soát, đơn giản | Phải tự xây pub/sub |
| MQTT-over-WS | Trình duyệt kết nối trực tiếp MQTT broker qua WS | Tậ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ống | Giao Thức | Lý Do |
|---|---|---|
| 10.000 cảm biến gửi nhiệt độ mỗi 30s | MQTT (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ộ | CoAP | UDP 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àng | AMQP | Message 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ếp | MQTT-over-WebSocket | Tận dụng MQTT topics hiện có, native trên trình duyệt |
| Firmware OTA updates (10MB+ files) | HTTP/HTTPS | Range requests, CDN-friendly, resume interrupted downloads |
| Gửi alerts đến Slack/PagerDuty | HTTP webhooks | Tương thích universal, tích hợp đơn giản |
| Device-to-device trong sensor mesh | CoAP | Địa chỉ trực tiếp, hỗ trợ multicast, không cần broker |
| Tích hợp enterprise đa hệ thống | AMQP | Exchanges, routing keys, message transformation |
| Hỗn hợp: devices + dashboard + backend | MQTT + AMQP + WS bridge | MQTT 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ángSai 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
- Lộ Trình IoT Patterns & Strategies — Tổng quan series 12 bài và lộ trình học tập
- Nền Tảng IoT: Kiến Trúc & Giao Thức — Nền tảng: kiến trúc phân tầng, loại thiết bị, kết nối
- Hướng Dẫn Đầy Đủ Giao Thức HTTP — Hiểu HTTP, giao thức mà IoT thường thay thế
- Giải Thích Kiến Trúc Server-Client — Nền tảng để hiểu các pattern dựa trên broker
Đâ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.