开发一个mqtt服务器
服务端开发使用nodejs
npm i aedes
npm i websocket-stream
npm i aedes-persistence-level
const aedes = require('aedes')();
const net = require('net');
const http = require('http');
const ws = require('websocket-stream');
const persistence = require('aedes-persistence-level');
const tcpServer = net.createServer(aedes.handle);
const TCP_PORT = 1883;
const httpServer = http.createServer();
const WS_PORT = 3000;
ws.createServer({ server: httpServer }, aedes.handle);
aedes.authenticate = (client, username, password, callback) => {
console.log(`认证请求 - 客户端: ${client.id}, 用户名: ${username}`);
if (username === 'admin' && password.toString() === 'password') {
callback(null, true);
} else {
const error = new Error('认证失败');
error.returnCode = 4;
callback(error, false);
}
};
aedes.authorizePublish = (client, packet, callback) => {
console.log(`发布授权 - 客户端: ${client.id}, 主题: ${packet.topic}`);
if (packet.topic.startsWith('$SYS/')) {
return callback(new Error('无权发布到系统主题'));
}
callback(null);
};
aedes.authorizeSubscribe = (client, packet, callback) => {
console.log(`订阅授权 - 客户端: ${client.id}, 主题: ${packet.topic}`);
callback(null, packet);
};
aedes.on('client', (client) => {
console.log(`客户端已连接: ${client.id}`);
});
aedes.on('clientDisconnect', (client) => {
console.log(`客户端已断开: ${client.id}`);
});
aedes.on('subscribe', (subscriptions, client) => {
console.log(`客户端 ${client.id} 订阅了主题:`, subscriptions.map(s => s.topic));
});
aedes.on('unsubscribe', (subscriptions, client) => {
console.log(`客户端 ${client.id} 取消订阅了主题:`, subscriptions);
});
aedes.on('publish', (packet, client) => {
if (client) {
console.log(`客户端 ${client.id} 发布消息到主题 ${packet.topic}: ${packet.payload.toString()}`);
}
});
tcpServer.listen(TCP_PORT, () => {
console.log(`Aedes MQTT 服务器运行在端口 ${TCP_PORT}`);
});
httpServer.listen(WS_PORT, () => {
console.log(`Aedes MQTT WebSocket 服务器运行在端口 ${WS_PORT}`);
});
const gracefulShutdown = () => {
console.log('正在关闭 MQTT 服务器...');
tcpServer.close(() => {
httpServer.close(() => {
aedes.close(() => {
console.log('MQTT 服务器已完全关闭');
process.exit(0);
});
});
});
};
process.on('SIGINT', gracefulShutdown);
process.on('SIGTERM', gracefulShutdown);
module.exports = { aedes, tcpServer, httpServer };
客户端使用
封装useMqtt
// hooks/useMqtt.ts
import { ref, computed, watch, onUnmounted, readonly, type Ref } from "vue"
import mqtt, {
type MqttClient,
type IClientOptions,
type IClientPublishOptions,
} from "mqtt"
// 连接状态类型
export type MqttConnectionStatus =
| "connecting"
| "connected"
| "reconnecting"
| "disconnected"
| "error"
// 消息类型
export interface MqttMessage {
topic: string
message: string | Buffer
timestamp: Date
}
// Hook 配置参数
export interface UseMqttOptions {
brokerUrl: string
clientId?: string
username?: string
password?: string
clean?: boolean
reconnectPeriod?: number
connectTimeout?: number
manualConnect?: boolean
}
// Hook 返回值类型
export interface UseMqttReturn {
// 状态
status: Ref<MqttConnectionStatus>
connected: Ref<boolean>
client: Ref<MqttClient | null>
// 消息
messages: Ref<MqttMessage[]>
lastMessage: Ref<MqttMessage | null>
// 方法
connect: () => void
disconnect: () => void
publish: (
topic: string,
message: string,
options?: IClientPublishOptions
) => void
subscribe: (topic: string | string[], options?: any) => void
unsubscribe: (topic: string | string[]) => void
// 错误
error: Ref<Error | null>
}
export const useMqtt = (options: UseMqttOptions): UseMqttReturn => {
const {
brokerUrl,
clientId,
username,
password,
clean = true,
reconnectPeriod = 5000,
connectTimeout = 30000,
manualConnect = false,
} = options
// 响应式状态
const status = ref<MqttConnectionStatus>("disconnected")
const messages = ref<MqttMessage[]>([])
const error = ref<Error | null>(null)
const client = ref<MqttClient | null>(null)
const lastMessage = ref<MqttMessage | null>(null)
// 计算属性
const connected = computed(() => status.value === "connected")
// 连接 MQTT
const connect = (): void => {
if (client.value?.connected) return
status.value = "connecting"
error.value = null
const mqttOptions: IClientOptions = {
clientId,
clean,
reconnectPeriod,
connectTimeout,
...(username && { username }),
...(password && { password }),
}
try {
const mqttClient = mqtt.connect(brokerUrl, mqttOptions)
client.value = mqttClient
mqttClient.on("connect", () => {
status.value = "connected"
error.value = null
})
mqttClient.on("reconnect", () => {
status.value = "reconnecting"
})
mqttClient.on("message", (topic: string, message: Buffer) => {
const newMessage: MqttMessage = {
topic,
message: message.toString(),
timestamp: new Date(),
}
messages.value.push(newMessage)
lastMessage.value = newMessage
})
mqttClient.on("error", (err: Error) => {
status.value = "error"
error.value = err
})
mqttClient.on("close", () => {
status.value = "disconnected"
})
mqttClient.on("offline", () => {
status.value = "disconnected"
})
} catch (err) {
status.value = "error"
error.value = err as Error
}
}
// 断开连接
const disconnect = (): void => {
if (client.value) {
client.value.end()
client.value = null
status.value = "disconnected"
messages.value = []
lastMessage.value = null
}
}
// 发布消息
const publish = (
topic: string,
message: string,
options?: IClientPublishOptions
): void => {
if (client.value?.connected) {
client.value.publish(topic, message, options)
} else {
throw new Error("MQTT client is not connected")
}
}
// 订阅主题
const subscribe = (topic: string | string[], options?: any): void => {
if (client.value?.connected) {
client.value.subscribe(topic, options)
} else {
throw new Error("MQTT client is not connected")
}
}
// 取消订阅
const unsubscribe = (topic: string | string[]): void => {
if (client.value?.connected) {
client.value.unsubscribe(topic)
}
}
// 自动连接
if (!manualConnect) {
connect()
}
// 组件卸载时自动断开连接
onUnmounted(() => {
disconnect()
})
return {
status: readonly(status),
connected: readonly(connected),
client,
messages: readonly(messages),
lastMessage: readonly(lastMessage),
error: readonly(error),
connect,
disconnect,
publish,
subscribe,
unsubscribe,
}
}
使用mqtt
import { useMqtt } from "@/hooks/useMqtt"
const publishTopic = ref("test/topic")
const publishMessage = ref("")
const subscribeTopic = ref("test/topic")
// 基础 MQTT 连接
const mqtt = useMqtt({
brokerUrl: "ws://localhost:3000/mqtt",
clientId: `test-client-1`,
username: "admin",
password: "password",
})
//订阅主题
const handleSubscribe = () => {
if (subscribeTopic.value) {
mqtt.subscribe(subscribeTopic.value)
}
}
//发布消息
const handlePublish = () => {
if (publishTopic.value && publishMessage.value) {
mqtt.publish(publishTopic.value, publishMessage.value)
publishMessage.value = ""
}
}
//接收消息
const messages = ref([])
mqtt.client.value.on("message", (topic, message) => {
const newMessage = {
topic,
message: message.toString(),
timestamp: new Date(),
}
messages.value.push(newMessage)
})