技术栈:
服务端:node.js ,nodejs-websocket ,event
前端技术:uniapp websocket得api可以查看该文档,https://uniapp.dcloud.io/api/timer
实现场景:在服务端和客户端如果出现了长连接传输数据的时候,出现了前端断开,服务端没有检测到前端的断开,服务端还保留数据,当客户端再次上线的时候就会出现某些问题。
还可以出现在,服务端与其他产品的问题,比如说,服务端和音箱。之间的传输数据是TCP
首先,贴出代码可以先看看,后面一步一步讲解
服务端
SocketClient.js
这个js文件中存放是,创建websocket得一些基本操作。用node.js创建websocket很简单。在网上百度一大堆教程,不知道怎么去创建得可以先去百度
const ws = require("nodejs-websocket");
const Observer = require('../Observer/Socket.js')
class SocketClient {
constructor(port){
this.port = port;
this.Run()
}
Run(){
//断线由心跳包控制
ws.createServer(function (conn) { //在人进来的时候,需要把他的conn连接池和他的身份id给关联对应起来
conn.on("text", function (str) { //接收字符串类型的数据
})
conn.on("close", function (code,res) {
for (var i=0;i<UserInfo.length;i++){
if(UserInfo[i].conn == conn){
UserInfo.splice(i,1)
console.log('触发关闭删除:'+ UserInfo.splice(i,1))
}
}
})
conn.on("error", function (err) {
for (var i=0;i<UserInfo.length;i++){
if(UserInfo[i].conn == conn){
UserInfo.splice(i,1)
console.log('发生错误删除:'+ UserInfo.splice(i,1))
}
}
})
conn.on("binary", function (inStream) {
inStream.on("readable", async function () {
var newData = inStream.read();
if (newData) {
Observer.emit('SocketToApp',conn,newData)
}
})
})
}).listen(this.port);
}
}
module.exports = SocketClient;
其次我们在看Socket.js这个文件
现在涉及到了event模块得使用,这个也很简单。node.js属于事件驱动行,全程异步操作。所以步会event模块得可以先去百度一下了解一下这个东西。
我们在 SocketClient.js
文件中 Observer.emit('SocketToApp',conn,newData)
有这一句话,这句话得意思就是调用Socket.js文件中得方法。 这个在event模块会有讲解得。不明白得可以去看看,其实就是一句话,在监听到客户端给我发二进制数据得时候我调用 SocketToApp
这个方法,传入了 conn,newData
这俩参数而已。conn是链接池,newData是客户端传入得数据
var Observer = require('../Observer');
const DataPackage = require('xbtool').LineData
Observer.on('SocketToApp',function (conn,newData) {
if(newData[2] == 1){
console.log(newData)
let GetString = new DataPackage(newData, 3); //先送棋盘,再送音箱
let DeviceStr = GetString.ReadString();
let item = DeviceStr.split('|')
let SaveOk = checkUserInfo(item[0],item[1]) //已经存在了
if(SaveOk == null){
let curDate= new Date();
let data = {
board:item[0],
soundBox:item[1],
conn:conn,
Time:curDate,
};
console.log('新人上线:board:' +item[0]+' soundBox:'+item[1])
UserInfo.push(data)
console.log('所有得设备列表:'+ JSON.stringify(DeviceList))
let str = ''
try{
for (var i=0;i<DeviceList.board.length;i++){
let tmp = DeviceList.board
if (tmp[i].deviceId == item[0]){
str =utils.WriteInt8(null,1)
console.log('音箱在线:')
}
}
}catch (e) {
}
if(str == ''){
str = utils.WriteInt8(null,0)
}
let b = false
try {
for (var j=0;j<DeviceList.SoundBox.length;j++){
let tmp = DeviceList.SoundBox
if(tmp[j].deviceId == item[1]){
b = true
str = uitls.WriteInt8(str,1)
console.log('棋盘在线:')
}
}
}catch (e) {
}
if(b == false){
str = uitls.WriteInt8(str,0)
}
str = utils.WriteCmdToBuffer(str,3);
conn.sendBinary(str)
}else {
console.log('被人挤下线:board:' +UserInfo[SaveOk].board)
let str15 = utils.WriteCmdToBuffer(null,15); //挤下线了发送互冲指令
UserInfo[SaveOk].conn.sendBinary(str15)
UserInfo[SaveOk].conn = conn
}
let str1 = utils.WriteCmdToBuffer(null,2);
conn.sendBinary(str1)
}else if(newData[2] == 13){ //处理心跳包
SolveHeartPackage(conn)
}else if(newData[2] == 5){
}
})
function SolveHeartPackage(conn) {
let curDate= new Date();
for (var i=0;i<UserInfo.length;i++){
if(UserInfo[i].conn == conn){
UserInfo[i].Time = curDate
}
}
}
function checkUserInfo(board,soundBox){
console.log(board,'棋盘id')
console.log(soundBox,'音箱id')
for (var i=0;i<UserInfo.length;i++){
if(UserInfo[i].board == board){
return i
}else if(UserInfo[i].soundBox == soundBox){
return i
}
}
return null
}
module.exports = Observer;
首先,我们代码里写的是 cmd == 13
得时候去调用心跳包得函数 ,cmd是什么呢?cmd是我自己定义得一种数据格式,等于客户端如果给我发送数据是13开头得那说明他就是心跳数据,如果不是13那就是其他数据。
function SolveHeartPackage(conn) {
let curDate= new Date();
for (var i=0;i<UserInfo.length;i++){
if(UserInfo[i].conn == conn){
UserInfo[i].Time = curDate
}
}
}
这个呢,是解析心跳包,首先我接收了到心跳包,传入了conn。这个时候呢我得想,怎么才能知道客户端离开了呢,于是我定义了一个类叫 UserInfo
里面有一个属性是time
用于记录客户端传入得每一次接收他心跳得当前时间,谁传给我得。我绑定在谁得Userinfo上,Userinfo上绑定了很多信息,在我第一次接收客户端数据得时候,就创建了。 ps:心跳包可不是客户端一链接上就发送得哟。。 好,这个时候我把每个用户都绑定了最后得时间。
setInterval(function () {
let ii = []
let date = new Date();
for (var i = 0; i < UserInfo.length; i++) {
let time = date - UserInfo[i].Time
if (time > 6000) {
ii.push(i)
}
}
if (ii.length !== 0) {
for (var j = 0; j < ii.length; j++) {
let index = ii[j]
console.log('离线检查:'+ UserInfo.splice(i,1))
UserInfo.splice(index,1)
}
}
}, 6000) //6秒检查一次
大家都知道这是一个异步得定时器,6秒执行一次。他得作用是什么呢?他得作用就是每过6秒去检测一下用户列表查看一下那个得时间长时间没有去更新了。如果找到了长时间没有去更新得,如果没有长时间更新那么我就是断开他得链接,就直接删除他在Userinfo里面数据。就ok了,我服务端得事情就ok了
let date = new Date();
for (var i = 0; i < UserInfo.length; i++) {
let time = date - UserInfo[i].Time
if (time > 6000) {
ii.push(i)
}
}
这段代码就是去检测,是否超时,我设置得时间是6000毫秒,大家想设置多久。自己随意
接下来是客户端,uniapp我建议做app大家可以去尝试一下很简单。基本得不教,自己去看文档,基本上都是ok得
我创建了一个netSocket.js文件
function CreateTheSocket(ipAddress){
FNetSocket = uni.connectSocket({
url:ipAddress,
header: {
'binarytype': 'arraybuffer'
},
method: 'GET',
success: function(e) {}
});
store.state.FNetSocket = FNetSocket
FNetSocket.onOpen(function(res){
console.log("ok socket connect ok");
SendUserDeviceInfo();
});
/*
收到了socket 的数据...
*/
FNetSocket.onMessage(function(res){
console.log("ok receive From Server ");
var content = new Int8Array(res.data);
ReadData(content);
});
FNetSocket.onClose(function(res){
console.log("ok here close the socket");
// #ifdef H5
var content = new Int8Array(evt.data);
ReadData(content);
// #endif
// #ifdef APP-PLUS
if (evt && evt.data) {
var content = new Int8Array(evt.data);
ReadData(content);
}
// #endif
});
FNetSocket.onError(function(res){
console.log("ok here the socket error");
})
}
这个是创建websocket得方法,看不懂得可以去对照文档看。应该可以得很简单,https://uniapp.dcloud.io/api/request/websocket
FNetSocket
是创建socket之后返回得对象,我在创建得时候就给服务端发送了一条信息,SendUserDeviceInfo
方法中发送了第一条数据
function SendUserDeviceInfo(){
var S = store.state.boardDeviceid + "|" + store.state.soundBoxDeviceid;
var SendData = WriteString(null, S);
SendData = WriteCmdToBuffer(SendData, 1);
if (FNetSocket != null) {
FNetSocket.send({
data:SendData
})
}
}
这些WriteString,WriteCmdToBuffer,都是我自己封装得方法,就是把数据变成二进制得方法
。
在FNetSocket.onOpen
打开socket得时候,我就调用了发送数据得方法,发送得是一个cmd=1得方法,大家可以去看一下服务端,我在服务端cmd =1 得时候,给客户端发送了一个cmd = 2得数据,就是告诉客户端,他可以开始发送心跳了
于是接下来就是,该在cmd=2得时候发送客户端得数据了
在ReadData
方法里面去进行数据解析,由于我这里传输得是二进制数据,涉及算法解析问题,就不给大家看了
由于在ReadData里面进行解析成功后,我接收到一个cmd= 2得数据
我在SendHeartPackage
方法里面,封装了心跳发送。意思就是说每过5秒发送一条cmd = 13得数据给服务端,我在服务端得时候接收到cmd =13得时候去操作心跳,大家可以去看前面服务端得代码可以看的见得。 这就是心跳得原理,感觉也不难
function SendHeartPackage(){
console.log('心跳')
setInterval(function(){
let SendData = WriteCmdToBuffer(null,13) //cmd 13是心跳包
FNetSocket.send({
data:SendData
})
console.log('sendHeartPackage')
},5000)
}
大家如果要尝试玩玩就用字符串为传输得格式,如果说是正式项目得运用就用 Buffer数据,如果用过node.js写过服务端得应该不会陌生得。 https://www.runoob.com/nodejs/nodejs-buffer.html 这个文档是解释什么是buffer数据得。大家如果耐心看完了,请给个赞,顺便动手试一试, 其实TCP也是同理,个人所理解长连接得心跳都是这个道理!