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