# 标签页通信
# websocket
WebSocket 是一种基于 TCP 的网络协议,用于在客户端和服务器之间建立持久连接,实现全双工通信。它允许服务器主动向客户端推送数据,同时也允许客户端向服务器发送数据,从而在Web应用程序中提供了实时、双向的通信能力。以下是对 WebSocket 的详细介绍:
一、WebSocket 的基本概念
- 协议基础:WebSocket 协议是独立于 HTTP 的,但握手阶段采用 HTTP 协议。它通过 HTTP/1.1 协议的 101 状态码进行握手,之后数据交换则遵循 WebSocket 的协议规范。
- 全双工通信:WebSocket 提供了全双工的通信模式,即数据可以在同一时间内双向流动,这使得实时交互变得更加高效和直接。
- 持久连接:WebSocket 连接一旦建立,就会保持长时间的连接状态,避免了传统 HTTP 请求中频繁建立和关闭连接的开销。
二、WebSocket 的特点
- 实时性:由于 WebSocket 的持久化连接,它可以实现实时的数据传输,避免了 Web 应用程序需要不断地发送请求以获取最新数据的情况。
- 双向通信:WebSocket 协议支持双向通信,这意味着服务器可以主动向客户端发送数据,而不需要客户端发送请求。
- 减少网络负载:由于 WebSocket 的持久化连接,它可以减少 HTTP 请求的数量,从而减少了网络负载和带宽的消耗。
- 更好的二进制支持:WebSocket 定义了二进制帧,相对 HTTP,可以更轻松地处理二进制内容。
- 跨域使用:WebSocket 协议可以跨域使用,允许不同源的客户端与服务器进行通信。
三、WebSocket 的工作原理 WebSocket 的工作流程可以分为以下几个阶段:
- 握手阶段:客户端通过发送一个 HTTP 请求给服务器来建立 WebSocket 连接。请求头中包含了一些特殊的字段,如 Upgrade 和 Connection 字段,告诉服务器它希望升级到 WebSocket 连接。服务器收到请求后,会进行相应的处理并返回一个 HTTP 响应,响应头中同样包含了一些特殊的字段,如 Upgrade 和 Connection 字段,以及一个 Sec-WebSocket-Accept 字段,用于验证请求的合法性。如果验证通过,WebSocket 连接就成功建立了。
- 数据传输阶段:一旦 WebSocket 连接建立成功,客户端和服务器就可以通过该连接进行双向通信了。数据交换遵循 WebSocket 的协议规范,包括数据帧的发送和接收。
- 连接关闭阶段:当客户端或服务器决定关闭连接时,可以发送一个特殊的消息(如 Close 帧),通知对方关闭连接。双方收到关闭消息后,会相应地关闭连接。
四、WebSocket 的应用场景 WebSocket 协议因其实时性和双向通信的特点,广泛应用于需要实时交互的 Web 应用程序中,如在线聊天、实时通知、实时游戏、远程控制等场景。
五、WebSocket 的安全性 虽然 WebSocket 协议本身提供了实时通信的能力,但在安全性方面仍需注意。由于 WebSocket 允许服务器主动向客户端发送数据,因此服务器必须确保只向合法的客户端发送数据,避免数据泄露或被恶意利用。此外,WebSocket 连接也可以通过 SSL/TLS 协议进行加密,以提高通信的安全性。
六、WebSocket 的实现 在 Web 应用程序中,WebSocket 可以通过浏览器提供的 WebSocket API 来实现。WebSocket API 提供了一系列的方法和事件处理程序,用于创建和管理 WebSocket 连接,以及通过该连接发送和接收数据。同时,服务器端也需要实现相应的 WebSocket 协议支持,以便与客户端进行通信。
总之,WebSocket 是一种高效、实时的通信协议,它极大地改善了 Web 应用程序中客户端和服务器之间的交互方式。随着 Web 技术的不断发展,WebSocket 将在更多领域得到应用和推广。
客户端
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
url:<input id="url" value="ws://127.0.0.1:3000/"/>
<button onclick="svc_connectPlatform()"> connect</button><br/>
<br/>
<input id="msg" />
<button id="sendbtn"> send</button>
<script>
var url = document.getElementById("url");
var sendbtn = document.getElementById("sendbtn");
var inputmsg = document.getElementById("msg");
sendbtn.onclick = function(){
svc_send(inputmsg.value);
}
function svc_connectPlatform() {
//alert("");
var wsServer = url.value.trim();
try {
svc_websocket = new WebSocket(wsServer);
} catch (evt) {
console.log("new WebSocket error:" + evt.data);
svc_websocket = null;
if (typeof(connCb) != "undefined" && connCb != null)
connCb("-1", "connect error!");
return;
}
//alert("");
svc_websocket.onopen = svc_onOpen;
svc_websocket.onclose = svc_onClose;
svc_websocket.onmessage = svc_onMessage;
svc_websocket.onerror = svc_onError;
}
function svc_onOpen(evt) {
console.log("Connected to WebSocket server.");
}
function svc_onClose(evt) {
console.log("Disconnected");
}
function svc_onMessage(evt) {
console.log('Retrieved data from server: ' + evt.data);
}
function svc_onError(evt) {
console.log('Error occured: ' + evt.data);
}
function svc_send(msg) {
if (svc_websocket.readyState == WebSocket.OPEN) {
svc_websocket.send(msg);
} else {
console.log("send failed. websocket not open. please check.");
}
}
</script>
</body>
</html>
服务端
var ws = require('nodejs-websocket');
var wsPort = 3000;
var peers = [];
var server = ws.createServer(function(conn){
peers.push(conn);
// 事件名称为text(读取字符串时,就叫做text),读取客户端传来的字符串
var count = 1;
conn.on('text', function(str) {
// 在控制台输出前端传来的消息
console.log(str);
//向前端回复消息
//conn.sendText('服务器端收到客户端端发来的消息了!' + count++);
//群发
for(let i=0,len=peers.length;i<len;i++){
if(peers[i]!=conn){
peers[i].sendText('转发客户端的消息:'+str);
}
}
});
conn.on('close',(code, reason)=>{
console.log("Connection closed")
});
conn.on("error", function (code, reason) {
console.log("异常关闭")
});
});
server.listen(wsPort,'0.0.0.0',()=>{
console.log('websocket服务启动-使用端口',wsPort);
});
# 为什么需要pong响应机制?
之所以需要 Pong 响应机制,是因为在 WebSocket 连接客户端和服务端之间发送数据时,底层的网络传输有可能会发生数据包丢失或延迟的情况,这可能导致客户端发送的 Ping 数据包无法成功送达服务端。如果只依靠客户端发送 Ping 数据包来保持 WebSocket 连接的活跃状态,那么不能可靠地判断连接是否正常。
Pong 响应机制可以通过检查服务端返回的 Pong 数据包,判断服务端是否正常连接。如果客户端在指定时间内未接收到服务端返回的 Pong 数据包,则可以判断连接已断开,从而执行相应的重连操作,从而保证 WebSocket 连接的稳定性和可靠性,并防止因连接长时间闲置而被服务端断开的情况发生。
# 是否监听ping响应失败可以执行重连?
ping响应失败无法判断服务端是否断开。有可能是因为网络传输问题(网络差)导致数据包丢失或者延迟情况导致发送失败,所以需要pong响应机制判断服务端的维护状态。
import { EventDispatcher } from './dispatcher';
export class WebSocketClient extends EventDispatcher {
// #socket链接
private url = '';
// #socket实例
private socket: WebSocket | null = null;
// #重连次数
private reconnectAttempts = 0;
// #最大重连数
private maxReconnectAttempts = 5;
// #重连间隔
private reconnectInterval = 10000; // 10 seconds
// #发送心跳数据间隔
private heartbeatInterval = 1000 * 30;
// #计时器id
private heartbeatTimer?: NodeJS.Timeout;
// #彻底终止ws
private stopWs = false;
// *构造函数
constructor(url: string) {
super();
this.url = url;
}
// >生命周期钩子
onopen(callBack: Function) {
this.addEventListener('open', callBack);
}
onmessage(callBack: Function) {
this.addEventListener('message', callBack);
}
onclose(callBack: Function) {
this.addEventListener('close', callBack);
}
onerror(callBack: Function) {
this.addEventListener('error', callBack);
}
// >消息发送
public send(message: string): void {
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
this.socket.send(message);
} else {
console.error('[WebSocket] 未连接');
}
}
// !初始化连接
public connect(): void {
if (this.reconnectAttempts === 0) {
this.log('WebSocket', `初始化连接中... ${this.url}`);
}
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
return;
}
this.socket = new WebSocket(this.url);
// !websocket连接成功
this.socket.onopen = event => {
this.stopWs = false;
// 重置重连尝试成功连接
this.reconnectAttempts = 0;
// 在连接成功时停止当前的心跳检测并重新启动
this.startHeartbeat();
this.log('WebSocket', `连接成功,等待服务端数据推送[onopen]... ${this.url}`);
this.dispatchEvent('open', event);
};
this.socket.onmessage = event => {
this.dispatchEvent('message', event);
this.startHeartbeat();
};
this.socket.onclose = event => {
if (this.reconnectAttempts === 0) {
this.log('WebSocket', `连接断开[onclose]... ${this.url}`);
}
if (!this.stopWs) {
this.handleReconnect();
}
this.dispatchEvent('close', event);
};
this.socket.onerror = event => {
if (this.reconnectAttempts === 0) {
this.log('WebSocket', `连接异常[onerror]... ${this.url}`);
}
this.closeHeartbeat();
this.dispatchEvent('error', event);
};
}
// > 断网重连逻辑
private handleReconnect(): void {
if (this.reconnectAttempts < this.maxReconnectAttempts) {
this.reconnectAttempts++;
this.log('WebSocket', `尝试重连... (${this.reconnectAttempts}/${this.maxReconnectAttempts}) ${this.url}`);
setTimeout(() => {
this.connect();
}, this.reconnectInterval);
} else {
this.closeHeartbeat();
this.log('WebSocket', `最大重连失败,终止重连: ${this.url}`);
}
}
// >关闭连接
public close(): void {
if (this.socket) {
this.stopWs = true;
this.socket.close();
this.socket = null;
this.removeEventListener('open');
this.removeEventListener('message');
this.removeEventListener('close');
this.removeEventListener('error');
}
this.closeHeartbeat();
}
// >开始心跳检测 -> 定时发送心跳消息
private startHeartbeat(): void {
if (this.stopWs) return;
if (this.heartbeatTimer) {
this.closeHeartbeat();
}
this.heartbeatTimer = setInterval(() => {
if (this.socket) {
this.socket.send(JSON.stringify({ type: 'heartBeat', data: {} }));
this.log('WebSocket', '送心跳数据...');
} else {
console.error('[WebSocket] 未连接');
}
}, this.heartbeatInterval);
}
// >关闭心跳
private closeHeartbeat(): void {
clearInterval(this.heartbeatTimer);
this.heartbeatTimer = undefined;
}
}
# localstorage
localstorage是浏览器多个标签共用的存储空间,所以可以用来实现多标签之间的通信 直接在window对象上添加监听即可:
window.onstorage = (e) => {console.log(e)}
// 或者这样
window.addEventListener('storage', (e) => console.log(e))
onstorage以及storage事件, 针对都是非当前页面对localStorage进行修改时才会触发,当前页面修改localStorage不会触发监听函数。 然后就是在对原有的数据的值进行修改时才会触发,比如原本已经有一个key会a值为b的localStorage,你再执行:localStorage.setItem('a', 'b')代码,同样是不会触发监听函数的。
注意
Safari 在无痕模式下设置localstorge值时会抛出 QuotaExceededError 的异常;
# localstorage监听变化
var orignalSetItem = localStorage.setItem;
localStorage.setItem = function(key,newValue){
var setItemEvent = new Event("setItemEvent");
setItemEvent.key = key;
setItemEvent.newValue = newValue;
setItemEvent.oldValue = localStorage.getItem(key);
window.dispatchEvent(setItemEvent);
console.log(arguments)
orignalSetItem.apply(this,arguments);
}
window.addEventListener("setItemEvent", function (e) {
console.log('key: '+e.key);
console.log('newValue: '+e.newValue);
console.log('oldValue: '+e.oldValue);
});
localStorage.setItem("n1","1234");
localStorage.setItem("n2","1234");
# h5新特性SharedWorker
- SharedWorker地址注意最好不是本地地址
- 在SharedWorker中,如果你想要将发送者的信息传播给其他页面(即连接到SharedWorker的其他端口),你不能直接通过port.postMessage发送消息给特定的其他端口,因为port对象只代表当前连接。但是,你可以设置一个机制来存储和广播消息给所有连接的端口。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SharedWorker Example</title>
</head>
<body>
<h1>SharedWorker Example</h1>
<button onclick="sendMessage()">Send Message to SharedWorker</button>
<script>
// 连接到SharedWorker
var worker = new SharedWorker('http://127.0.0.1:8848/code/sharedworker.js');
// 连接到SharedWorker的端口
var port = worker.port;
// 监听从SharedWorker发送的消息
port.onmessage = function(e) {
console.log('从sharedwork获取到信息', e.data);
// 在这里,你可以根据接收到的消息更新页面或其他操作
};
// 发送消息到SharedWorker的函数
function sendMessage() {
port.postMessage('html页面发送的消息');
}
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SharedWorker Example</title>
</head>
<body>
<h1>SharedWorker Example</h1>
<!-- <button onclick="sendMessage()">Send Message to SharedWorker</button> -->
<script>
// 连接到SharedWorker
var worker = new SharedWorker('http://127.0.0.1:8848/code/sharedworker.js');
// 连接到SharedWorker的端口
var port = worker.port;
// 监听从SharedWorker发送的消息
port.onmessage = function(e) {
console.log('从sharedwork获取到信息', e.data);
// 在这里,你可以根据接收到的消息更新页面或其他操作
};
// 发送消息到SharedWorker的函数
function sendMessage() {
port.postMessage('index页面发送消息!');
}
</script>
</body>
</html>
var ports = [];
self.onconnect = function(e) {
var port = e.ports[0];
// 将新端口添加到数组中
ports.push(port);
port.onmessage = function(e) {
var data = e.data;
console.log('Received message from port: ', data);
// 广播消息给所有端口(除了发送者)
ports.forEach(function(otherPort) {
if (otherPort !== port) {
otherPort.postMessage('sharedworkerjs接收到了信息并广播: ' + data);
}
});
};
// 当端口关闭时,从数组中移除它
port.onclose = function() {
var index = ports.indexOf(port);
if (index !== -1) {
ports.splice(index, 1);
}
};
// 示例:向所有端口发送消息
setInterval(function() {
ports.forEach(function(p) {
p.postMessage('Hello from the shared worker!');
});
}, 1000);
};
同一个页面开两个窗口,也是会支持,数据的同步和获取