1.websocket简介
微信小程序原生websoceket接口,使用方便,调试简单,开发ws小程序非常方便。
但作者之前基于mine和netty开发很多年,对『基于消息的socket开发模式』情有独钟。故,在项目中简单封装了小程序原生websocket接口,使代码组织结构以消息为主线,围绕消息和消息的处理逻辑展开。可使代码思路清晰,解耦逻辑,方便维护。
// 代码逻辑组织结构,围绕消息展开,整个项目解耦清晰,扩展性好
Client.addMessageHandler('join_room',function(message){
// doSomething
})
Client.addMessageHandler('game_start',function(message){
// doSomething
})
2.原生微信小程序websocket用法
wx.connectSocket 连接服务器
wx.onSocketOpen 连接成功后回掉
wx.onSocketMessage 接收消息
wx.sendSocketMessage 发送消息
3.先封装一个简易通信协议
3.1简易协议v1.0
发送和服务器返回消息,包含cmd和body两个字段;
其中cmd字段表示消息要完成的操作类型,body字段包含本次操作的业务数据;
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, // 服务器是否连接
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('服务器错误' + res.errMsg);
})
// ON_CLOSE ==================================
Client.addEventHandler('onClose', function (res) {
that.connectError();
console.log('onClose');
})
// 以下全部是基于消息的开发,收到不同消息,做不同的处理
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结束');
});
// 客户端发送加入房间消息,服务器端回复
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: '加入房间成功',
})
}*/
});
// 服务器下发的双方全部就绪的消息,收到这个消息后,控制界面开始游戏
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 ==================================
// 以上消息回掉全部安装完毕了,连接服务器
Client.connect('wss://mini001.xxx.com/ws/server');
},
// 点击重新开始按钮,给服务器发送一个 要求重新开始的请求
// 服务器会广播到房间内其他玩家
onRestart: function () {
var cmd = Client.cmdRestartRequest();
Client.sendMsg(cmd);
}
},
// 收到对方玩家 再来一次 的消息,回应服务器
onRestartResponse: function () {
Client.sendMsg(Cmd.cmdRestartResponse());
},
// 选择答案,如果正确,提交服务器
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 生成简易协议文本
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开发
4.1核心API
4.1.1安装三种事件的回调函数,如果不关心这个事件也可以不安装
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 发起连接
Client.connect('ws://xxxx')
开发完成
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心跳,防止服务器端主动断开
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('服务器端主动断开');
}
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,
}