你不会还不知道碉堡了的Websocekt把!

346 阅读8分钟

一、 前言

笔者在了解跨域的解决方法时,突然看到了websocket不受同源策略的影响,通过 WebSocket,前端和服务器可以建立实时连接,进行双向数据传输。就算全双工服务,相信大家都还记得计网里的全双工,半双工,单工。

二、WebSocket 基础概念

2.1 什么是 WebSocket

WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议。与传统的 HTTP 协议不同,WebSocket 允许客户端和服务器在连接建立后,随时向对方发送数据,实现真正的实时双向通信。它的出现使得浏览器和服务器之间的通信变得更加高效、便捷。

2.2 WebSocket 的优点

  • 实时性强:由于是全双工通信,服务器可以主动向客户端推送数据,无需客户端频繁发起请求,大大提高了数据传输的实时性。
  • 低开销:WebSocket 连接建立后,通信过程中只需要较少的头部信息,相比 HTTP 请求的多次往返,减少了数据传输的开销。
  • 跨域支持:在合理配置的情况下,WebSocket 可以很好地处理跨域通信问题,为不同域名之间的实时通信提供了便利。

三、WebSocket 工作原理

3.1 握手阶段

WebSocket 连接的建立基于 HTTP 协议。客户端首先发送一个带有特殊头部信息的 HTTP 请求,告知服务器它想要升级为 WebSocket 连接。请求中包含一些关键信息,如 Connection: UpgradeUpgrade: websocket 等。服务器接收到请求后,如果支持 WebSocket 协议,会返回一个状态码为 101 的响应,表示同意升级协议,从而完成握手过程,建立 WebSocket 连接。

3.2 数据传输阶段

握手成功后,客户端和服务器就可以在同一个 TCP 连接上进行双向的数据传输。数据以帧的形式进行传输,WebSocket 协议定义了不同类型的帧,如文本帧、二进制帧等,以满足不同的数据传输需求。

3.3 关闭连接阶段

当通信结束时,客户端或服务器可以发送关闭帧来关闭 WebSocket 连接。接收到关闭帧的一方会发送一个确认关闭帧,然后双方关闭 TCP 连接。

四、前端(浏览器环境)使用 WebSocket

4.1 创建 WebSocket 连接

在浏览器环境中,使用 new WebSocket() 来创建 WebSocket 连接,它接受一个必需参数和一个可选参数。

语法
const socket = new WebSocket(url, [protocols]);
参数解释
  • url(必需) :这是一个字符串,表示要连接的 WebSocket 服务器的地址。该地址必须使用 ws:// 或 wss:// 协议前缀,其中 ws:// 用于非加密连接,类似于 HTTP;wss:// 用于加密连接,类似于 HTTPS。

    • 示例:

// 非加密连接
const socket = new WebSocket('ws://example.com:8080'); 
// 加密连接
const secureSocket = new WebSocket('wss://secure-example.com:443'); 
  • protocols(可选) :这是一个字符串或字符串数组,用于指定子协议。子协议允许在 WebSocket 连接上使用不同的应用层协议,服务器可以根据客户端请求的子协议选择合适的处理方式。如果提供多个子协议,服务器会从列表中选择一个支持的协议并在握手响应中返回给客户端。

    • 示例:

// 单个子协议
const socketWithProtocol = new WebSocket('ws://example.com:8080', 'my - protocol'); 
// 多个子协议
const socketWithMultipleProtocols = new WebSocket('ws://example.com:8080', ['protocol1', 'protocol2']); 

4.2 监听 WebSocket 事件

创建 WebSocket 连接后,我们可以监听不同的事件来处理连接状态和接收数据。

const socket = new WebSocket('ws://example.com:8080');

// 连接成功事件
socket.onopen = function () {
    console.log('WebSocket 连接已建立');
    socket.send('Hello from client');
};

// 接收消息事件
socket.onmessage = function (event) {
    console.log('收到服务器消息:', event.data);
};

// 连接关闭事件
socket.onclose = function () {
    console.log('WebSocket 连接已关闭');
};

// 连接错误事件
socket.onerror = function (error) {
    console.error('WebSocket 连接错误:', error);
};

五、后端(以 Node.js 的 ws 库为例)使用 WebSocket

5.1 创建服务器实例

在 Node.js 中,使用 ws 库创建 WebSocket 服务器时,创建服务器实例和处理客户端连接的方式与前端不同。

方式一:直接指定端口
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
方式二:绑定到现有的 HTTP 服务器
const http = require('http');
const server = http.createServer();
const wss = new WebSocket.Server({ server });
server.listen(8080);

5.2 参数解释

在创建 WebSocket.Server 实例时,传递的是一个配置对象,常用参数如下:

  • port:指定 WebSocket 服务器监听的端口号。如果使用这种方式创建服务器,它会自动创建一个 HTTP 服务器并监听指定端口。

    • 示例:
const wss = new WebSocket.Server({ port: 8080 });
  • server:绑定到现有的 HTTP 服务器实例。这种方式适用于需要在同一个端口上同时处理 HTTP 请求和 WebSocket 连接的场景。

    • 示例:

const http = require('http');
const server = http.createServer();
const wss = new WebSocket.Server({ server });
server.listen(8080);
  • path:可选参数,指定 WebSocket 服务器监听的路径。只有客户端请求的路径与该参数匹配时,才会建立 WebSocket 连接。

    • 示例:
const wss = new WebSocket.Server({ port: 8080, path: '/ws' });
  • verifyClient:可选参数,是一个函数,用于在客户端连接时进行验证。该函数接收一个包含客户端信息的对象作为参数,可以根据需要对客户端进行身份验证、权限检查等操作,并返回一个布尔值表示是否允许客户端连接。

    • 示例:

const wss = new WebSocket.Server({
    port: 8080,
    verifyClient: function (info) {
        // info 包含客户端连接的相关信息,如 origin、req 等
        return info.origin === 'http://example.com'; 
    }
});

5.3 处理客户端连接和消息

const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });

wss.on('connection', function (ws) {
    console.log('新的 WebSocket 连接');

    ws.on('message', function (message) {
        console.log('收到客户端消息:', message);
        ws.send('Hello from server');
    });

    ws.on('close', function () {
        console.log('WebSocket 连接已关闭');
    });

    ws.on('error', function (error) {
        console.error('WebSocket 连接错误:', error);
    });
});

他和HTTP握手的区别

握手过程

  • HTTP 握手:客户端发送一个 HTTP 请求到服务器,请求包含请求行(如 GET /index.html HTTP/1.1)、请求头(如 User - AgentAccept 等)和可选的请求体。服务器接收到请求后,解析请求信息,然后返回一个 HTTP 响应,响应包含状态行(如 HTTP/1.1 200 OK)、响应头(如 Content - TypeContent - Length 等)和响应体(如 HTML 页面内容)。
  • WebSocket 握手:WebSocket 握手基于 HTTP 协议,客户端首先发送一个特殊的 HTTP 请求,请求中包含一些特定的头部信息,表明它希望将连接升级为 WebSocket 连接。服务器接收到请求后,检查这些头部信息,如果支持 WebSocket 协议,就会返回一个状态码为 101 的响应,表示同意升级协议。之后,连接就从 HTTP 连接转换为 WebSocket 连接。

Websocket如何维持长连接?

1. WebSocket的心跳机制

目的
  • 检测客户端与服务器之间的连接是否仍然有效。
  • 防止因网络空闲导致连接被中间设备(如防火墙、代理服务器)关闭。
实现方式

心跳机制通常是通过定期发送“心跳包”来检测连接状态。心跳包可以是自定义的消息类型或特定的数据格式。

常见流程
  1. 客户端定时向服务器发送心跳请求(例如:{"type": "ping"})。
  2. 服务器接收到心跳请求后,返回心跳响应(例如:{"type": "pong"})。
  3. 如果客户端在一定时间内未收到服务器的响应,则认为连接已断开,触发断线重连逻辑。
注意事项
  • 心跳间隔时间不宜过短,否则会增加网络负担;也不宜过长,否则可能导致问题发现延迟。通常建议设置为20秒~60秒
  • 心跳消息的内容可以根据业务需求自定义,但应尽量简洁,减少带宽占用。

2. WebSocket的断线重连

目的
  • 当WebSocket连接意外中断时,自动重新建立连接,确保通信不中断。
实现方式

断线重连可以通过监听WebSocket的onclose事件来触发。以下是常见的实现步骤:

基本逻辑
  1. 监听关闭事件

    • 在WebSocket实例中绑定onclose事件处理函数。
    • 当检测到连接关闭时,启动重连逻辑。
  2. 设置重连间隔

    • 使用指数退避算法(Exponential Backoff)动态调整重连间隔时间,避免频繁重连失败导致资源浪费。
    • 示例:首次重连等待1秒,第二次等待2秒,第三次等待4秒,依此类推。
  3. 限制重连次数(可选)

    • 设置最大重连次数,防止无限重连导致性能问题。
    • 如果超过最大重连次数仍未成功,可以提示用户手动刷新页面或采取其他措施。
  4. 重连成功后的回调

    • 重连成功后,重新订阅相关数据或恢复之前的状态。
代码实例

以下是一个简单的WebSocket心跳机制和断线重连的实现:

class WebSocketClient {
    constructor(url, reconnectLimit = 5) {
        this.url = url;
        this.socket = null;
        this.isReconnecting = false;
        this.reconnectCount = 0;
        this.reconnectLimit = reconnectLimit;
        this.pingInterval = null;
        this.reconnectInterval = null;
    }

    connect() {
        this.socket = new WebSocket(this.url);

        this.socket.onopen = () => {
            console.log('WebSocket connection established.');
            this.startPing();
            this.resetReconnectCount();
        };

        this.socket.onmessage = (event) => {
            const data = JSON.parse(event.data);
            if (data.type === 'pong') {
                console.log('Received pong from server.');
            }
        };

        this.socket.onclose = () => {
            console.log('WebSocket connection closed.');
            this.stopPing();
            this.reconnect();
        };

        this.socket.onerror = (error) => {
            console.error('WebSocket error:', error);
        };
    }

    startPing() {
        this.pingInterval = setInterval(() => {
            if (this.socket.readyState === WebSocket.OPEN) {
                this.socket.send(JSON.stringify({ type: 'ping' }));
                console.log('Sent ping to server.');
            }
        }, 30000); // 每30秒发送一次心跳
    }

    stopPing() {
        clearInterval(this.pingInterval);
    }

    resetReconnectCount() {
        this.reconnectCount = 0;
        clearInterval(this.reconnectInterval);
    }

    reconnect() {
        if (this.reconnectCount >= this.reconnectLimit) {
            console.error('Max reconnect attempts reached.');
            return;
        }

        this.reconnectCount++;
        const delay = Math.pow(2, this.reconnectCount) * 1000; // 指数退避算法

        console.log(`Reconnecting in ${delay / 1000} seconds...`);
        this.reconnectInterval = setTimeout(() => {
            this.connect();
        }, delay);
    }
}

// 使用示例
const wsClient = new WebSocketClient('wss://example.com/socket');
wsClient.connect();

总结

  • 心跳机制用于检测连接的有效性,防止因网络空闲导致连接被关闭。
  • 断线重连用于在网络异常或连接中断时恢复通信。
  • 结合心跳机制和断线重连,可以显著提升WebSocket应用的可靠性和用户体验。

END

希望大家看完能把自己的心得输入到评论区中,加深印象,理解Websocket!