微信小程序websocket基于消息的開發(fā)模式

1.websocket簡介

微信小程序原生websoceket接口,使用方便瑰煎,調(diào)試簡單屹篓,開發(fā)ws小程序非常方便。
但作者之前基于mine和netty開發(fā)很多年撇吞,對『基于消息的socket開發(fā)模式』情有獨鐘俗冻。故,在項目中簡單封裝了小程序原生websocket接口牍颈,使代碼組織結(jié)構(gòu)以消息為主線迄薄,圍繞消息和消息的處理邏輯展開≈笏辏可使代碼思路清晰讥蔽,解耦邏輯涣易,方便維護。

// 代碼邏輯組織結(jié)構(gòu)冶伞,圍繞消息展開新症,整個項目解耦清晰,擴展性好
Client.addMessageHandler('join_room',function(message){
  // doSomething
})
Client.addMessageHandler('game_start',function(message){
  // doSomething
})

2.原生微信小程序websocket用法

wx.connectSocket 連接服務(wù)器
wx.onSocketOpen 連接成功后回掉
wx.onSocketMessage 接收消息
wx.sendSocketMessage 發(fā)送消息

3.先封裝一個簡易通信協(xié)議

3.1簡易協(xié)議v1.0

發(fā)送和服務(wù)器返回消息碰缔,包含cmd和body兩個字段账劲;
其中cmd字段表示消息要完成的操作類型,body字段包含本次操作的業(yè)務(wù)數(shù)據(jù)金抡;
cmd必須有值瀑焦,body字段可以為空

{cmd:'cmd_name',body:{}}

3.2先看一下封裝后,基于消息的websocket使用方法

const Client = require('../../util/PKClient.js');
const Cmd = require('../../util/Cmd.js');

Page({
  data: {
    server_is_connect: false, // 服務(wù)器是否連接
    join_room_ready: false, // 自己加入房間是否成功
    player_is_ready: false, // 雙方玩家是否就緒
  },

  onLoad: function (query) {
    var that = this;
    wx.showLoading({
      title: '加入PK中...',
    })
    // ON_OPEN ==================================
    Client.addEventHandler('onOpen', function (res) {
      wx.hideLoading();
      console.log('onOpen');
      that.setData({
        server_is_connect: true
      })
      Client.sendMsg(Cmd.cmdHello());
      //加入房間
      wx.showLoading({
        title: 'PK準備中...',
      })
      var cmd = Client.cmdJoinRoom(query.room_id, that.data.my_info);
      Client.sendMsg(cmd);
    })

    // ON_ERROR ==================================
    Client.addEventHandler('onError', function (res) {
      console.log('onError');
      that.connectError();
      that.errorExit('服務(wù)器錯誤' + res.errMsg);
    })
    // ON_CLOSE ==================================
    Client.addEventHandler('onClose', function (res) {
      that.connectError();
      console.log('onClose');
    })

// 以下全部是基于消息的開發(fā)梗肝,收到不同消息榛瓮,做不同的處理
    Client.addMessageHandler('hello', function (message) {
      console.log('hell back @ ' + (new Date()));
    })

    // 對方要求重新開始游戲,收到消息后巫击,界面上提示玩家
    Client.addMessageHandler('restart_request', function (message) {
      that.setData({
        is_peer_request_restart: true
      });
    })

// 收到對方玩家掉線的消息
    Client.addMessageHandler('peer_offline', function (message) {
      Client.close();
      that.errorExit('對方已掉線,PK結(jié)束');
    });
// 客戶端發(fā)送加入房間消息禀晓,服務(wù)器端回復(fù)
    Client.addMessageHandler('join_room', function (message) {
      /*
      wx.hideLoading();
      if (!message.body['result']) {
        that.errorExit('好友正在PK中,你可以邀請其他好友PK');
        Client.close();
      } else {
        that.setData({
          join_room_ready: true
        })
        wx.showToast({
          title: '加入房間成功',
        })
      }*/
    });
// 服務(wù)器下發(fā)的雙方全部就緒的消息坝锰,收到這個消息后粹懒,控制界面開始游戲
    Client.addMessageHandler('ready_confirm', function (message) {
      /*
      var quiz = message.body['quiz'];
      for (var i in quiz) {
        quiz[i]['type_name'] = Api.getGarbageName(quiz[i]['type']);
      }
      that.setData({
        is_peer_request_restart: false,
        score: {},
        game_over: false,
        time: 0,
        timeFormat: '0',
        quiz: quiz,
        quiz_id: 0,
        showResult: false,
        him_info: message.body['him_info'],
        player_is_ready: true
      });

      that.data.setInter = setInterval(
        function () {
          that.setData({
            time: that.data.time + 1,
            timeFormat: that.formatTime(that.data.time + 1)
          });
        }
        , 1000);
      */
    });
    

    Client.addMessageHandler('game_over', function (message) {
      var r = message.body['result'];
      that.gameOver(r);
    });


    // DO_CONNECT ==================================
// 以上消息回掉全部安裝完畢了,連接服務(wù)器
    Client.connect('wss://mini001.xxx.com/ws/server');
  },

// 點擊重新開始按鈕顷级,給服務(wù)器發(fā)送一個 要求重新開始的請求
// 服務(wù)器會廣播到房間內(nèi)其他玩家
  onRestart: function () {
    var cmd = Client.cmdRestartRequest();
    Client.sendMsg(cmd);
  }


},
// 收到對方玩家 再來一次 的消息凫乖,回應(yīng)服務(wù)器
  onRestartResponse: function () {
    Client.sendMsg(Cmd.cmdRestartResponse());
  },

// 選擇答案,如果正確弓颈,提交服務(wù)器
  onClickAnswer: function (e) {
    var selectType = e.currentTarget.dataset.type;
    var quiz = this.data.quiz;
    var answer = quiz[this.data.quiz_id]['type'];
    if (selectType != answer) {
      wx.showToast({
        title: '回答錯誤',
      })
    } else {
      wx.showToast({
        title: '回答正確',
      })
      Client.sendMsg(Cmd.cmdRight());
    }
  },)

// Cmd.js   生成簡易協(xié)議文本
const getCmdStr = (cmd,body) => {
  var openid = Login.getOpenId();
  return JSON.stringify({cmd:cmd,openid:openid,body:body});
}
const cmdNewRoom = (my_info) => {
    return getCmdStr('new_room',my_info);
}
const cmdJoinRoom = (room_id,my_info) => {
  return getCmdStr('join_room', { id: room_id, my_info: my_info});
}
const cmdTxt = (room_id,txt) => {
  return getCmdStr('send_txt',{txt:txt,room_id:room_id});
}
const cmdHello = (room_id, txt) => {
  return getCmdStr('hello','');
}
const cmdRight = () => {
  return getCmdStr('right', '');
}
const cmdRestartRequest = () => {
  return getCmdStr('restart_request', '');
}
const cmdRestartResponse = () => {
  return getCmdStr('restart_response', '');
}

module.exports = {
  cmdNewRoom: cmdNewRoom,
  cmdJoinRoom: cmdJoinRoom,
  cmdTxt: cmdTxt,
  cmdHello: cmdHello,
  cmdRight: cmdRight,
  cmdRestartRequest: cmdRestartRequest,
  cmdRestartResponse: cmdRestartResponse

}

4.基于消息的微信小程序websocket開發(fā)

4.1核心API

4.1.1安裝三種事件的回調(diào)函數(shù)帽芽,如果不關(guān)心這個事件也可以不安裝
Client.addEventHandler('onOpen',function(){ });
Client.addEventHandler('onError',function(){ });
Client.addEventHandler('onClose',function(){ });
4.1.2安裝消息回掉,即什么消息要怎么處理
Client.addMessageHandler('hello',function(message){})
Client.addMessageHandler('peer_offline',function(message){})
4.1.3 發(fā)起連接
Client.connect('ws://xxxx')

開發(fā)完成

5.Client.js封裝代碼

// Client.js
function json_decode(str) {
  var p = new Promise(function (resolve, reject) {
    var cc = JSON.parse(str);
    resolve(cc);
  });
  return p;
}

let is_open = false;
let socketMsgQueue = [];
let heartbeatTimer = '';

let eventHandler = {
  onOpen: false,
  onError: false,
  onClose: false
};
let messageHandler = {};

const addMessageHandler = (cmd, handler) => {
  messageHandler[cmd] = handler;
}
const addEventHandler = (event, handler) => {
  eventHandler[event] = handler;
}
const connect = (server_url) => {
  wx.connectSocket({ url: server_url })
}
const close = (reason) => {
  wx.closeSocket({ reason: reason });
}

const sendMsg = (msg) => {
  console.log('send msg:' + msg);
  if (is_open) {
    wx.sendSocketMessage({
      data: msg
    })
  } else {
    socketMsgQueue.push(msg)
  }
}

/**原生安裝 */

wx.onSocketOpen(function (res) {

  // 20s心跳翔冀,防止服務(wù)器端主動斷開
  heartbeatTimer = setInterval(function () {
    sendMsg(cmdHello());
  }, 20000);

  console.log('wx.onSocketOpen', res);
  is_open = true;
  for (let i = 0; i < socketMsgQueue.length; i++) {
    sendMsg(socketMsgQueue[i])
  }
  socketMsgQueue = []

  if (eventHandler['onOpen']) {
    eventHandler['onOpen'](res);
  }
})

wx.onSocketMessage(function (res) {
  console.log('wx.onSocketMessage', res);
  json_decode(res.data).then(function (message) {
    var cmd = message['cmd'];
    if (messageHandler[cmd]) {
      messageHandler[cmd](message);
    } else {

      console.log('cmd not handler', message);
    }
  }).catch(function (e) {
    console.log("cmd parse faild", e);
    console.log(e);
  })
});
wx.onSocketClose(function (res) {
  if (res.code == 1006) {
    clearInterval(heartbeatTimer);
    close('服務(wù)器端主動斷開');
  }
  console.log('wx.onSocketClose', res);
  if (eventHandler['onClose']) {
    eventHandler['onClose'](res);
  }
});

wx.onSocketError(function (res) {
  clearInterval(heartbeatTimer);
  console.log('wx.onSocketError', res);
  if (eventHandler['onError']) {
    eventHandler['onError'](res);
  }
})

module.exports = {
  connect: connect,
  close: close,
  sendMsg: sendMsg,

  addMessageHandler: addMessageHandler,
  addEventHandler: addEventHandler,

}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末导街,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子纤子,更是在濱河造成了極大的恐慌搬瑰,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件控硼,死亡現(xiàn)場離奇詭異跌捆,居然都是意外死亡,警方通過查閱死者的電腦和手機象颖,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門佩厚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人说订,你說我怎么就攤上這事抄瓦〔ゾ粒” “怎么了惋啃?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵中跌,是天一觀的道長须妻。 經(jīng)常有香客問我,道長煞额,這世上最難降的妖魔是什么思恐? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮膊毁,結(jié)果婚禮上胀莹,老公的妹妹穿的比我還像新娘。我一直安慰自己婚温,他們只是感情好描焰,可當我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著栅螟,像睡著了一般荆秦。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上力图,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天步绸,我揣著相機與錄音,去河邊找鬼吃媒。 笑死瓤介,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的晓折。 我是一名探鬼主播惑朦,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼兽泄,長吁一口氣:“原來是場噩夢啊……” “哼漓概!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起病梢,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤胃珍,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后蜓陌,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體觅彰,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年钮热,在試婚紗的時候發(fā)現(xiàn)自己被綠了填抬。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡隧期,死狀恐怖飒责,靈堂內(nèi)的尸體忽然破棺而出赘娄,到底是詐尸還是另有隱情,我是刑警寧澤宏蛉,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布遣臼,位于F島的核電站,受9級特大地震影響拾并,放射性物質(zhì)發(fā)生泄漏揍堰。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一嗅义、第九天 我趴在偏房一處隱蔽的房頂上張望屏歹。 院中可真熱鬧,春花似錦芥喇、人聲如沸西采。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽械馆。三九已至,卻和暖如春武通,著一層夾襖步出監(jiān)牢的瞬間霹崎,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工冶忱, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留尾菇,地道東北人。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓囚枪,卻偏偏與公主長得像派诬,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子链沼,可洞房花燭夜當晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容