- 试了一下小程序的websocket,提供个简单的代码吧
[代码]var[代码] [代码]util = require([代码][代码]'../../../utils/util.js'[代码][代码]);[代码] [代码]var[代码] [代码]socketOpen = [代码][代码]false[代码][代码];[代码] [代码]var[代码] [代码]frameBuffer_Data, session, SocketTask;[代码] [代码]var[代码] [代码]url = [代码][代码]'wss://wss......./Chat'[代码][代码];[代码] [代码]Page({[代码] [代码] [代码][代码]/**[代码] [代码] [代码][代码]* 页面的初始数据[代码] [代码] [代码][代码]*/[代码] [代码] [代码][代码]data: {[代码] [代码] [代码][代码]toView: [代码][代码]'green'[代码][代码],[代码] [代码] [代码][代码]windowH: [代码][代码]"1000"[代码][代码],[代码] [代码] [代码][代码]user_input_text: [代码][代码]''[代码][代码], [代码][代码]//用户输入文字[代码] [代码] [代码][代码]inputValue: [代码][代码]''[代码][代码],[代码] [代码] [代码][代码]returnValue: [代码][代码]''[代码][代码],[代码] [代码] [代码][代码]addImg: [代码][代码]false[代码][代码],[代码] [代码] [代码][代码]//格式示例数据,可为空[代码] [代码] [代码][代码]allContentList: [],[代码] [代码] [代码][代码]num: 0,[代码] [代码] [代码][代码]wo: [代码][代码]""[代码][代码],[代码] [代码] [代码][代码]ta: [代码][代码]""[代码][代码],[代码] [代码] [代码][代码]youImg: [代码][代码]""[代码] [代码] [代码][代码]},[代码] [代码] [代码][代码]scrollToTop() {[代码] [代码] [代码][代码]const that = [代码][代码]this[代码][代码];[代码] [代码] [代码][代码]this[代码][代码].setAction({[代码] [代码] [代码][代码]scrollTop: that.windowH[代码] [代码] [代码][代码]})[代码] [代码] [代码][代码]},[代码] [代码] [代码][代码]bindKeyInput: [代码][代码]function[代码][代码](e) {[代码] [代码] [代码][代码]this[代码][代码].setData({[代码] [代码] [代码][代码]inputValue: e.detail.value[代码] [代码] [代码][代码]})[代码] [代码] [代码][代码]},[代码] [代码] [代码][代码]// 提交文字[代码] [代码] [代码][代码]submitTo: [代码][代码]function[代码][代码](e) {[代码] [代码] [代码][代码]let that = [代码][代码]this[代码][代码];[代码] [代码] [代码][代码]var[代码] [代码]content = that.data.inputValue;[代码] [代码] [代码][代码]var[代码] [代码]data = {[代码] [代码] [代码][代码]// body: that.data.inputValue,[代码] [代码] [代码][代码]"name"[代码][代码]: that.data.wo,[代码] [代码] [代码][代码]"content"[代码][代码]: content,[代码] [代码] [代码][代码]"type"[代码][代码]: 5,[代码] [代码] [代码][代码]"toName"[代码][代码]: that.data.ta[代码] [代码] [代码][代码]}[代码] [代码] [代码][代码]//静态显示[代码] [代码] [代码][代码]this[代码][代码].data.allContentList.push({[代码] [代码] [代码][代码]"id"[代码][代码]: [代码][代码]""[代码][代码],[代码] [代码] [代码][代码]"hx_id"[代码][代码]: [代码][代码]""[代码][代码],[代码] [代码] [代码][代码]"wo"[代码][代码]: that.data.wo,[代码] [代码] [代码][代码]"ta"[代码][代码]: that.data.ta,[代码] [代码] [代码][代码]"content"[代码][代码]: content,[代码] [代码] [代码][代码]"voice_url"[代码][代码]: [代码][代码]""[代码][代码],[代码] [代码] [代码][代码]"fileurl"[代码][代码]: [代码][代码]null[代码][代码],[代码] [代码] [代码][代码]"create_date"[代码][代码]: util.formatTime[代码] [代码] [代码][代码]});[代码] [代码] [代码][代码]this[代码][代码].setData({[代码] [代码] [代码][代码]allContentList: [代码][代码]this[代码][代码].data.allContentList,[代码] [代码] [代码][代码]inputValue: [代码][代码]''[代码] [代码] [代码][代码]})[代码] [代码] [代码][代码]//至底[代码] [代码] [代码][代码]that.bottom();[代码] [代码] [代码][代码]//socket[代码] [代码] [代码][代码]if[代码] [代码](socketOpen) {[代码] [代码] [代码][代码]// 如果打开了socket就发送数据给服务器[代码] [代码] [代码][代码]that.sendSocketMessage(data);[代码] [代码] [代码][代码]}[代码] [代码] [代码][代码]},[代码] [代码] [代码][代码]//通过 WebSocket 连接发送数据,需要先 wx.connectSocket,并在 wx.onSocketOpen 回调之后才能发送。[代码] [代码] [代码][代码]sendSocketMessage: [代码][代码]function[代码][代码](msg) {[代码] [代码] [代码][代码]var[代码] [代码]that = [代码][代码]this[代码][代码];[代码] [代码] [代码][代码]console.log([代码][代码]'通过 WebSocket 连接发送数据'[代码][代码], JSON.stringify(msg))[代码] [代码] [代码][代码]// debugger[代码] [代码] [代码][代码]SocketTask.send([代码] [代码] [代码][代码]{[代码] [代码] [代码][代码]data: JSON.stringify(msg)[代码] [代码] [代码][代码]},[代码] [代码] [代码][代码]function[代码] [代码](res){[代码] [代码] [代码][代码]console.log([代码][代码]'已发送'[代码][代码], res)[代码] [代码] [代码][代码]}[代码] [代码] [代码][代码])[代码] [代码] [代码][代码]},[代码] [代码] [代码][代码]addImg: [代码][代码]function[代码][代码](e) {[代码] [代码] [代码][代码]var[代码] [代码]a = [代码][代码]this[代码][代码].data.addImg;[代码] [代码] [代码][代码]if[代码] [代码](a == [代码][代码]true[代码][代码]) {[代码] [代码] [代码][代码]this[代码][代码].setData({[代码] [代码] [代码][代码]addImg: [代码][代码]false[代码] [代码] [代码][代码]})[代码] [代码] [代码][代码]} [代码][代码]else[代码] [代码]{[代码] [代码] [代码][代码]this[代码][代码].setData({[代码] [代码] [代码][代码]addImg: [代码][代码]true[代码] [代码] [代码][代码]})[代码] [代码] [代码][代码]}[代码] [代码] [代码][代码]},[代码] [代码] [代码][代码]tap() {[代码] [代码] [代码][代码]for[代码] [代码](let i = 0; i < order.length; ++i) {[代码] [代码] [代码][代码]if[代码] [代码](order[i] === [代码][代码]this[代码][代码].data.toView) {[代码] [代码] [代码][代码]this[代码][代码].setData({[代码] [代码] [代码][代码]toView: order[i + 1],[代码] [代码] [代码][代码]scrollTop: (i + 1) * 200[代码] [代码] [代码][代码]})[代码] [代码] [代码][代码]break[代码] [代码] [代码][代码]}[代码] [代码] [代码][代码]}[代码] [代码] [代码][代码]},[代码] [代码] [代码][代码]tapMove() {[代码] [代码] [代码][代码]this[代码][代码].setData({[代码] [代码] [代码][代码]scrollTop: [代码][代码]this[代码][代码].data.scrollTop + 10[代码] [代码] [代码][代码]})[代码] [代码] [代码][代码]},[代码] [代码] [代码][代码]/**[代码] [代码] [代码][代码]* 生命周期函数--监听页面加载[代码] [代码] [代码][代码]*/[代码] [代码] [代码][代码]onLoad: [代码][代码]function[代码][代码](options) {[代码] [代码] [代码][代码]const that = [代码][代码]this[代码][代码];[代码] [代码] [代码][代码]that.setData({[代码] [代码] [代码][代码]ta: options.tel,[代码] [代码] [代码][代码]youImg: options.youImg,[代码] [代码] [代码][代码]wo: util.phone[代码] [代码] [代码][代码]})[代码] [代码] [代码][代码]// 获取设备高度[代码] [代码] [代码][代码]wx.getSystemInfo({[代码] [代码] [代码][代码]success: [代码][代码]function[代码][代码](res) {[代码] [代码] [代码][代码]var[代码] [代码]clientHeight = res.windowHeight,[代码] [代码] [代码][代码]clientWidth = res.windowWidth,[代码] [代码] [代码][代码]rpxR = 750 / clientWidth;[代码] [代码] [代码][代码]var[代码] [代码]helfH = clientHeight * 1 * rpxR;[代码] [代码] [代码][代码]//var textH = helfH - 100;[代码] [代码] [代码][代码]that.setData({[代码] [代码] [代码][代码]windowH: helfH[代码] [代码] [代码][代码]});[代码] [代码] [代码][代码]}[代码] [代码] [代码][代码]});[代码] [代码] [代码][代码]//加载历史数据[代码] [代码] [代码][代码]// util.apiHtml +http://localhost:9193[代码] [代码] [代码] [代码] [代码][代码]//修改已读[代码] [代码] [代码] [代码] [代码] [代码] [代码][代码]},[代码] [代码] [代码][代码]/**[代码] [代码] [代码][代码]* 生命周期函数--监听页面初次渲染完成[代码] [代码] [代码][代码]*/[代码] [代码] [代码][代码]onReady: [代码][代码]function[代码][代码]() {[代码] [代码] [代码][代码]var[代码] [代码]that = [代码][代码]this[代码][代码];[代码] [代码] [代码] [代码] [代码][代码]SocketTask.onOpen(res => {[代码] [代码] [代码][代码]socketOpen = [代码][代码]true[代码][代码];[代码] [代码] [代码][代码]console.log([代码][代码]'监听 WebSocket 连接打开事件。'[代码][代码], res)[代码] [代码] [代码][代码]//发送登陆信息[代码] [代码] [代码][代码]var[代码] [代码]data = {[代码] [代码] [代码][代码]// body: that.data.inputValue,[代码] [代码] [代码][代码]"Name"[代码][代码]: that.data.wo,[代码] [代码] [代码][代码]"content"[代码][代码]: [代码][代码]"login"[代码][代码],[代码] [代码] [代码][代码]"type"[代码][代码]: 4[代码] [代码] [代码][代码]}[代码] [代码] [代码][代码]that.sendSocketMessage(data);[代码] [代码] [代码][代码]//循环发送心跳[代码] [代码] [代码][代码]setInterval([代码] [代码] [代码][代码]function[代码] [代码]() {[代码] [代码] [代码][代码]var[代码] [代码]ping = { [代码][代码]"type"[代码][代码]: [代码][代码]"ping"[代码] [代码]};[代码] [代码] [代码][代码]that.sendSocketMessage(ping);[代码] [代码] [代码][代码]}, 20000[代码] [代码] [代码][代码]); [代码] [代码] [代码][代码]})[代码] [代码] [代码][代码]SocketTask.onClose(onClose => {[代码] [代码] [代码][代码]console.log([代码][代码]'监听 WebSocket 连接关闭事件。'[代码][代码], onClose)[代码] [代码] [代码][代码]socketOpen = [代码][代码]false[代码][代码];[代码] [代码] [代码][代码]this[代码][代码].webSocket()[代码] [代码] [代码][代码]})[代码] [代码] [代码][代码]SocketTask.onError(onError => {[代码] [代码] [代码][代码]console.log([代码][代码]'监听 WebSocket 错误。错误信息'[代码][代码], onError)[代码] [代码] [代码][代码]socketOpen = [代码][代码]false[代码] [代码] [代码][代码]})[代码] [代码] [代码][代码]SocketTask.onMessage(onMessage => {[代码] [代码] [代码][代码]console.log([代码][代码]"onMessage:::::"[代码][代码]+onMessage.data);[代码] [代码] [代码][代码]if[代码] [代码](onMessage.data.indexOf([代码][代码]"上线"[代码][代码]) != -1 || onMessage.data.indexOf([代码][代码]"下线"[代码][代码]) != -1) {[代码] [代码] [代码][代码]return[代码][代码];[代码] [代码] [代码][代码]}[代码] [代码] [代码][代码]console.log([代码][代码]'监听WebSocket接受到服务器的消息事件。服务器返回的消息'[代码][代码], JSON.parse(onMessage.data))[代码] [代码] [代码][代码]var[代码] [代码]onMessage_data = JSON.parse(onMessage.data)[代码] [代码] [代码][代码]if[代码] [代码](onMessage_data.toName == that.data.wo && onMessage_data.name == that.data.ta) {[代码] [代码] [代码][代码]// addmsglist1(msg1.name, msg1.content)[代码] [代码] [代码][代码]that.data.allContentList.push({[代码] [代码] [代码][代码]"id"[代码][代码]: [代码][代码]""[代码][代码],[代码] [代码] [代码][代码]"hx_id"[代码][代码]: [代码][代码]""[代码][代码],[代码] [代码] [代码][代码]"wo"[代码][代码]: that.data.ta,[代码] [代码] [代码][代码]"ta"[代码][代码]: that.data.wo,[代码] [代码] [代码][代码]"content"[代码][代码]: onMessage_data.content,[代码] [代码] [代码][代码]"voice_url"[代码][代码]: [代码][代码]""[代码][代码],[代码] [代码] [代码][代码]"fileurl"[代码][代码]: [代码][代码]null[代码][代码],[代码] [代码] [代码][代码]"create_date"[代码][代码]: util.formatTime[代码] [代码] [代码][代码]});[代码] [代码] [代码][代码]that.setData({[代码] [代码] [代码][代码]allContentList: that.data.allContentList[代码] [代码] [代码][代码]})[代码] [代码] [代码][代码]that.bottom();[代码] [代码] [代码][代码]}[代码] [代码] [代码][代码]})[代码] [代码] [代码][代码]},[代码] [代码] [代码][代码]// 获取hei的id节点然后屏幕焦点调转到这个节点 [代码] [代码] [代码][代码]bottom: [代码][代码]function[代码][代码]() {[代码] [代码] [代码][代码]var[代码] [代码]that = [代码][代码]this[代码][代码];[代码] [代码] [代码][代码]this[代码][代码].setData({[代码] [代码] [代码][代码]scrollTop: 1000000[代码] [代码] [代码][代码]})[代码] [代码] [代码][代码]},[代码] [代码] [代码][代码]/**[代码] [代码] [代码][代码]* 生命周期函数--监听页面显示[代码] [代码] [代码][代码]*/[代码] [代码] [代码][代码]onShow: [代码][代码]function[代码][代码]() {[代码] [代码] [代码][代码]if[代码] [代码](!socketOpen) {[代码] [代码] [代码][代码]this[代码][代码].webSocket()[代码] [代码] [代码][代码]}[代码] [代码] [代码][代码]},[代码] [代码] [代码][代码]webSocket: [代码][代码]function[代码][代码]() {[代码] [代码] [代码][代码]const that = [代码][代码]this[代码][代码];[代码] [代码] [代码][代码]// 创建Socket[代码] [代码] [代码][代码]SocketTask = wx.connectSocket({[代码] [代码] [代码][代码]url: url,[代码] [代码] [代码][代码]data: [代码][代码]'data'[代码][代码],[代码] [代码] [代码][代码]header: {[代码] [代码] [代码][代码]'content-type'[代码][代码]: [代码][代码]'application/json'[代码] [代码] [代码][代码]},[代码] [代码] [代码][代码]method: [代码][代码]'post'[代码][代码],[代码] [代码] [代码][代码]success: [代码][代码]function[代码][代码](res) {[代码] [代码] [代码][代码]console.log([代码][代码]'WebSocket连接创建'[代码][代码], res)[代码] [代码] [代码] [代码] [代码][代码]},[代码] [代码] [代码][代码]fail: [代码][代码]function[代码][代码](err) {[代码] [代码] [代码][代码]wx.showToast({[代码] [代码] [代码][代码]title: [代码][代码]'网络异常!'[代码][代码],[代码] [代码] [代码][代码]})[代码] [代码] [代码][代码]console.log(err)[代码] [代码] [代码][代码]},[代码] [代码] [代码][代码]})[代码] [代码] [代码] [代码] [代码][代码]},[代码] [代码] [代码][代码]/**[代码] [代码] [代码][代码]* 生命周期函数--监听页面隐藏[代码] [代码] [代码][代码]*/[代码] [代码] [代码][代码]onHide: [代码][代码]function[代码][代码]() {[代码] [代码] [代码][代码]SocketTask.close([代码][代码]function[代码] [代码](res) {[代码] [代码] [代码][代码]console.log(res);[代码] [代码] [代码][代码]})[代码] [代码] [代码][代码]},[代码] [代码] [代码][代码]/**[代码] [代码] [代码][代码]* 生命周期函数--监听页面卸载[代码] [代码] [代码][代码]*/[代码] [代码] [代码][代码]onUnload: [代码][代码]function[代码][代码]() {[代码] [代码] [代码][代码]SocketTask.close([代码][代码]function[代码][代码](res){[代码] [代码] [代码][代码]console.log(res);[代码] [代码] [代码][代码]})[代码] [代码] [代码][代码]},[代码] [代码] [代码][代码]/**[代码] [代码] [代码][代码]* 页面相关事件处理函数--监听用户下拉动作[代码] [代码] [代码][代码]*/[代码] [代码] [代码][代码]onPullDownRefresh: [代码][代码]function[代码][代码]() {[代码] [代码] [代码][代码]},[代码] [代码] [代码][代码]/**[代码] [代码] [代码][代码]* 页面上拉触底事件的处理函数[代码] [代码] [代码][代码]*/[代码] [代码] [代码][代码]onReachBottom: [代码][代码]function[代码][代码]() {[代码] [代码] [代码][代码]},[代码] [代码] [代码][代码]/**[代码] [代码] [代码][代码]* 用户点击右上角分享[代码] [代码] [代码][代码]*/[代码] [代码] [代码][代码]onShareAppMessage: [代码][代码]function[代码][代码]() {[代码] [代码] [代码][代码]}[代码] [代码]})[代码]
2019-05-16 - (20)长连开发经验分享
在微信小程序/小游戏的开发中,网络传输主要是依靠http的短连接和webSocket 长连接来完成的。 在一般web服务中,大多使用短连接来向服务器请求资源,与服务器的交互频率低,次数少。而在一些需要与服务器交互频繁,需要及时收到服务器推送的场景,比如直播、多人实时游戏,更适合使用 webSocket 进行通讯。 这次的小故事主要分享 webSocket 在微信小程序/小游戏开发上的一些经验。 长连的生命周期介绍 webSocket的生命周期一共有4个状态:connecting、open、closing、closed。我们可以通过 socketTask 的 readyState 属性来获取当前 webSocket 长连的状态。webSocket 的生命周期过程和 API 间的调用关系可以简单的入下图所示。 [图片] 注意:只有长连处在 open 状态,才能够正常的收发消息,其他状态均会报错。 客户端长连的断开机制 当小游戏进入到后台运行超过5秒时,客户端会禁止小游戏的所有网络连接。这是一个非常频繁的断线逻辑,十分考验程序断线错误处理逻辑。建议大家可以在用户点击右上角按钮退出小程序/小游戏时,主动帮用户断线,待用户切回时再重接上去。 当 webSocket 长连超过一段时间没有任何网络传输时,客户端会主动关闭这条长连,以节省资源。开发者可以设置业务心跳,每隔一段时间与后台进行一次通讯,维持长连。 如何选择长连的接口 API接口主要有两类,一类是前缀为 “wx” 的接口,一类 “socketTask” 的接口。举例,同样是连接长连后发送一条消息,两种写法区别如下。 [图片] 最初小游戏只允许存在1个 webSocket 连接时,并没开放 socketTask 的管理方式。随着小游戏的能力提升,可支持同时存在的 webSocket 连接个数变多,在使用 wx.connectSocket 创建 webSocket 连接时会返回 socketTask 任务对象,便于去管理每一条连接链路。 推荐开发者尽量使用 socketTask 的方式去管理 webSocket 链接,每一条链路的生命周期都更加可控。同时存在多个 webSocket 的链接的情况下使用 wx 前缀的方法可能会带来一些和预期不一致的情况。例如:当存在多条连接时,wx.onSocketOpen、wx.sendSocketMessage、wx.onSocketMessage 等接口会只作用于第一条连接的长连。且wx.onSocketOpen 接口不能多次注册 webSocket 长连的回调函数,仅最后一次生效。使用 socketTask 任务的方式则不会出现上述问题。 开发与调试的建议 01微信提供了 webSocket 最基础的接口能力,开发者可以在其基础上进行封装,根据业务需要扩展能力。比如封装一个 offSocketOpen 的方法来取消注册 socketOpen 的回调函数。 02 长连并没有像短连那样“一问一答”的交互形式。在某些场景下,开发者需要这种与服务器的交互。建议前端与后台协议,每条客户端上行的信息,服务器都下发一个对应的回包,去“模拟短连”。比如开发者向服务器询问1+1和1+2等于多少,服务器返回了3和2,便可清晰知道哪一个数字对应着哪一个请求的答案。此外,还可以设置业务超时逻辑,便于判断上传是否丢包 03 在 webSocket 发送数据时,数据格式可以选择string或者ArrayBuffer。这里要注意的是,由于小游戏禁止了 Function() 和 eval()语法。所以像 protobufjs 这类用了这些语法的库是不能直接拿来用的。 04 在测试调试长连的时,目前开发者工具不支持通过设置 offline 模拟长连断网的情况(短连是支持的),所以在测试断线重连的一些情况时,可以辅助一些第三方工具,或者用真机调试以及“拔网线”的方式来测试。 05 在进行多人游戏测试时,在开发者工具中熟练使用“自定义编译条件”,以及“多账号调试”这两个功能可以极大的提升开发测试效率。 06 长连占用的系统资源,会导致手机发热比较明显。所以在不需要使用 webSocket 的场景下,建议及早断开长连,需要时再连接。 异常断线的监控 监控长连是否异常断线,在长连的使用中,尤其在小游戏多人对战中是尤为重要的。socketError 事件并不能认为是异常断线。 首先 socketError 事件并不一定会导致断线,其次若是由客户端机制断开的长连,是不会触发 socketError 事件的。 最简单的方式可以通过 onClose 回调函数触发时系统传入的 code 是否为1000来判断。当然开发者自身也可以通过代码判断是否是自身调用的 close 函数触发的 onclose 事件,监控异常断线。 希望大家在实际应用中能帮助到到大家。
2018-09-29