# 标签页通信

# 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

  1. SharedWorker地址注意最好不是本地地址
  2. 在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);  
};

同一个页面开两个窗口,也是会支持,数据的同步和获取

最后更新: 8/8/2024, 2:30:12 PM