1. WebSocketHTML5 定义的全双工、持久化网络通信协议,基于 TCP 连接,允许客户端与服务器之间实时双向数据传输;
  2. 相比 HTTP 1.1“请求 - 响应” 模式 (单向、无状态、需频繁建连)WebSocket 仅需一次握手建立连接,之后双方可随时发送数据,延迟更低、带宽占用更少,适用于实时聊天、直播弹幕、协同编辑、实时数据监控等场景;

WebSocket 核心特性

  1. 全双工通信:客户端和服务器可 同时发送 / 接收数据,无需等待对方响应;

  2. 持久连接:一次 TCP 握手后保持连接,避免 HTTP 重复建连的开销;

  3. 轻量协议:帧头部仅 2-14 字节 (HTTP 头部通常数百字节),数据传输效率高;

  4. 跨域支持:原生支持跨域通信,无需额外配置 (如 CORS)

  5. 基于 TCP:依赖 TCP 可靠性 (有序、无丢失、无重复),但需自行处理重连、心跳等场景;

  6. 文本 / 二进制兼容:支持传输 UTF-8 文本 (如 JSON) 和二进制数据 (如图片、视频流)

WebSocket 协议流程

握手阶段(HTTP 升级请求)

  1. WebSocket 连接建立依赖 HTTP 握手升级,客户端先发送 HTTP 请求,告知服务器 “要切换到 WebSocket 协议”

  2. 客户端请求头 (关键字段)

    GET /ws-endpoint HTTP/1.1
    Host: example.com
    Connection: Upgrade  # 表示要升级协议
    Upgrade: websocket  # 目标协议为 WebSocket
    Sec-WebSocket-Version: 13  # 必须为 13(标准版本)
    Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==  # 随机字符串(用于服务器验证)
    Sec-WebSocket-Protocol: chat, game  # 可选:协商子协议(如聊天、游戏)
    
  3. 服务器响应头 (关键字段)

    HTTP/1.1 101 Switching Protocols  # 状态码 101 表示协议切换成功
    Connection: Upgrade
    Upgrade: websocket
    Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=  # 由 Sec-WebSocket-Key 计算得出(验证握手合法性)
    Sec-WebSocket-Protocol: chat  # 可选:确认最终使用的子协议
    

数据传输阶段

握手成功后,HTTP 连接升级为 WebSocket 连接,双方通过帧 (Frame) 传输数据:

  1. 帧结构包含:操作码 (OPCODE)、掩码位、数据长度、载荷数据;
  2. 关键操作码:
    1. 0x00:继续帧 (分片数据的后续帧)
    2. 0x01:文本帧 (UTF-8 编码)
    3. 0x02:二进制帧;
    4. 0x08:关闭帧 (主动关闭连接)
    5. 0x09:Ping 帧 (心跳检测,需响应 Pong)
    6. 0x0A:Pong 帧 (响应 Ping)

关闭阶段

  1. 任意一方发送 0x08 关闭帧,对方响应后断开 TCP 连接 (优雅关闭)

  2. 若超时未响应,直接断开连接 (强制关闭)

客户端 API 详解(浏览器原生)

浏览器内置 WebSocket 构造函数,无需依赖第三方库,API 简洁易用;

建立连接

if (typeof window.WebSocket == 'function') { // 检测一下浏览器是否支持 Websocket
    // 语法:new WebSocket(url[, protocols])
    // url:WebSocket 服务地址(ws:// 非加密,wss:// 加密,类似 http/https)
    // protocols:可选,子协议数组(如 ["chat", "json"]),需服务器支持
    const ws = new WebSocket('wss://example.com/ws-endpoint');
} else {
    alert("您的浏览器不支持 websocket");
}

核心事件(回调函数)

  1. WebSocket 实例通过事件监听处理连接状态、数据收发,常用事件如下:

    事件名 触发时机 回调参数 说明
    open 连接建立成功时 event (无关键数据) 仅触发一次
    message 收到服务器发送的数据时 event (data 字段为数据) 数据类型:String/Blob/ArrayBuffer
    error 连接出错时 (如网络中断、协议错误) event 出错后连接会关闭
    close 连接关闭时 (主动关闭 / 异常关闭) event (code/reason) code:关闭码,reason:原因
  2. 事件监听示例:

    // 1. 连接成功
    ws.onopen = () => {
      console.log('WebSocket 连接已建立');
      // 连接成功后可立即发送数据
      ws.send('Hello, Server!');
    };
    
    // 2. 接收服务器数据
    ws.onmessage = (event) => {
      // 数据类型判断(根据服务器发送的格式处理)
      if (typeof event.data === 'string') {
        console.log('收到文本数据:', event.data);
      } else if (event.data instanceof Blob) {
        console.log('收到二进制 Blob:', event.data);
        // 如需解析 Blob(如图片),可通过 FileReader 转换
        const reader = new FileReader();
        reader.onload = (e) => console.log('Blob 解析结果:', e.target.result);
        reader.readAsDataURL(event.data);
      } else if (event.data instanceof ArrayBuffer) {
        console.log('收到 ArrayBuffer:', event.data);
      }
    };
    
    // 3. 连接出错
    ws.onerror = (event) => {
      console.error('WebSocket 错误:', event);
    };
    
    // 4. 连接关闭
    ws.onclose = (event) => {
      console.log(`连接关闭:状态码 ${event.code},原因 ${event.reason}`);
      // 常见关闭码:1000(正常关闭)、1001(客户端/服务器离开)、1006(异常关闭)
    };
    

核心方法

  1. 发送数据:ws.send(data)

    1. 支持发送文本、二进制数据 (Blob / ArrayBuffer / ArrayBufferView)
    2. 注意:send() 仅在 open 事件触发后调用 (连接建立前调用会报错)
    // 1. 发送文本(最常用,如 JSON 字符串)
    ws.send(JSON.stringify({ type: 'chat', content: 'Hello' }));
    
    // 2. 发送 Blob(如文件)
    const fileInput = document.getElementById('file-input');
    const file = fileInput.files[0];
    if (file) ws.send(file);
    
    // 3. 发送 ArrayBuffer(二进制数据)
    const buffer = new ArrayBuffer(4);
    const view = new Uint8Array(buffer);
    view.set([0x10, 0x20, 0x30, 0x40]);
    ws.send(buffer);
    
  2. 关闭连接:ws.close([code[, reason]])

    1. 主动关闭连接,可选参数:
      • code:关闭码 (默认 1000,需符合 RFC 6455 标准)
      • reason:关闭原因 (字符串,UTF-8 编码,长度不超过 123 字节)
    // 正常关闭连接
    ws.close(1000, '客户端主动断开连接');
    

核心属性

属性名 类型 说明
readyState 数字 连接状态 (0:CONNECTING,1:OPEN,2:CLOSING,3:CLOSED)
bufferedAmount 数字 已发送但未被服务器确认的字节数 (判断发送队列是否拥堵)
protocol 字符串 最终协商的子协议 (如 “chat”)
url 字符串 连接的 WebSocket 服务地址
// 判断连接状态
if (ws.readyState === WebSocket.OPEN) {
  console.log('连接正常,可发送数据');
} else if (ws.readyState === WebSocket.CONNECTING) {
  console.log('连接正在建立中...');
}

服务器端实现 node

const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 }); // 监听 8080 端口

// 新客户端连接时触发
wss.on('connection', (ws) => {
  console.log('新客户端连接');

  // 接收客户端数据
  ws.on('message', (data) => {
    console.log('收到客户端数据:', data.toString());
    // 回复客户端(支持广播:wss.clients.forEach 遍历所有连接)
    ws.send(`服务器回复:${data.toString()}`);
  });

  // 客户端断开连接
  ws.on('close', (code, reason) => {
    console.log(`客户端断开:${code} - ${reason}`);
  });

  // 错误处理
  ws.on('error', (err) => {
    console.error('服务器错误:', err);
  });
});

关键实战场景与解决方案

心跳检测(防止连接被网关断开)

  1. 多数网关 (如 Nginx) 会断开长时间无数据传输的连接,需通过 Ping/Pong 帧 保持连接;

  2. 示例代码:

    // 客户端心跳逻辑
    let pingInterval;
    
    ws.onopen = () => {
      // 每 30 秒发送一次 Ping
      pingInterval = setInterval(() => {
        if (ws.readyState === WebSocket.OPEN) {
          ws.send(JSON.stringify({ type: 'ping' })); // 自定义 Ping 消息(或用原生 Ping 帧)
          // 原生 Ping 帧(部分浏览器支持,无需手动序列化)
          // ws.ping();
        }
      }, 30000);
    };
    
    ws.onclose = () => {
      clearInterval(pingInterval); // 关闭时清除定时器
    };
    
    // 服务器收到 Ping 后回复 Pong
    // Node.js 示例(ws 库)
    ws.on('message', (data) => {
      const msg = JSON.parse(data);
      if (msg.type === 'ping') {
        ws.send(JSON.stringify({ type: 'pong' }));
      }
    });
    

自动重连(网络中断后恢复)

  1. 连接异常关闭时,客户端需自动重试连接 (避免用户手动刷新)

  2. 示例代码:

    let ws;
    let reconnectInterval;
    const MAX_RETRY = 10; // 最大重连次数
    let retryCount = 0;
    
    // 初始化连接
    function initWebSocket() {
      ws = new WebSocket('wss://example.com/ws-endpoint');
    
      ws.onopen = () => {
        console.log('连接成功');
        retryCount = 0; // 重置重连次数
        clearInterval(reconnectInterval);
      };
    
      ws.onclose = () => {
        console.log('连接关闭,尝试重连...');
        if (retryCount < MAX_RETRY) {
          retryCount++;
          // 指数退避重连(1s、2s、4s...,避免频繁重试)
          reconnectInterval = setTimeout(initWebSocket, 1000 * Math.pow(2, retryCount));
        } else {
          console.log('重连失败,请刷新页面');
        }
      };
    
      // 其他事件(message/error)...
    }
    
    // 启动连接
    initWebSocket();
    

跨域处理

WebSocket 原生支持跨域,无需额外配置,但需注意:

  1. 服务器需正确响应 Sec-WebSocket-Accept 头部;
  2. 若客户端指定 Sec-WebSocket-Protocol,服务器需返回匹配的子协议;
  3. 部分旧版服务器可能需配置允许跨域源 (如 Nginx 转发时无需额外处理)

安全考量

  1. 优先使用 wss:// (加密传输,类似 HTTPS),避免数据被窃听或篡改;

  2. 握手时可添加身份验证 (如在 URL 中携带 Token:wss://example.com/ws?token=xxx,服务器验证 Token 合法性)

  3. 限制单客户端连接数,防止恶意连接攻击;

  4. 对接收的数据进行校验 (如 JSON 格式、数据长度),避免注入攻击;

WebSocket 与 HTTP/2、SSE 的区别

特性 WebSocket HTTP/2 SSE(Server-Sent Events)
通信方向 全双工 (双向) 全双工 (但基于请求 - 响应) 单工 (服务器→客户端)
连接类型 持久 TCP 连接 持久 TCP 连接 持久 HTTP 连接
适用场景 实时聊天、弹幕、协同编辑 多路复用 HTTP 请求 实时通知、数据推送 (如股票行情)
客户端兼容性 所有现代浏览器 (IE10+) 现代浏览器 (IE 不支持) 现代浏览器 (IE 不支持)
服务器复杂度 中等 (需帧解析) (需 TLS 支持) (基于 HTTP 响应)
数据格式 文本 / 二进制 文本 / 二进制 仅文本 (UTF-8)

Socket.IO chat 案例

<!DOCTYPE html>
<html>

<head>
  <title>Socket.IO chat</title>
  <style>
    body {
      margin: 0;
      padding-bottom: 3rem;
      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
    }

    #form {
      background: rgba(0, 0, 0, 0.15);
      padding: 0.25rem;
      position: fixed;
      bottom: 0;
      left: 0;
      right: 0;
      display: flex;
      height: 3rem;
      box-sizing: border-box;
      backdrop-filter: blur(10px);
    }

    #input {
      border: none;
      padding: 0 1rem;
      flex-grow: 1;
      border-radius: 2rem;
      margin: 0.25rem;
    }

    #input:focus {
      outline: none;
    }

    #form>button {
      background: #333;
      border: none;
      padding: 0 1rem;
      margin: 0.25rem;
      border-radius: 3px;
      outline: none;
      color: #fff;
    }

    #messages {
      list-style-type: none;
      margin: 0;
      padding: 0;
    }

    #messages>li {
      padding: 0.5rem 1rem;
    }

    #messages>li:nth-child(odd) {
      background: #efefef;
    }
  </style>
</head>

<body>
  <ul id="messages"></ul>
  <form id="form" action="">
    <input id="input" autocomplete="off" /><button>Send</button>
  </form>
  <!-- socket.io(对原生的 WebSocket 进行了封装,使用起来更方便,客户端/服务端要同时使用) -->
  <script src="/socket.io/socket.io.js"></script>

  <script>
    var socket = io();

    var messages = document.getElementById('messages');
    var form = document.getElementById('form');
    var input = document.getElementById('input');

    form.addEventListener('submit', function(e) {
      e.preventDefault();
      if (input.value) {
        socket.emit('chat message', input.value);
        input.value = '';
      }
    });

    socket.on('chat message', function(msg) {
      var item = document.createElement('li');
      item.textContent = msg;
      messages.appendChild(item);
      window.scrollTo(0, document.body.scrollHeight);
    });
  </script>
</body>

</html>
const app = require('express')();
const http = require('http').Server(app);
const io = require('socket.io')(http);
const port = process.env.PORT || 3000;

app.get('/', (req, res) => {
    res.sendFile(__dirname + '/index.html'); // http://localhost:3000
});

io.on('connection', (socket) => {
    socket.on('chat message', msg => {
        io.emit('chat message', msg);
    });
});

http.listen(port, () => {
    console.log(`Socket.IO server running at http://localhost:${port}/`);
});
打赏作者
您的打赏是我前进的动力
微信
支付宝
评论

你好👏🏻,我是 ✍🏻   疯狂 codding 中...

粽子

这有关于前端开发的技术文档和你分享。

相信你可以在这里找到对你有用的知识和教程。

了解更多

目录

  1. 1. WebSocket 核心特性
  2. 2. WebSocket 协议流程
    1. 2.1. 握手阶段(HTTP 升级请求)
    2. 2.2. 数据传输阶段
    3. 2.3. 关闭阶段
  3. 3. 客户端 API 详解(浏览器原生)
    1. 3.1. 建立连接
    2. 3.2. 核心事件(回调函数)
    3. 3.3. 核心方法
    4. 3.4. 核心属性
  4. 4. 服务器端实现 node
  5. 5. 关键实战场景与解决方案
    1. 5.1. 心跳检测(防止连接被网关断开)
    2. 5.2. 自动重连(网络中断后恢复)
    3. 5.3. 跨域处理
    4. 5.4. 安全考量
  6. 6. WebSocket 与 HTTP/2、SSE 的区别
  7. 7. Socket.IO chat 案例