我是
靠谱客的博主
大意手套,最近开发中收集的这篇文章主要介绍
WebSocket在服务端和客户端通信demo,支持心跳检测+断线重连,觉得挺不错的,现在分享给大家,希望可以做个参考。
概述
一、为什么需要 WebSocket?
初次接触 WebSocket 的人,都会问同样的问题:我们已经有了 HTTP 协议,为什么还需要另一个协议?它能带来什么好处?
答案很简单,因为 HTTP 协议有一个缺陷:通信只能由客户端发起。
举例来说,我们想了解今天的天气,只能是客户端向服务器发出请求,服务器返回查询结果。HTTP 协议做不到服务器主动向客户端推送信息。
这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。我们只能使用"轮询":每隔一段时候,就发出一个询问,了解服务器有没有新的信息。最典型的场景就是聊天室。
轮询的效率低,非常浪费资源(因为必须不停连接,或者 HTTP 连接始终打开)。因此,工程师们一直在思考,有没有更好的方法。WebSocket 就是这样发明的。
它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。
其他特点包括:
(1)建立在 TCP 协议之上,服务器端的实现比较容易。
(2)与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。
(3)数据格式比较轻量,性能开销小,通信高效。
(4)可以发送文本,也可以发送二进制数据。
(5)没有同源限制,客户端可以与任意服务器通信。
(6)协议标识符是ws
(如果加密,则为wss
),服务器网址就是 URL
----------------------------------------------------------------------------------------------------------------------------
以上参考: http://www.ruanyifeng.com/blog/2017/05/websocket.html
下面是可以用于生产环境的demo
服务端:
// npm install nodejs-websocket
const
ws =
require(
"nodejs-websocket")
console.
log(
"开始建立链接")
let
rd,
obj,
time,
intervalObj =
null
const
server =
ws.
createServer(
conn
=> {
conn.
on(
"text",
str
=> {
time =
new
Date()
time =
time.
getFullYear() +
"-" + (
time.
getMonth() +
1) +
"-" +
time.
getDate() +
" "
+
time.
getHours() +
":" +
time.
getMinutes() +
":" +
time.
getSeconds()
console.
log(
`接受到的信息为:
${
str
}
, 接受到信息的时间为:
${
time
}
`)
obj =
JSON.
parse(
str)
// 回应浏览器发送的ping,判断链接是否已经断开
if (
obj.
event ===
"ping") {
conn.
sendText(
'{"event": "pong"}')
}
// 订阅数据
if (
obj.
event ===
"getData") {
// 模拟数据一直在推送
intervalObj =
setInterval(()
=> {
rd =
Math.
floor(
Math.
random(
0,
1) *
10)
conn.
sendText(
JSON.
stringify({
event:
'getData',
dt: {
num:
rd}}))
},
1000)
}
})
conn.
on(
"close",
function (
code,
reason) {
clearInterval(
intervalObj)
console.
log(
"Connection closed")
})
// 必须监控error, 每当浏览器刷新时会断开链接报错
conn.
on(
"error",
function (
error) {
clearInterval(
intervalObj)
console.
log(
"Connection error",
error)
})
}).
listen(
8001)
客户端
index.html
<!DOCTYPE html
>
<
html
lang=
"en"
>
<
head
>
<
meta
charset=
"UTF-8"
>
<
meta
name=
"viewport"
content=
"width=device-width, initial-scale=1.0"
>
<
meta
http-equiv=
"X-UA-Compatible"
content=
"ie=edge"
>
<
title
>Document
</
title
>
</
head
>
<
body
>
<
div
id=
"message"
></
div
>
</
body
>
</
html
>
<
script
type=
"text/javascript"
src=
"./webSocket.js"
></
script
>
<
script
type=
"text/javascript"
src=
"./init.js"
></
script
>
init.js
const
msg =
document.
querySelector(
"#message")
let
ws,
_getTimer
// 整个站点只要创建一次websocket链接,不同的数据源进行多次订阅即可
ws =
new
webSocketFn(
"ws://127.0.0.1:8001")
// 注册异常调用函数,每个订阅要有一个异常处理函数
let
webSocketData = ()
=> {
ws.
webSocketSend(
'{"event": "getData"}') }
let
ajaxData = ()
=> {
// 接口请求
console.
log(
777)
}
let
fail =
ws.
getSocketFile(
webSocketData,
ajaxData)
ws.
errorCallBackFunArr.
push(
fail)
// 注册成功的回掉,每个订阅对应的要注册一个回掉
ws.
successFn[
'getData'] = (
data)
=> {
data && (
msg.
innerHTML =
data.
dt.
num)
}
let
firstSend = ()
=> {
if (
ws.
isConnection()) {
webSocketData()
}
else {
// 第一次请求可能websocket还没有链接,过200毫秒再试一次
ajaxData()
setTimeout(
webSocketData,
200)
}
}
firstSend()
webSocket.js
class
WebSocketClass {
constructor (
wsUrl) {
this.
successFn = {}
// 成功的回掉函数
this.
wsUrl =
wsUrl
// 请求的url
this.
errorCallBackFunArr = []
// 当推送异常时执行的数组
this.
isConnection =
false
// 判断是否支持weosocket
this.
isErrorCallBack =
false
this.
lockReconnect =
false
// 避免重复连接
this.
ping =
null
this.
sendObj = {}
this.
heartCheck()
this.
createWebSocket()
}
createWebSocket () {
try {
this.
webSocket =
new
WebSocket(
this.
wsUrl)
this.
initEventHandle()
}
catch (
e) {
this.
errorCallBackData()
this.
reconnect(
this.
wsUrl)
}
}
initEventHandle () {
this.
webSocket.
onclose = ()
=> {
this.
errorCallBackData()
this.
reconnect(
this.
wsUrl)
}
this.
webSocket.
onerror = ()
=> {
this.
errorCallBackData()
this.
reconnect(
this.
wsUrl)
}
this.
webSocket.
onopen = ()
=> {
this.
isConnection =
true
this.
isErrorCallBack =
false
clearInterval(
this.
ping)
this.
ping =
setInterval(()
=> {
this.
send(
'{"event": "ping"}')
},
10000)
// 心跳检测重置
this.
heartCheck.
start()
}
this.
webSocket.
onmessage =
data
=> {
// 如果获取到消息,心跳检测重置
// 拿到任何消息都说明当前连接是正常的
this.
heartCheck.
start()
this.
decodeData(
data)
}
}
send(
cmd) {
// 只有当 webSocket.readyState 为 OPEN才发送订阅
// readyState属性返回实例对象的当前状态,共有四种。
// CONNECTING:值为0,表示正在连接。
// OPEN:值为1,表示连接成功,可以通信了。
// CLOSING:值为2,表示连接正在关闭。
// CLOSED:值为3,表示连接已经关闭,或者打开连接失败。
if (!
cmd) {
return
}
// this.sendObj[cmd] && (delete clearTimeout(this.sendObj[cmd]))
if (
this.
webSocket.
readyState ===
1) {
// 只有当链接打开时才进行订阅
this.
webSocket.
send(
cmd)
}
// else if (this.webSocket.readyState === 0) { // 如果链接处于正在链接中,则进行延时订阅
// this.sendObj[cmd] = setTimeout(() => {
// this.send(cmd)
// }, 50)
// }
}
reconnect (
url) {
if (
this.
lockReconnect) {
return
}
this.
lockReconnect =
true
// 没连接上会一直重连,设置延迟避免请求过多
setTimeout(()
=> {
this.
createWebSocket(
url)
this.
lockReconnect =
false
},
2000)
}
errorCallBackData () {
this.
isConnection =
false
if (!
this.
isErrorCallBack &&
this.
errorCallBackFunArr.
length) {
this.
isErrorCallBack =
true
for (
let
i =
0,
len =
this.
errorCallBackFunArr.
length;
i <
len;
i++) {
if ((
typeof
this.
errorCallBackFunArr[
i]) ===
'function') {
this.
errorCallBackFunArr[
i]()
}
}
}
}
decodeData (
data) {
if (
data.
data
instanceof
Blob) {
let
blob =
data.
data
// js中的blob没有没有直接读出其数据的方法,通过FileReader来读取相关数据
let
reader =
new
FileReader()
reader.
readAsArrayBuffer(
blob)
// 当读取操作成功完成时调用.
reader.
onload = (
evt)
=> {
if (
evt.
target.
readyState ===
FileReader.
DONE) {
let
result =
new
Uint8Array(
evt.
target.
result)
// 如果后端进行压缩数据处理(zlib),那么要引入解析zlib的js
result = (
new
window.
Zlib.
RawInflate(
result)).
decompress()
let
strResult =
''
let
length =
result.
length
for (
let
i =
0;
i <
length;
i++) {
strResult +=
String.
fromCharCode(
result[
i])
}
this.
callBackData(
JSON.
parse(
strResult))
}
}
return
}
let
d =
JSON.
parse(
data.
data)
// 如果后端需要等待,则返回code:10010,过一段时间后重新订阅
if (
d.
code ===
'10010') {
let
dt = {}
Object.
assign(
dt,
d)
delete
dt.
code
delete
dt.
msg
setTimeout(()
=> {
this.
send(
JSON.
stringify(
dt))
},
2000)
return
}
this.
callBackData(
JSON.
parse(
data.
data))
}
callBackData (
data) {
if (
data
instanceof
Array) {
for (
let
i =
0;
i <
data.
length;
i++) {
this.
doCallback(
data[
i])
}
}
else
if (
data
instanceof
Object) {
if (
data.
hasOwnProperty(
'event') &&
data.
event ===
'pong') {
return
}
data.
payload && (
data.
payload =
JSON.
parse(
data.
payload))
this.
doCallback(
data)
}
}
doCallback (
data) {
if (
data.
event) {
let
fn =
this.
successFn[
data.
event]
if (
typeof
fn ===
'function') {
fn(
data)
}
}
}
// 心跳检测
heartCheck () {
let
that =
this
this.
heartCheck = {
timeout:
10000,
// 10秒
timeoutObj:
null,
serverTimeoutObj:
null,
reset
:
function () {
clearTimeout(
this.
timeoutObj)
clearTimeout(
this.
serverTimeoutObj)
},
start
:
function () {
this.
reset()
let
self =
this
this.
timeoutObj =
setTimeout(
function () {
// 这里发送一个心跳,后端收到后,返回一个心跳消息
// onmessage拿到返回的心跳就说明连接正常
that.
send(
'{"event": "ping"}')
self.
serverTimeoutObj =
setTimeout(
function () {
// 如果超过一定时间还没重置,说明后端主动断开了
// 如果onclose会执行reconnect,我们执行ws.close()就行了.如果直接执行reconnect 会触发onclose导致重连两次
that.
webSocket.
close()
},
self.
timeout)
},
this.
timeout)
}
}
}
}
function
webSocketFn (
url) {
let
wb =
new
WebSocketClass(
url)
return {
webSocketSend:
wb.
send.
bind(
wb),
errorCallBackFunArr:
wb.
errorCallBackFunArr,
successFn:
wb.
successFn,
isConnection
:
function () {
return
wb.
isConnection
},
getSocketFile (
webSocketData,
ajaxData) {
let
socketFail = (
isFirst)
=> {
if (
this.
isConnection()) {
webSocketData()
}
else {
ajaxData()
setTimeout(
socketFail,
2000)
}
}
return
socketFail
}
}
}
最后
以上就是大意手套为你收集整理的WebSocket在服务端和客户端通信demo,支持心跳检测+断线重连的全部内容,希望文章能够帮你解决WebSocket在服务端和客户端通信demo,支持心跳检测+断线重连所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复