详解心跳包,理解心跳包原理。用node.js一步一步手把手构建websocket心跳包检测

2021年9月7日 3点热度 0条评论 来源: 年轻人_gyc

技术栈:

服务端: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也是同理,个人所理解长连接得心跳都是这个道理!

    原文作者:年轻人_gyc
    原文地址: https://blog.csdn.net/a519637560/article/details/103073478
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系管理员进行删除。