后端 mqtt day2 | 青训营笔记

100 阅读2分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 2 天

今天学习的内容为直播推流项目

今天完善我的基于环信SDK的推流直播+coplay项目, 主要是coplay部分, 虽然主要是写react, 但也算是巩固学习rtc相关内容吧.

coplay的需求可以简化为如下,

  1. 客户端A开启游戏直播, 订阅通信信道
  2. 客户端B加入通信信道.
  3. 客户端B检测用户输入, 以一定频率发送给客户端A
  4. 客户端A接收输入消息, 发送给游戏作为2p信号.

环信直接没有提供RTC接口, 但我找到了类似可用的协议 MQTT.

MQTT(Message Queuing Telemetry Transport,消息队列遥测传输协议),是一种基于发布/订阅(publish/subscribe)模式的"轻量级"通讯协议,该协议构建于TCP/IP协议上,由IBM在1999年发布。MQTT最大优点在于,可以以极少的代码和有限的带宽,为连接远程设备提供实时可靠的消息服务。作为一种低开销、低带宽占用的即时通讯协议,使其在物联网、小型设备、移动应用等方面有较广泛的应用。

首先做好准备工作, 注册/开通MQTT服务, 获取 appId, host, apiUrl 等信息.

具体的, 使用sdk的流程如下

  1. getAppToken , 携带appClientId与secret 获取app级别access_token
  2. getUserToken, 用户名+cid+app token验证, 获取usertoken. 这里的cid建议是 deviceId@appID, 我这里deviceId直接使用用户名
  3. 建立client (host+port+cid), 并填入配置参数 + 验证信息
  4. 建立连接后 subscribe 你关注的 topic
  5. 在onMessage回调中处理信息

包装了一层useEffect 封装成react hook


class MQTT {
  constructor(isClient, token, uname, onMessage, clientId, topic) {
    this.client = null;
    this.host = MqttConfig.host;
    this.port = MqttConfig.port;
    this.appClientId = MqttConfig.appClientId;
    this.appClientSecret = MqttConfig.appClientSecret;
    this.appId = MqttConfig.appId;
    this.reconnTimeout = MqttConfig.reconnTimeout;
    this.cleanSession = MqttConfig.cleanSession;
    this.useSSL = MqttConfig.useSSL;
    this.apiUrl = MqttConfig.apiUrl;
    this.clientId = clientId;
    this.username = uname;
    this.password = token;
    this.topic = topic;
    this.onMessage = onMessage;
    this.isClient = isClient;
    this.queueMes = [];
    this.connecting = false;
  }

  connect = () => {
    console.log("[MQTT] conn", this.clientId);
    this.connecting = false;
    this.client = new Client(this.host, this.port, this.clientId);
    const options = {
      timeout: 3,
      onSuccess: () => {
        // console.log("[MQTT] connected, go subscribe ", this.topic);
        if (!this.client || !this.client.isConnected()) {
          // console.log("mqttClient is not connected");
          // return;
        }
        // if (!this.isClient) {
        this.client.subscribe(this.topic, { qos: 0 });
        // }
        if (this.queueMes.length > 0) {
          this.queueMes.forEach((mes) => {
            this.send(mes);
          });
          this.queueMes = [];
        }
      },
      mqttVersion: 4,
      useSSL: this.useSSL,
      cleanSession: this.cleanSession,
      onFailure: (res) => {
        if (!this.isClient && !this.connecting) {
          this.connecting = setTimeout(this.connect(), this.reconnTimeout);
          // console.log("[MQTT] connect failed", res);
        }
      },
      reconnect: true,
      keepAliveInterval: 60,
      userName: this.username,
      password: this.password,
    };
    this.client.onConnectionLost = (res) => {
      if (!this.isClient && !this.connecting) {
        // console.log("[MQTT] connection lost", res);
        this.connecting = setTimeout(this.connect(), this.reconnTimeout);
      }
    };
    this.client.onMessageArrived = (message) => {
      console.log("[MQTT] message arrived", message);
      this.onMessage(message.payloadString);
    };
    this.client.connect(options);
  };

  disconnect = () => {
    if (this.connecting) {
      clearTimeout(this.connecting);
      this.connecting = false;
    }
    if (this.client && this.client.isConnected()) {
      try {
        this.client.unsubscribe(this.topic);
        this.client.disconnect();
      } catch (e) {
        console.log("mqttClient disconnect error", e);
      }
    }
  };

  send = (data) => {
    if (this.client && this.client.isConnected()) {
      const message = new Message(data);
      message.destinationName = this.topic;
      this.client.send(message);
    } else {
      this.queueMes.push(data);
      this.connect();
    }
  };

  isConnected = () => {
    return this.client && this.client.isConnected();
  };
}

const useMqtt = (topic, uname, onMessage = () => {}, isClient = true) => {
  const { apiUrl, appClientId, appClientSecret, appId } = MqttConfig;

  const [mqttClient, setMqttClient] = useState(null);

  const clientId = `${uname}@${appId}`;
  // console.log("client id is ", clientId, topic);
  let mqtt = null;
  useEffect(() => {
    const getAppToken = async () => {
      http("post", `${apiUrl}/openapi/rm/app/token`, {
        appClientId: appClientId,
        appClientSecret: appClientSecret,
      })
        .then((res) => {
          const access_token = res.body.access_token;
          // console.log("[MQTT] access token ", res);
          getUserToken(access_token);
        })
        .catch(console.error);
    };

    const getUserToken = async (access_token) => {
      axios
        .post(
          `${apiUrl}/openapi/rm/user/token`,
          {
            username: uname,
            expires_in: 86400,
            cid: clientId,
          },
          {
            headers: {
              "Content-Type": "application/json",
              Authorization: `${access_token}`,
            },
          }
        )
        .then((res) => {
          // console.log("[MQTT] user token ", res);
          const token = res.data.body.access_token;
          // mqttConnect(token);
          mqtt = new MQTT(isClient, token, uname, onMessage, clientId, topic);
          mqtt.connect();
          setMqttClient(mqtt);
        })
        .catch(console.error);
    };
    getAppToken();
    return () => {
      if (mqtt) {
        mqtt.disconnect();
      }
    };
  }, []);
  return mqttClient;
};

export default useMqtt;

使用第三方接口最快的方式还是直接调通demo在上面改造, 自己手动魔改还是需要一步步填坑的, bug还没修完, 继续写代码

引用连接

MQTT 入门介绍 | 菜鸟教程 (runoob.com)
快速使用MQTT Web版 SDK实现消息收发