前后端实现websocket通信

128 阅读6分钟

前后端WebSocket通信:实时交互的桥梁 引言 在现代Web应用中,实时通信已成为许多应用的核心需求,从即时聊天、在线协作到实时数据更新,用户期望获得无缝的交互体验。传统的HTTP协议由于其请求-响应的无状态特性,难以高效地满足这些实时性要求。WebSocket技术的出现,为前后端之间的全双工实时通信提供了完美的解决方案。

一、WebSocket基础概念

  1. 什么是WebSocket WebSocket是一种在单个TCP连接上进行全双工通信的协议,它使得客户端和服务器之间能够建立持久连接,并进行双向数据传输。WebSocket协议由HTML5规范引入,旨在解决HTTP协议在实时通信方面的局限性。

  2. WebSocket与HTTP的区别 ​​连接方式​​:HTTP是无状态的请求-响应模式,每次通信都需要建立新的连接;WebSocket建立一次连接后保持持久连接 ​​通信方向​​:HTTP主要是服务器响应客户端请求;WebSocket支持全双工通信,双方可以随时主动发送消息 ​​头部开销​​:HTTP每次请求都携带大量头部信息;WebSocket连接建立后数据帧头部很小 ​​实时性​​:HTTP轮询方式延迟高;WebSocket实现真正的实时通信 二、WebSocket工作原理

  3. 握手过程 WebSocket连接始于HTTP握手,客户端发起一个特殊的HTTP请求:

GET /chat HTTP/1.1 Host: 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= 这个过程称为"协议升级",一旦握手成功,连接就从HTTP协议升级为WebSocket协议。

  1. 数据传输 握手完成后,客户端和服务器之间就可以通过WebSocket协议进行数据传输。WebSocket数据帧包含以下主要部分:

操作码(Opcode):标识帧类型(文本、二进制等) 掩码(Mask):用于客户端到服务器的数据掩码 负载数据(Payload data):实际传输的数据内容 三、前端WebSocket实现

  1. 基本使用 现代浏览器提供了原生的WebSocket API,使用简单直观:

// 创建WebSocket连接 const socket = new WebSocket('ws://localhost:8080');

// 连接打开时触发 socket.onopen = function(event) { console.log('WebSocket连接已建立'); // 发送消息到服务器 socket.send('Hello, Server!'); };

// 接收消息时触发 socket.onmessage = function(event) { console.log('收到服务器消息:', event.data); // 处理服务器发送的数据 };

// 连接关闭时触发 socket.onclose = function(event) { if (event.wasClean) { console.log(连接已关闭,代码=${event.code} 原因=${event.reason}); } else { console.log('连接意外中断'); } };

// 发生错误时触发 socket.onerror = function(error) { console.error('WebSocket错误:', error); }; 2. 实际应用示例 ​​实时聊天应用:​​

class ChatApp { constructor() { this.socket = null; this.init(); }

init() { this.socket = new WebSocket('ws://localhost:8080/chat');

this.socket.onopen = () => {
  console.log('已连接到聊天服务器');
  this.updateStatus('已连接');
};

this.socket.onmessage = (event) => {
  const message = JSON.parse(event.data);
  this.displayMessage(message);
};

this.socket.onclose = () => {
  console.log('与聊天服务器断开连接');
  this.updateStatus('连接已断开');
  // 尝试重连
  setTimeout(() => this.init(), 3000);
};

}

sendMessage(text) { if (this.socket && this.socket.readyState === WebSocket.OPEN) { const message = { type: 'chat', content: text, timestamp: new Date().toISOString(), userId: this.getUserId() }; this.socket.send(JSON.stringify(message)); } else { console.log('WebSocket未连接'); } }

// 其他方法... } 四、后端WebSocket实现

  1. Node.js + ws库实现 const WebSocket = require('ws');

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

// 存储所有连接的客户端 const clients = new Set();

wss.on('connection', function connection(ws) { console.log('新的客户端已连接'); clients.add(ws);

// 接收客户端消息 ws.on('message', function incoming(message) { console.log('收到消息:', message.toString());

try {
  const data = JSON.parse(message);
  
  // 广播消息给所有客户端
  clients.forEach(client => {
    if (client !== ws && client.readyState === WebSocket.OPEN) {
      client.send(JSON.stringify({
        ...data,
        timestamp: new Date().toISOString()
      }));
    }
  });
} catch (error) {
  console.error('解析消息错误:', error);
}

});

// 客户端断开连接 ws.on('close', function close() { console.log('客户端已断开连接'); clients.delete(ws); });

// 发送欢迎消息 ws.send(JSON.stringify({ type: 'system', content: '欢迎连接到聊天服务器!', timestamp: new Date().toISOString() })); });

console.log('WebSocket服务器运行在 ws://localhost:8080'); 2. Spring Boot实现 import org.springframework.web.socket.*; import org.springframework.web.socket.handler.TextWebSocketHandler; import org.springframework.context.annotation.Configuration; import org.springframework.web.socket.config.annotation.EnableWebSocket; import org.springframework.web.socket.config.annotation.WebSocketConfigurer; import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;

import java.util.concurrent.CopyOnWriteArrayList;

@Configuration @EnableWebSocket public class WebSocketConfig implements WebSocketConfigurer {

@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
    registry.addHandler(new ChatWebSocketHandler(), "/chat")
            .setAllowedOrigins("*"); // 生产环境应设置具体域名
}

}

class ChatWebSocketHandler extends TextWebSocketHandler {

private final CopyOnWriteArrayList<WebSocketSession> sessions = new CopyOnWriteArrayList<>();

@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
    sessions.add(session);
    System.out.println("新连接建立: " + session.getId());
    
    // 发送欢迎消息
    session.sendMessage(new TextMessage(
        "{\"type\":\"system\",\"content\":\"欢迎连接到聊天服务器!\"}"
    ));
}

@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
    System.out.println("收到消息: " + message.getPayload());
    
    // 广播消息给所有连接的客户端
    for (WebSocketSession webSocketSession : sessions) {
        if (webSocketSession.isOpen() && !webSocketSession.getId().equals(session.getId())) {
            webSocketSession.sendMessage(message);
        }
    }
}

@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
    sessions.remove(session);
    System.out.println("连接关闭: " + session.getId() + ", 状态: " + status);
}

} 五、WebSocket高级应用

  1. 心跳机制 为保持连接活跃和检测连接状态,实现心跳机制:

// 前端心跳实现 class WebSocketWithHeartbeat { constructor(url) { this.url = url; this.socket = null; this.heartbeatInterval = 30000; // 30秒 this.heartbeatTimer = null; this.reconnectAttempts = 0; this.maxReconnectAttempts = 5;

this.connect();

}

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

this.socket.onopen = () => {
  console.log('WebSocket连接已建立');
  this.reconnectAttempts = 0;
  this.startHeartbeat();
};

this.socket.onclose = () => {
  console.log('WebSocket连接已关闭');
  this.stopHeartbeat();
  this.attemptReconnect();
};

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

}

startHeartbeat() { this.heartbeatTimer = setInterval(() => { if (this.socket && this.socket.readyState === WebSocket.OPEN) { this.socket.send(JSON.stringify({ type: 'heartbeat' })); } }, this.heartbeatInterval); }

stopHeartbeat() { if (this.heartbeatTimer) { clearInterval(this.heartbeatTimer); this.heartbeatTimer = null; } }

attemptReconnect() { if (this.reconnectAttempts < this.maxReconnectAttempts) { this.reconnectAttempts++; const delay = Math.min(1000 * this.reconnectAttempts, 10000); // 最大延迟10秒 console.log(尝试重连 (${this.reconnectAttempts}/${this.maxReconnectAttempts}),延迟 ${delay}ms);

  setTimeout(() => this.connect(), delay);
} else {
  console.log('达到最大重连次数,停止重连');
}

} } 2. 消息队列与重传机制 class ReliableWebSocket { constructor(url) { this.url = url; this.socket = null; this.messageQueue = []; this.pendingAcks = new Map(); this.messageIdCounter = 0;

this.connect();

}

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

this.socket.onopen = () => {
  console.log('连接已建立,发送队列中的消息');
  this.flushMessageQueue();
};

this.socket.onmessage = (event) => {
  const message = JSON.parse(event.data);
  
  if (message.type === 'ack') {
    // 收到确认消息
    this.pendingAcks.delete(message.messageId);
  } else {
    // 处理业务消息
    this.handleBusinessMessage(message);
    
    // 发送确认
    this.sendAck(message.messageId);
  }
};

this.socket.onclose = () => {
  console.log('连接已关闭');
};

}

send(data) { const messageId = ++this.messageIdCounter; const message = { id: messageId, type: 'data', payload: data, timestamp: Date.now() };

if (this.socket && this.socket.readyState === WebSocket.OPEN) {
  this.sendMessageWithAck(message);
} else {
  // 连接未就绪,加入队列
  this.messageQueue.push(message);
}

}

sendMessageWithAck(message) { this.pendingAcks.set(message.id, { message: message, timestamp: Date.now(), retries: 0 });

this.socket.send(JSON.stringify(message));

// 设置重传定时器
setTimeout(() => this.checkPendingAcks(), 5000); // 5秒后检查确认

}

sendAck(messageId) { if (this.socket && this.socket.readyState === WebSocket.OPEN) { this.socket.send(JSON.stringify({ type: 'ack', messageId: messageId })); } }

checkPendingAcks() { const now = Date.now(); for (let [messageId, ackInfo] of this.pendingAcks) { if (now - ackInfo.timestamp > 5000 && ackInfo.retries < 3) { // 5秒未收到确认且重试次数小于3次 ackInfo.retries++; ackInfo.timestamp = now; this.socket.send(JSON.stringify(ackInfo.message)); } else if (now - ackInfo.timestamp > 5000 && ackInfo.retries >= 3) { // 超过最大重试次数,放弃该消息 this.pendingAcks.delete(messageId); console.log(消息 ${messageId} 重试多次未确认,已放弃); } } }

flushMessageQueue() { while (this.messageQueue.length > 0) { const message = this.messageQueue.shift(); this.sendMessageWithAck(message); } } } 六、WebSocket安全考虑

  1. 安全连接 (WSS) // 使用wss://代替ws:// const socket = new WebSocket('wss://example.com/chat');

// 后端配置SSL/TLS证书 // Node.js示例 const https = require('https'); const fs = require('fs'); const WebSocket = require('ws');

const server = https.createServer({ cert: fs.readFileSync('/path/to/cert.pem'), key: fs.readFileSync('/path/to/key.pem') });

const wss = new WebSocket.Server({ server }); // ...其他WebSocket配置 2. 认证与授权 // 前端连接时携带token const token = 'user-auth-token'; const socket = new WebSocket(ws://localhost:8080?token=${token});

// 后端验证token wss.on('connection', function connection(ws, req) { const url = new URL(req.url, ws://${req.headers.host}); const token = url.searchParams.get('token');

if (!isValidToken(token)) { ws.close(4403, 'Unauthorized'); // 4403是自定义的未授权代码 return; }

// 验证通过,继续处理连接 // ... }); 七、性能优化

  1. 连接管理 // 连接池管理 class WebSocketManager { constructor() { this.connections = new Map(); this.maxConnections = 100; }

addConnection(userId, ws) { if (this.connections.size >= this.maxConnections) { // 达到最大连接数,拒绝新连接或关闭最旧的连接 this.closeOldestConnection(); }

this.connections.set(userId, ws);

}

removeConnection(userId) { this.connections.delete(userId); }

getConnection(userId) { return this.connections.get(userId); }

broadcast(message, excludeUserId = null) { for (let [userId, ws] of this.connections) { if (userId !== excludeUserId && ws.readyState === WebSocket.OPEN) { ws.send(message); } } }

closeOldestConnection() { if (this.connections.size > 0) { const oldestKey = this.connections.keys().next().value; const oldestWs = this.connections.get(oldestKey); oldestWs.close(); this.connections.delete(oldestKey); } } } 2. 消息压缩 // 简单的消息压缩示例 function compressMessage(message) { // 对于简单的JSON消息,可以使用JSON.stringify的优化 return JSON.stringify(message); }

function decompressMessage(compressedMessage) { try { return JSON.parse(compressedMessage); } catch (error) { console.error('消息解析错误:', error); return null; } }

// 实际项目中可以使用更高效的压缩算法如pako (zlib) // 需要引入pako库: npm install pako /* import pako from 'pako';

function compressMessage(message) { const jsonStr = JSON.stringify(message); const compressed = pako.deflate(jsonStr); return btoa(String.fromCharCode.apply(null, compressed)); }

function decompressMessage(compressedMessage) { try { const binaryString = atob(compressedMessage); const charData = binaryString.split('').map(function(x) { return x.charCodeAt(0); }); const compressed = new Uint8Array(charData); const decompressed = pako.inflate(compressed); return JSON.parse(new TextDecoder().decode(decompressed)); } catch (error) { console.error('消息解压错误:', error); return null; } } */ 八、实际应用场景

  1. 实时聊天应用 WebSocket是构建实时聊天系统的理想选择,支持群聊、私聊、在线状态等功能。

  2. 在线协作工具 如在线文档编辑、白板协作、项目管理工具等,需要多人实时同步操作。

  3. 实时数据监控 股票行情、物联网设备监控、服务器状态监控等需要实时数据更新的场景。

  4. 多人在线游戏 游戏状态同步、玩家位置更新、实时对战等游戏功能。

结语 WebSocket技术为现代Web应用提供了强大的实时通信能力,解决了传统HTTP协议在实时性方面的局限。通过合理的设计和实现,开发者可以构建出响应迅速、交互流畅的实时应用。本文介绍了WebSocket的基础概念、前后端实现、高级应用和最佳实践,希望能为读者在实际项目中的应用提供有价值的参考。

随着Web技术的不断发展,WebSocket将继续在实时通信领域发挥重要作用,为用户带来更加丰富和即时的网络体验。