詳解心跳包,理解心跳包原理。用node.js一步一步手把手構建websocket心跳包檢測

技術棧:

服務端: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也是同理,個人覺得長連接得心跳都是這個道理

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章