一、WebSocket基本概念

WebSocket是一种网络通信协议,提供 全双工(full-duplex)通信通道,允许客户端和服务器之间进行实时双向数据传输。与传统的HTTP请求-响应模式不同,WebSocket建立连接后保持持久连接,双方可以随时发送数据。

核心特点
  • 双向通信:服务器和客户端可以随时发送消息。

  • 持久连接:一次握手后保持连接状态,减少开销。

  • 低延迟:避免HTTP请求的头部开销和连接建立时间。

  • 二进制支持:可传输文本和二进制数据。

  • 单一TCP连接:所有通信通过同一连接进行。

  • 轻量级:数据帧头部较小(2-14字节)。

二、WebSocket 协议基础

握手过程:
  1. 客户端发送HTTP请求,包含WebSocket升级头信息。

  2. 服务器响应HTTP 101状态码,表示协议切换。

  3. 建立TCP连接,后续通信使用WebSocket协议。

# 客户端请求
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13

# 服务器响应
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

三、WebSocket与HTTP的区别

特性

HTTP

WebSocket

通信模式

请求-响应

全双工

连接类型

短连接

长连接

头部开销

每次请求都有完整头部

仅握手时有头部

实时性

低(轮询/长轮询)

服务器推送

不支持(需客户端主动请求)

原生支持

协议标识符

http:// 或 https://

ws:// 或 wss://

四、JavaScript WebSocket API

WebSocket示例

服务端:

const WebSocket = require('ws');

// 创建WebSocket服务器,监听端口8080
const wss = new WebSocket.Server({ port: 8080 });

console.log('WebSocket server is running on ws://localhost:8080');

// 客户端连接事件
wss.on('connection', (ws) => {
    console.log('A new client connected');

    // 发送欢迎消息给新客户端
    ws.send('Welcome to the WebSocket server!');

    // 接收客户端消息事件
    ws.on('message', (message) => {
        console.log(`Received: ${message}`);

        // 广播消息给所有连接的客户端
        wss.clients.forEach((client) => {
            if (client.readyState === WebSocket.OPEN) {
                client.send(`Client: ${message}`);
            }
        });
    });

    // 客户端断开连接事件
    ws.on('close', () => {
        console.log('A client disconnected');
    });

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

客户端:

<div id="messages"></div>
<input type="text" id="input" placeholder="输入消息...">
<button onclick="sendMessage()">发送</button>
<button onclick="closeConnection()">关闭连接</button>
<button onclick="connect()">重新连接</button>

<script>
    let ws;
    const messagesDiv = document.getElementById('messages');
    const input = document.getElementById('input');

    // 连接WebSocket服务器
    function connect() {
        // 创建WebSocket连接
        ws = new WebSocket('ws://localhost:8080');

        // 连接打开事件
        ws.onopen = () => {
            addMessage('已连接到服务器');
        };

        // 接收消息事件
        ws.onmessage = (event) => {
            addMessage(`服务器: ${event.data}`);
        };

        // 连接关闭事件
        ws.onclose = () => {
            addMessage('与服务器的连接已关闭');
        };

        // 错误处理
        ws.onerror = (error) => {
            addMessage(`连接错误: ${error}`);
        };
    }

    // 发送消息
    function sendMessage() {
        const message = input.value;
        if (message && ws && ws.readyState === WebSocket.OPEN) {
            ws.send(message);
            addMessage(`我: ${message}`);
            input.value = '';
        } else {
            addMessage('无法发送消息,请检查连接');
        }
    }

    // 关闭连接
    function closeConnection() {
        if (ws) {
            ws.close();
        }
    }

    // 添加消息到显示区域
    function addMessage(message) {
        const div = document.createElement('div');
        div.textContent = new Date().toLocaleTimeString() + ': ' + message;
        messagesDiv.appendChild(div);
        messagesDiv.scrollTop = messagesDiv.scrollHeight;
    }

    // 页面加载时自动连接
    window.onload = connect;

    // 回车键发送消息
    input.addEventListener('keypress', (e) => {
        if (e.key === 'Enter') {
            sendMessage();
        }
    });
</script>

五、WebSocket事件、属性和方法

WebSocket 事件

事件名称

描述

事件处理程序属性

open

连接成功建立时触发

onopen

message

客户端接收服务端数据时触发

onmessage

error

通信发生错误时触发

onerror

close

连接关闭时触发

onclose

WebSocket 属性

属性名称

类型

描述

readyState

number

当前连接状态:

  • 0 (CONNECTING):连接正在建立

  • 1 (OPEN):连接已建立

  • 2 (CLOSING):连接正在关闭

  • 3 (CLOSED):连接已关闭

bufferedAmount

number

已发送但尚未被服务器确认的字节数

extensions

string

服务器选择的扩展

protocol

string

服务器选择的子协议

url

string

WebSocket 服务器的 URL

binaryType

string

接收二进制数据的类型,可以是 "blob" 或 "arraybuffer"

WebSocket 方法

方法名称

描述

send(data)

向服务器发送数据

close([code[, reason]])

关闭 WebSocket 连接

事件示例(客户端)
// 创建 WebSocket 连接
const socket = new WebSocket('ws://example.com/socket');

// 连接建立时触发
socket.onopen = function(event) {
  console.log('WebSocket 连接已建立');
  socket.send('Hello Server!');
};

// 接收消息时触发
socket.onmessage = function(event) {
  console.log('收到消息:', event.data);
};

// 发生错误时触发
socket.onerror = function(error) {
  console.error('WebSocket 错误:', error);
};

// 连接关闭时触发
socket.onclose = function(event) {
  console.log('WebSocket 连接已关闭:', event.code, event.reason);
};
方法示例(客户端)
// 发送文本消息
socket.send('Hello World');

// 发送二进制数据
const buffer = new ArrayBuffer(16);
const view = new Uint8Array(buffer);
for (let i = 0; i < view.length; i++) {
  view[i] = i;
}
socket.send(buffer);

// 关闭连接
socket.close(1000, '正常关闭'); // 1000为正常关闭代码

六、最佳实践与注意事项

连接管理示例(客户端)
// 自动重连机制
function connectWebSocket() {
  const socket = new WebSocket('ws://example.com/socket');
  
  socket.onclose = function() {
    console.log('连接关闭,5秒后重连...');
    setTimeout(connectWebSocket, 5000);
  };
  
  return socket;
}

let socket = connectWebSocket();
消息处理示例(客户端)
// 分片消息处理
let messageBuffer = '';

socket.onmessage = function(event) {
  if (event.data instanceof Blob) {
    // 处理二进制数据
    const reader = new FileReader();
    reader.onload = function() {
      processMessage(reader.result);
    };
    reader.readAsText(event.data);
  } else {
    // 处理文本数据
    processMessage(event.data);
  }
};

function processMessage(data) {
  // 解析JSON消息
  try {
    const message = JSON.parse(data);
    // 根据消息类型处理
    switch(message.type) {
      case 'chat':
        displayChatMessage(message);
        break;
      case 'update':
        updateData(message);
        break;
      // 其他消息类型
    }
  } catch (error) {
    console.error('消息解析错误:', error);
  }
}
心跳机制示例(客户端)
// 心跳配置
const HEARTBEAT_INTERVAL = 30000; // 30秒
let heartbeatTimer;

// 启动心跳
socket.onopen = function() {
  startHeartbeat();
};

function startHeartbeat() {
  heartbeatTimer = setInterval(() => {
    if (socket.readyState === 1) {
      socket.send(JSON.stringify({ type: 'ping' }));
    }
  }, HEARTBEAT_INTERVAL);
}

// 重置心跳
socket.onmessage = function(event) {
  const message = JSON.parse(event.data);
  if (message.type === 'pong') {
    // 收到服务器回应,重置心跳
    clearInterval(heartbeatTimer);
    startHeartbeat();
  }
};

// 关闭时清除心跳
socket.onclose = function() {
  clearInterval(heartbeatTimer);
};
输入验证与过滤(客户端)
// 消息验证
function validateMessage(message) {
    // 检查消息大小
    if (message.length > 10000) {
        throw new Error('消息过长');
    }
    
    // 验证消息格式
    const data = JSON.parse(message);
    
    if (!data.type || !data.payload) {
        throw new Error('无效的消息格式');
    }
    
    // 防止XSS攻击
    const sanitize = (str) => {
        return str.replace(/[<>]/g, '');
    };
    
    if (typeof data.payload === 'string') {
        data.payload = sanitize(data.payload);
    }
    
    return data;
}
二进制数据传输(客户端)
// 使用二进制传输大型数据
function sendBinaryData(data) {
    if (typeof data === 'string') {
        // 字符串编码
        const encoder = new TextEncoder();
        const buffer = encoder.encode(data);
        socket.send(buffer);
    } else if (data instanceof ArrayBuffer) {
        socket.send(data);
    } else if (data instanceof Blob) {
        socket.send(data);
    }
}

// 接收二进制数据
socket.binaryType = 'arraybuffer'; // 或 'blob'
socket.onmessage = (event) => {
    if (event.data instanceof ArrayBuffer) {
        const decoder = new TextDecoder();
        const text = decoder.decode(event.data);
        console.log('解码后的文本:', text);
    }
};

WebSocket为Web应用提供了实时双向通信能力,大幅提升了用户体验。JavaScript的WebSocket API简洁易用,结合适当的连接管理、消息处理和安全措施,可以构建高效可靠的实时应用。

彩蛋

完整示例代码,请移步Cnb: 《WebSocket完整示例