node开发一个mqtt服务器

34 阅读2分钟

开发一个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');
​
// 创建 TCP 服务器
const tcpServer = net.createServer(aedes.handle);
const TCP_PORT = 1883;
​
// 创建 HTTP 服务器用于 WebSocket
const httpServer = http.createServer();
const WS_PORT = 3000;
​
// 设置 WebSocket
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()}`);
  }
});
​
// 启动 TCP 服务器
tcpServer.listen(TCP_PORT, () => {
  console.log(`Aedes MQTT 服务器运行在端口 ${TCP_PORT}`);
});
​
// 启动 WebSocket 服务器
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);
});