websocket(下文简称ws)
1.ws是什么:是一种在单个TCP连接上进行全双工通信的协议
tips:全双工指可以同时(瞬时)进行信号的双向传输(A→B且B→A)。指A→B的同时B→A,是瞬时同步的。
tips:单工就是在只允许甲方向乙方传送信息,而乙方不能向甲方传送
2.为什么要用ws:能够与服务器保持长久性连接,且允许服务端主动向客户端推送消息
解释:因为原本已有的http协议有个缺陷:通信只能由客户端发起。现在很多情况都会出现服务器有连续的状态变化,http协议是无法满足处理这情况。后来也出现解决方案轮询:
轮询是在特定的的时间间隔(如每1秒),由浏览器对服务器发出HTTP请求,然后由服务器返回最新的数据给客户端的浏览器。
后面又基于轮询基础上有了Commet方案,采用全双工和长连接,但缺陷还是存在:需要反复发送请求和大量消耗服务器资源。
所以基于这种情况,HTML5定义了ws协议,它可以更好的节省服务器资源和带宽,并且能够更实时地进行通讯。
3.ws基本速用:
基本封装思路:创建ws对象,然后自定义方法对应到ws对象的方法上
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script language="javascript" type="text/javascript">
var wsUri = "wss://echo.websocket.org/";
var output;
function init() {
output = document.getElementById("output");
testWebSocket();
}
function testWebSocket() {
websocket = new WebSocket(wsUri);
websocket.onopen = function (evt) {
onOpen(evt)
};
websocket.onclose = function (evt) {
onClose(evt)
};
websocket.onmessage = function (evt) {
onMessage(evt)
};
websocket.onerror = function (evt) {
onError(evt)
};
}
function onOpen(evt) {
writeToScreen("CONNECTED");
doSend("WebSocket rocks");
}
function onClose(evt) {
writeToScreen("DISCONNECTED");
}
function onMessage(evt) {
writeToScreen('<span style = "color: blue;">RESPONSE: ' +
evt.data + '</span>'); websocket.close();
}
function onError(evt) {
writeToScreen('<span style = "color: red;">ERROR:</span> '
+ evt.data);
}
function doSend(message) {
writeToScreen("SENT: " + message); websocket.send(message);
}
function writeToScreen(message) {
var pre = document.createElement("p");
pre.style.wordWrap = "break-word";
pre.innerHTML = message;
output.appendChild(pre);
}
window.addEventListener("load", init, false);
</script>
<h2>WebSocket Test</h2>
<div id="output"></div>
</body>
</html>
remark:监听推送返回,可以在onmessage保存data到redux里,再在使用地方进行读取,函数组件可以在useEffect直接监听这个onmessage赋值的变量
const socket = {
host: '',
errorCount: 0,
keepaliveObj: null,
webSocket: null,
retryConnect: null,
// 创建websocket连接
connectSocket(domain) {
const pThis = this;
if (pThis.keepaliveObj !== null) {
clearInterval(pThis.keepaliveObj);
}
const webSocket = new WebSocket(domain);
window.webSocket = webSocket;
pThis.webSocket = webSocket;
/* 建立链接 */
webSocket.onopen = evt => {
console.log('webSocket连接成功');
// 保持链接 心态机制
clearInterval(pThis.keepaliveObj);
pThis.keepaliveObj = setInterval(() => {
pThis.webSocket.send('');
}, 30000);
};
/* 链接关闭 */
webSocket.onclose = evt => {
console.log('webSocket连接关闭');
// 重连
pThis.connectSocket(domain);
};
/* 接收服务器推送消息 */
webSocket.onmessage = evt => {
const data = JSON.parse(evt.data);
// 保持链接
if (data.kind === 'heartbeat' && data.code === 0) {
console.log('success');
}
// console.log(data)
};
/* 连接发生错误时 */
webSocket.onerror = (evt, e) => {
console.log(evt);
// pThis.connectSocket()
};
},
// 初始化weosocket
connectWebSocket(domain) {
const pThis = this;
if (pThis.keepaliveObj !== null) {
clearInterval(pThis.keepaliveObj);
}
if (domain) {
this.host = domain;
}
pThis.webSocket = new WebSocket(pThis.host);
// 保存一份信息在window窗口
window.webSocket = pThis.webSocket;
// websocket 事件
pThis.webSocket.onopen = pThis.webSocketOnOpen;
pThis.webSocket.onmessage = pThis.webSocketOnMessage;
pThis.webSocket.onclose = pThis.webSocketClose;
pThis.webSocket.onerror = pThis.webSocketOnError;
if (pThis.retryConnect != null) {
clearInterval(pThis.retryConnect);
}
},
// 链接成功
webSocketOnOpen() {
clearInterval(socket.retryConnect);
socket.webSocketSend('{"kind": "heartbeat", "body": {}}');
// 保持链接
clearInterval(socket.keepaliveObj);
socket.keepaliveObj = setInterval(() => {
socket.webSocketSend('');
}, 30000);
},
// 数据接收
webSocketOnMessage(evt) {
let data = {};
try {
data = JSON.parse(evt.data);
// 数据过滤
if (data.kind === 'heartbeat') { }
if (data.code !== 0) { }
} catch (error) {
console.log(data, 'data error OnMessage');
console.log(`webSocketOnMessage parse error:${error}`);
}
//处理,可以丢到redux,也可以直接返回,主要能让每个调用的地方能接受到返回的消息体
return data
},
// 数据发送
webSocketSend(agentData) {
const {
webSocket
} = socket;
console.log(webSocket)
// 0: 对应常量CONNECTING(numeric value 0),
// 正在建立连接连接, 还没有完成。 The connection has not yet been established.
// 1: 对应常量OPEN(numeric value 1),
// 连接成功建立, 可以进行通信。 The WebSocket connection is established and communication is possible.
// 2: 对应常量CLOSING(numeric value 2)
// 连接正在进行关闭握手, 即将关闭。 The connection is going through the closing handshake.
// 3: 对应常量CLOSED(numeric value 3)
// 连接已经关闭或者根本没有建立。 The connection has been closed or could not be opened.
if (webSocket.readyState === 1) {
socket.webSocket.send(agentData);
} else {
console.log('state', webSocket.readyState)
// socket.connectWebSocket();
// socket.webSocketSend(agentData);
}
},
// 关闭
webSocketClose(evt) {
console.log(`connection closed (${evt.code})`);
// 错误次数少于10时重连
if (socket.errorCount <= 10) {
socket.connectWebSocket();
socket.errorCount++;
} else {
// 1分钟连一次
socket.retryConnect = setInterval(() => {
socket.connectWebSocket();
}, 60000);
}
},
// 错误
webSocketOnError(evt, e) {
console.log(`connection closed (${evt.code})`);
},
};
export default socket;
4.ws使用可能遇到问题
ws关闭状态码表:大概归结网络错误、长时间连接和服务器发生错误(由于数据量过大,遭受攻击等)
移动端:js在手机熄屏后会中断,在唤醒之后js会继续执行。所以设置在js中的定时发送心跳包的功能在手机熄屏后就没法执行了。熄屏时间过长,这个时候链接就会被服务端强制断开,并且大部分手机在熄屏时,websocket连接就已经断开了。方法如下:
document.addEventListener('visibilitychange', function () {
if (document.visibilityState == 'hidden') {
that.hiddenTime = new Date().getTime() //记录页面隐藏时间
} else {
let visibleTime = new Date().getTime();
if ((visibleTime - that.hiddenTime) / 1000 > 10) { //页面再次可见的时间-隐藏时间>10S,重连
typeof that.ws.close == 'function' && that.ws.close(); //先主动关闭连接
setTimeout(function () {
that.openSocket() //打开连接,使用的vuejs,这是websocket的连接方法
}, 3000); //3S后重连
} else {
//非重连处理
}
}
});
总结:大概思路都是在ws断开方法里设置定时重连机制