SockJS 中文教程
1. 核心概念
SockJS 是一个浏览器 JavaScript 库,提供了一个类似 WebSocket 的对象,用于在浏览器和服务器之间建立低延迟、全双工的通信通道。它由 Marek Majkowski 开发和维护。
1.1 什么是 SockJS?
SockJS 是一个实时通信库,提供了:
- 类似 WebSocket 的 API
- 自动降级到其他传输方式(如 XHR Streaming、XHR Polling 等)
- 跨域支持
- 轻量级的服务器实现
- 与各种前端框架的集成
1.2 为什么选择 SockJS?
- 兼容性:支持所有现代浏览器,包括那些不支持 WebSocket 的浏览器
- 简单易用:提供了类似 WebSocket 的 API,易于上手
- 可靠性:自动处理连接断开和重连
- 跨域支持:内置了跨域支持
- 轻量级:客户端库体积小,服务器实现高效
2. 安装与配置
2.1 基本安装
在 Node.js 项目中安装 SockJS 服务器:
npm install sockjs在前端项目中安装 SockJS 客户端:
npm install sockjs-client2.2 基本配置
创建一个简单的 SockJS 服务器:
import http from 'http';
import sockjs from 'sockjs';
// 创建 HTTP 服务器
const server = http.createServer();
// 创建 SockJS 服务器
const sockjsServer = sockjs.createServer({
// 配置选项
sockjs_url: 'https://cdn.jsdelivr.net/npm/sockjs-client@1.6.1/dist/sockjs.min.js',
prefix: '/echo'
});
// 监听连接事件
sockjsServer.on('connection', (conn) => {
console.log('新连接:', conn.id);
// 监听消息事件
conn.on('data', (message) => {
console.log('收到消息:', message);
// 发送消息回客户端
conn.write(message);
});
// 监听关闭事件
conn.on('close', () => {
console.log('连接关闭:', conn.id);
});
});
// 将 SockJS 服务器挂载到 HTTP 服务器
sockjsServer.installHandlers(server, {
prefix: '/echo'
});
// 启动服务器
server.listen(3000, () => {
console.log('服务器运行在 http://localhost:3000');
});2.3 客户端配置
创建一个简单的 SockJS 客户端:
import SockJS from 'sockjs-client';
// 创建 SockJS 连接
const sock = new SockJS('http://localhost:3000/echo');
// 监听连接打开事件
sock.onopen = function() {
console.log('连接打开');
// 发送消息
sock.send('Hello SockJS!');
};
// 监听消息事件
sock.onmessage = function(e) {
console.log('收到消息:', e.data);
};
// 监听连接关闭事件
sock.onclose = function() {
console.log('连接关闭');
};
// 监听错误事件
sock.onerror = function(error) {
console.error('连接错误:', error);
};3. 基本使用
3.1 事件系统
SockJS 使用事件系统进行通信,服务器和客户端都可以触发和监听事件:
3.1.1 服务器端事件
import http from 'http';
import sockjs from 'sockjs';
const server = http.createServer();
const sockjsServer = sockjs.createServer();
sockjsServer.on('connection', (conn) => {
console.log('新连接:', conn.id);
// 监听数据事件
conn.on('data', (message) => {
console.log('收到消息:', message);
try {
// 解析消息
const data = JSON.parse(message);
// 根据消息类型处理
switch (data.type) {
case 'chat':
// 处理聊天消息
console.log('聊天消息:', data.content);
conn.write(JSON.stringify({
type: 'chat',
content: `服务器收到: ${data.content}`
}));
break;
case 'userJoin':
// 处理用户加入
console.log('用户加入:', data.username);
conn.write(JSON.stringify({
type: 'system',
content: `${data.username} 加入了聊天`
}));
break;
default:
console.log('未知消息类型:', data.type);
}
} catch (error) {
console.error('解析消息错误:', error);
conn.write(JSON.stringify({
type: 'error',
content: '无效的消息格式'
}));
}
});
// 监听关闭事件
conn.on('close', () => {
console.log('连接关闭:', conn.id);
});
});
sockjsServer.installHandlers(server, {
prefix: '/echo'
});
server.listen(3000, () => {
console.log('服务器运行在 http://localhost:3000');
});3.1.2 客户端事件
import SockJS from 'sockjs-client';
const sock = new SockJS('http://localhost:3000/echo');
sock.onopen = function() {
console.log('连接打开');
// 发送用户加入消息
sock.send(JSON.stringify({
type: 'userJoin',
username: 'John Doe'
}));
// 发送聊天消息
sock.send(JSON.stringify({
type: 'chat',
content: 'Hello everyone!'
}));
};
sock.onmessage = function(e) {
console.log('收到消息:', e.data);
try {
// 解析消息
const data = JSON.parse(e.data);
// 根据消息类型处理
switch (data.type) {
case 'chat':
// 处理聊天消息
console.log('聊天消息:', data.content);
break;
case 'system':
// 处理系统消息
console.log('系统消息:', data.content);
break;
case 'error':
// 处理错误消息
console.error('错误消息:', data.content);
break;
default:
console.log('未知消息类型:', data.type);
}
} catch (error) {
console.error('解析消息错误:', error);
}
};
sock.onclose = function() {
console.log('连接关闭');
};
sock.onerror = function(error) {
console.error('连接错误:', error);
};3.2 连接管理
SockJS 提供了连接管理功能,包括连接状态检查、重连等:
3.2.1 客户端连接管理
import SockJS from 'sockjs-client';
// 创建连接
let sock = new SockJS('http://localhost:3000/echo');
// 连接状态
let isConnected = false;
// 重连尝试次数
let reconnectAttempts = 0;
const maxReconnectAttempts = 5;
// 重连延迟(毫秒)
const reconnectDelay = 1000;
sock.onopen = function() {
console.log('连接打开');
isConnected = true;
reconnectAttempts = 0;
};
sock.onmessage = function(e) {
console.log('收到消息:', e.data);
};
sock.onclose = function() {
console.log('连接关闭');
isConnected = false;
// 尝试重连
if (reconnectAttempts < maxReconnectAttempts) {
reconnectAttempts++;
console.log(`尝试重连 ${reconnectAttempts}/${maxReconnectAttempts}`);
setTimeout(() => {
sock = new SockJS('http://localhost:3000/echo');
setupSocketListeners(sock);
}, reconnectDelay * reconnectAttempts);
} else {
console.log('重连失败,已达到最大尝试次数');
}
};
sock.onerror = function(error) {
console.error('连接错误:', error);
};
// 设置 Socket 监听器
function setupSocketListeners(socket) {
socket.onopen = sock.onopen;
socket.onmessage = sock.onmessage;
socket.onclose = sock.onclose;
socket.onerror = sock.onerror;
}
// 发送消息
function sendMessage(message) {
if (isConnected) {
sock.send(message);
} else {
console.error('连接未打开,无法发送消息');
}
}
// 关闭连接
function closeConnection() {
if (sock) {
sock.close();
}
}3.3 跨域支持
SockJS 内置了跨域支持,无需额外配置:
3.3.1 服务器端跨域配置
import http from 'http';
import sockjs from 'sockjs';
const server = http.createServer();
const sockjsServer = sockjs.createServer({
// 允许的来源
origins: ['*']
});
sockjsServer.on('connection', (conn) => {
console.log('新连接:', conn.id);
conn.on('data', (message) => {
console.log('收到消息:', message);
conn.write(message);
});
conn.on('close', () => {
console.log('连接关闭:', conn.id);
});
});
sockjsServer.installHandlers(server, {
prefix: '/echo'
});
server.listen(3000, () => {
console.log('服务器运行在 http://localhost:3000');
});3.3.2 客户端跨域连接
import SockJS from 'sockjs-client';
// 连接到不同域名的服务器
const sock = new SockJS('http://other-domain.com/echo');
sock.onopen = function() {
console.log('跨域连接打开');
sock.send('Hello from different domain!');
};
sock.onmessage = function(e) {
console.log('收到消息:', e.data);
};
sock.onclose = function() {
console.log('连接关闭');
};4. 高级特性
4.1 与 Express 集成
SockJS 可以与 Express 框架集成:
import express from 'express';
import http from 'http';
import sockjs from 'sockjs';
const app = express();
const server = http.createServer(app);
// 创建 SockJS 服务器
const sockjsServer = sockjs.createServer();
// 监听连接事件
sockjsServer.on('connection', (conn) => {
console.log('新连接:', conn.id);
conn.on('data', (message) => {
console.log('收到消息:', message);
conn.write(message);
});
conn.on('close', () => {
console.log('连接关闭:', conn.id);
});
});
// 挂载 SockJS 服务器
sockjsServer.installHandlers(server, {
prefix: '/echo'
});
// Express 路由
app.get('/', (req, res) => {
res.send('Hello Express!');
});
// 启动服务器
server.listen(3000, () => {
console.log('服务器运行在 http://localhost:3000');
});4.2 认证和授权
在 SockJS 应用中实现认证和授权:
import http from 'http';
import sockjs from 'sockjs';
const server = http.createServer();
const sockjsServer = sockjs.createServer();
// 存储已认证的连接
const authenticatedConnections = new Map();
sockjsServer.on('connection', (conn) => {
console.log('新连接:', conn.id);
// 等待认证消息
conn.on('data', (message) => {
try {
const data = JSON.parse(message);
if (data.type === 'authenticate') {
// 验证 token
const token = data.token;
// 这里只是示例,实际应用中需要实现真实的验证逻辑
if (token === 'valid-token') {
// 认证成功
const user = {
id: '1',
name: 'John Doe'
};
authenticatedConnections.set(conn.id, user);
// 发送认证成功消息
conn.write(JSON.stringify({
type: 'authenticate',
success: true,
user
}));
// 监听后续消息
conn.on('data', (msg) => {
handleAuthenticatedMessage(conn, msg);
});
} else {
// 认证失败
conn.write(JSON.stringify({
type: 'authenticate',
success: false,
error: '无效的 token'
}));
conn.close();
}
} else {
// 未认证的连接只能发送认证消息
conn.write(JSON.stringify({
type: 'error',
error: '请先认证'
}));
}
} catch (error) {
console.error('解析消息错误:', error);
conn.write(JSON.stringify({
type: 'error',
error: '无效的消息格式'
}));
}
});
conn.on('close', () => {
console.log('连接关闭:', conn.id);
authenticatedConnections.delete(conn.id);
});
});
// 处理已认证的消息
function handleAuthenticatedMessage(conn, message) {
const user = authenticatedConnections.get(conn.id);
console.log(`收到 ${user.name} 的消息:`, message);
// 处理消息
conn.write(JSON.stringify({
type: 'message',
content: `服务器收到: ${message}`
}));
}
sockjsServer.installHandlers(server, {
prefix: '/echo'
});
server.listen(3000, () => {
console.log('服务器运行在 http://localhost:3000');
});4.3 消息广播
在 SockJS 应用中实现消息广播:
import http from 'http';
import sockjs from 'sockjs';
const server = http.createServer();
const sockjsServer = sockjs.createServer();
// 存储所有连接
const connections = new Set();
sockjsServer.on('connection', (conn) => {
console.log('新连接:', conn.id);
connections.add(conn);
// 广播新用户加入
broadcast({
type: 'system',
content: `用户 ${conn.id} 加入了聊天`
}, conn.id);
conn.on('data', (message) => {
console.log('收到消息:', message);
try {
const data = JSON.parse(message);
// 广播消息给所有连接
broadcast({
type: 'chat',
user: conn.id,
content: data.content
});
} catch (error) {
console.error('解析消息错误:', error);
}
});
conn.on('close', () => {
console.log('连接关闭:', conn.id);
connections.delete(conn);
// 广播用户离开
broadcast({
type: 'system',
content: `用户 ${conn.id} 离开了聊天`
});
});
});
// 广播消息给所有连接
function broadcast(message, excludeId = null) {
const messageStr = JSON.stringify(message);
connections.forEach((conn) => {
if (conn.id !== excludeId) {
try {
conn.write(messageStr);
} catch (error) {
console.error('发送消息错误:', error);
}
}
});
}
sockjsServer.installHandlers(server, {
prefix: '/echo'
});
server.listen(3000, () => {
console.log('服务器运行在 http://localhost:3000');
});4.4 性能优化
SockJS 应用的性能优化:
4.4.1 服务器端性能优化
import http from 'http';
import sockjs from 'sockjs';
const server = http.createServer();
const sockjsServer = sockjs.createServer({
// 配置选项
sockjs_url: 'https://cdn.jsdelivr.net/npm/sockjs-client@1.6.1/dist/sockjs.min.js',
// 禁用不需要的传输方式
transports: ['websocket', 'xhr-streaming', 'xhr-polling']
});
// 存储连接
const connections = new Map();
// 定期清理无效连接
setInterval(() => {
connections.forEach((conn) => {
if (conn.readyState !== 1) {
connections.delete(conn.id);
}
});
console.log(`当前连接数: ${connections.size}`);
}, 60000); // 每分钟清理一次
sockjsServer.on('connection', (conn) => {
console.log('新连接:', conn.id);
connections.set(conn.id, conn);
// 限制消息大小
conn.on('data', (message) => {
if (message.length > 1024 * 1024) { // 限制为 1MB
conn.write(JSON.stringify({
type: 'error',
error: '消息大小超过限制'
}));
return;
}
console.log('收到消息:', message);
conn.write(message);
});
conn.on('close', () => {
console.log('连接关闭:', conn.id);
connections.delete(conn.id);
});
});
sockjsServer.installHandlers(server, {
prefix: '/echo'
});
server.listen(3000, () => {
console.log('服务器运行在 http://localhost:3000');
});4.4.2 客户端性能优化
import SockJS from 'sockjs-client';
// 创建连接
const sock = new SockJS('http://localhost:3000/echo', null, {
// 配置选项
transports: ['websocket', 'xhr-streaming', 'xhr-polling'],
timeout: 5000
});
// 消息队列
const messageQueue = [];
let isProcessingQueue = false;
// 发送消息
function sendMessage(message) {
if (sock.readyState === SockJS.OPEN) {
sock.send(message);
} else {
// 将消息加入队列
messageQueue.push(message);
processMessageQueue();
}
}
// 处理消息队列
function processMessageQueue() {
if (isProcessingQueue || messageQueue.length === 0 || sock.readyState !== SockJS.OPEN) {
return;
}
isProcessingQueue = true;
const message = messageQueue.shift();
sock.send(message);
// 延迟处理下一条消息,避免消息积压
setTimeout(() => {
isProcessingQueue = false;
processMessageQueue();
}, 10);
}
sock.onopen = function() {
console.log('连接打开');
// 处理队列中的消息
processMessageQueue();
};
sock.onmessage = function(e) {
console.log('收到消息:', e.data);
};
sock.onclose = function() {
console.log('连接关闭');
};
sock.onerror = function(error) {
console.error('连接错误:', error);
};
// 批量发送消息
function sendBatchMessages(messages) {
messages.forEach((message) => {
sendMessage(JSON.stringify(message));
});
}5. 最佳实践
5.1 项目结构
推荐的 SockJS 项目结构:
├── src/
│ ├── server/ # 服务器端代码
│ │ ├── index.js # 服务器入口
│ │ ├── sockjs.js # SockJS 配置
│ │ ├── handlers/ # 消息处理器
│ │ ├── auth/ # 认证和授权
│ │ ├── broadcast/ # 广播功能
│ │ └── utils/ # 工具函数
│ ├── client/ # 客户端代码
│ │ ├── index.js # 客户端入口
│ │ ├── sockjs.js # SockJS 客户端配置
│ │ ├── components/ # 组件
│ │ └── utils/ # 工具函数
│ └── shared/ # 共享代码
│ └── events.js # 事件名称
├── package.json
└── .env5.2 安全性
- 认证和授权:实现用户认证和权限控制
- 输入验证:验证所有客户端输入
- 速率限制:防止消息轰炸
- 加密通信:在生产环境中使用 HTTPS
- CORS 配置:正确配置 CORS 选项
5.3 错误处理
- 客户端错误处理:处理连接错误、消息解析错误等
- 服务器端错误处理:处理消息处理错误、连接错误等
- 优雅降级:当 WebSocket 不可用时,自动降级到其他传输方式
- 重连机制:实现自动重连功能
6. 实用案例
6.1 构建实时聊天应用
6.1.1 服务器端实现
import http from 'http';
import sockjs from 'sockjs';
const server = http.createServer();
const sockjsServer = sockjs.createServer();
// 存储在线用户
const onlineUsers = new Map();
// 存储连接
const connections = new Map();
sockjsServer.on('connection', (conn) => {
console.log('新连接:', conn.id);
connections.set(conn.id, conn);
// 监听消息
conn.on('data', (message) => {
try {
const data = JSON.parse(message);
switch (data.type) {
case 'join':
// 用户加入
handleUserJoin(conn, data.username);
break;
case 'chat':
// 聊天消息
handleChatMessage(conn, data.content);
break;
case 'leave':
// 用户离开
handleUserLeave(conn);
break;
default:
console.log('未知消息类型:', data.type);
}
} catch (error) {
console.error('解析消息错误:', error);
conn.write(JSON.stringify({
type: 'error',
error: '无效的消息格式'
}));
}
});
conn.on('close', () => {
console.log('连接关闭:', conn.id);
handleUserLeave(conn);
connections.delete(conn.id);
});
});
// 处理用户加入
function handleUserJoin(conn, username) {
const userId = conn.id;
onlineUsers.set(userId, {
id: userId,
name: username
});
console.log(`${username} 加入了聊天`);
// 发送用户加入消息
conn.write(JSON.stringify({
type: 'join',
success: true,
user: onlineUsers.get(userId)
}));
// 广播用户加入
broadcast({
type: 'userJoined',
user: onlineUsers.get(userId)
}, userId);
// 广播在线用户列表
broadcast({
type: 'onlineUsers',
users: Array.from(onlineUsers.values())
});
}
// 处理聊天消息
function handleChatMessage(conn, content) {
const userId = conn.id;
const user = onlineUsers.get(userId);
if (!user) {
conn.write(JSON.stringify({
type: 'error',
error: '请先加入聊天'
}));
return;
}
console.log(`${user.name}: ${content}`);
// 广播聊天消息
broadcast({
type: 'chat',
user: user,
content: content,
timestamp: new Date().toISOString()
});
}
// 处理用户离开
function handleUserLeave(conn) {
const userId = conn.id;
const user = onlineUsers.get(userId);
if (user) {
console.log(`${user.name} 离开了聊天`);
onlineUsers.delete(userId);
// 广播用户离开
broadcast({
type: 'userLeft',
user: user
});
// 广播在线用户列表
broadcast({
type: 'onlineUsers',
users: Array.from(onlineUsers.values())
});
}
}
// 广播消息
function broadcast(message, excludeId = null) {
const messageStr = JSON.stringify(message);
connections.forEach((conn) => {
if (conn.id !== excludeId) {
try {
conn.write(messageStr);
} catch (error) {
console.error('发送消息错误:', error);
}
}
});
}
sockjsServer.installHandlers(server, {
prefix: '/chat'
});
server.listen(3000, () => {
console.log('服务器运行在 http://localhost:3000');
});6.1.2 客户端实现
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>实时聊天应用</title>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: Arial, sans-serif;
line-height: 1.6;
background-color: #f4f4f4;
}
.container {
max-width: 800px;
margin: 20px auto;
background-color: #fff;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
overflow: hidden;
}
.header {
background-color: #333;
color: #fff;
padding: 15px;
text-align: center;
}
.chat-container {
display: flex;
height: 500px;
}
.users-list {
width: 200px;
background-color: #f9f9f9;
border-right: 1px solid #ddd;
padding: 15px;
overflow-y: auto;
}
.users-list h3 {
margin-bottom: 10px;
color: #333;
}
.users-list ul {
list-style: none;
}
.users-list li {
padding: 8px;
margin-bottom: 5px;
background-color: #e8f4f8;
border-radius: 4px;
}
.messages {
flex: 1;
padding: 15px;
overflow-y: auto;
}
.message {
margin-bottom: 15px;
padding: 10px;
border-radius: 8px;
max-width: 70%;
}
.message.own {
background-color: #e3f2fd;
align-self: flex-end;
margin-left: auto;
}
.message.other {
background-color: #f1f1f1;
align-self: flex-start;
}
.message.system {
background-color: #e8f5e8;
align-self: center;
margin-left: auto;
margin-right: auto;
max-width: 90%;
text-align: center;
}
.message-info {
font-size: 12px;
color: #666;
margin-bottom: 5px;
}
.message-text {
font-size: 14px;
}
.input-area {
display: flex;
padding: 15px;
border-top: 1px solid #ddd;
}
.input-area input {
flex: 1;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
margin-right: 10px;
}
.input-area button {
padding: 10px 20px;
background-color: #333;
color: #fff;
border: none;
border-radius: 4px;
cursor: pointer;
}
.input-area button:hover {
background-color: #555;
}
.join-form {
padding: 20px;
text-align: center;
}
.join-form input {
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
margin-right: 10px;
}
.join-form button {
padding: 10px 20px;
background-color: #333;
color: #fff;
border: none;
border-radius: 4px;
cursor: pointer;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>实时聊天应用</h1>
</div>
<!-- 加入表单 -->
<div class="join-form" id="join-form">
<input type="text" id="username" placeholder="请输入用户名">
<button id="join-button">加入聊天</button>
</div>
<!-- 聊天区域 -->
<div class="chat-container" id="chat-container" style="display: none;">
<div class="users-list">
<h3>在线用户</h3>
<ul id="users-list"></ul>
</div>
<div class="messages" id="messages"></div>
</div>
<!-- 输入区域 -->
<div class="input-area" id="input-area" style="display: none;">
<input type="text" id="message-input" placeholder="输入消息...">
<button id="send-button">发送</button>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/sockjs-client@1.6.1/dist/sockjs.min.js"></script>
<script>
// 全局变量
let sock;
let username;
let userId;
// DOM 元素
const joinForm = document.getElementById('join-form');
const usernameInput = document.getElementById('username');
const joinButton = document.getElementById('join-button');
const chatContainer = document.getElementById('chat-container');
const usersList = document.getElementById('users-list');
const messagesContainer = document.getElementById('messages');
const inputArea = document.getElementById('input-area');
const messageInput = document.getElementById('message-input');
const sendButton = document.getElementById('send-button');
// 加入聊天
function joinChat() {
username = usernameInput.value.trim();
if (!username) {
alert('请输入用户名');
return;
}
// 创建 SockJS 连接
sock = new SockJS('http://localhost:3000/chat');
// 监听连接打开
sock.onopen = function() {
console.log('连接打开');
// 发送加入消息
sock.send(JSON.stringify({
type: 'join',
username: username
}));
};
// 监听消息
sock.onmessage = function(e) {
console.log('收到消息:', e.data);
handleMessage(JSON.parse(e.data));
};
// 监听连接关闭
sock.onclose = function() {
console.log('连接关闭');
showSystemMessage('连接已关闭');
};
// 监听错误
sock.onerror = function(error) {
console.error('连接错误:', error);
showSystemMessage('连接错误,请刷新页面重试');
};
}
// 处理消息
function handleMessage(message) {
switch (message.type) {
case 'join':
if (message.success) {
userId = message.user.id;
// 显示聊天界面
joinForm.style.display = 'none';
chatContainer.style.display = 'flex';
inputArea.style.display = 'flex';
showSystemMessage('加入聊天成功');
} else {
showSystemMessage('加入聊天失败: ' + message.error);
}
break;
case 'chat':
showChatMessage(message);
break;
case 'userJoined':
showSystemMessage(`${message.user.name} 加入了聊天`);
break;
case 'userLeft':
showSystemMessage(`${message.user.name} 离开了聊天`);
break;
case 'onlineUsers':
updateUsersList(message.users);
break;
case 'error':
showSystemMessage('错误: ' + message.error);
break;
default:
console.log('未知消息类型:', message.type);
}
}
// 显示聊天消息
function showChatMessage(message) {
const messageElement = document.createElement('div');
messageElement.className = `message ${message.user.id === userId ? 'own' : 'other'}`;
const messageInfo = document.createElement('div');
messageInfo.className = 'message-info';
messageInfo.textContent = `${message.user.name} · ${new Date(message.timestamp).toLocaleTimeString()}`;
const messageText = document.createElement('div');
messageText.className = 'message-text';
messageText.textContent = message.content;
messageElement.appendChild(messageInfo);
messageElement.appendChild(messageText);
messagesContainer.appendChild(messageElement);
// 滚动到底部
messagesContainer.scrollTop = messagesContainer.scrollHeight;
}
// 显示系统消息
function showSystemMessage(content) {
const messageElement = document.createElement('div');
messageElement.className = 'message system';
messageElement.textContent = content;
messagesContainer.appendChild(messageElement);
// 滚动到底部
messagesContainer.scrollTop = messagesContainer.scrollHeight;
}
// 更新用户列表
function updateUsersList(users) {
usersList.innerHTML = '';
users.forEach(user => {
const userElement = document.createElement('li');
userElement.textContent = user.name;
if (user.id === userId) {
userElement.style.fontWeight = 'bold';
userElement.style.backgroundColor = '#c3e6cb';
}
usersList.appendChild(userElement);
});
}
// 发送消息
function sendMessage() {
const content = messageInput.value.trim();
if (!content) {
return;
}
// 发送消息
sock.send(JSON.stringify({
type: 'chat',
content: content
}));
// 清空输入框
messageInput.value = '';
}
// 事件监听器
joinButton.addEventListener('click', joinChat);
usernameInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
joinChat();
}
});
sendButton.addEventListener('click', sendMessage);
messageInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
sendMessage();
}
});
</script>
</body>
</html>6.2 与 React 集成
SockJS 可以与 React 框架集成:
6.2.1 服务器端实现
import http from 'http';
import sockjs from 'sockjs';
const server = http.createServer();
const sockjsServer = sockjs.createServer();
// 存储连接
const connections = new Map();
sockjsServer.on('connection', (conn) => {
console.log('新连接:', conn.id);
connections.set(conn.id, conn);
conn.on('data', (message) => {
console.log('收到消息:', message);
try {
const data = JSON.parse(message);
// 广播消息
broadcast({
type: 'message',
id: conn.id,
content: data.content,
timestamp: new Date().toISOString()
});
} catch (error) {
console.error('解析消息错误:', error);
}
});
conn.on('close', () => {
console.log('连接关闭:', conn.id);
connections.delete(conn.id);
});
});
// 广播消息
function broadcast(message) {
const messageStr = JSON.stringify(message);
connections.forEach((conn) => {
try {
conn.write(messageStr);
} catch (error) {
console.error('发送消息错误:', error);
}
});
}
sockjsServer.installHandlers(server, {
prefix: '/echo'
});
server.listen(3000, () => {
console.log('服务器运行在 http://localhost:3000');
});6.2.2 React 客户端实现
import React, { useState, useEffect, useRef } from 'react';
import SockJS from 'sockjs-client';
function App() {
const [messages, setMessages] = useState([]);
const [inputValue, setInputValue] = useState('');
const sockRef = useRef(null);
const messagesEndRef = useRef(null);
// 初始化 SockJS 连接
useEffect(() => {
sockRef.current = new SockJS('http://localhost:3000/echo');
sockRef.current.onopen = function() {
console.log('连接打开');
};
sockRef.current.onmessage = function(e) {
console.log('收到消息:', e.data);
const message = JSON.parse(e.data);
setMessages(prevMessages => [...prevMessages, message]);
};
sockRef.current.onclose = function() {
console.log('连接关闭');
};
// 清理函数
return () => {
if (sockRef.current) {
sockRef.current.close();
}
};
}, []);
// 滚动到底部
useEffect(() => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [messages]);
// 发送消息
const sendMessage = () => {
if (inputValue.trim() && sockRef.current) {
sockRef.current.send(JSON.stringify({
content: inputValue
}));
setInputValue('');
}
};
return (
<div style={{ maxWidth: '800px', margin: '0 auto', padding: '20px' }}>
<h1>React SockJS 聊天</h1>
<div
style={{
height: '400px',
border: '1px solid #ddd',
borderRadius: '8px',
padding: '10px',
overflowY: 'auto',
marginBottom: '20px'
}}
>
{messages.map((message, index) => (
<div
key={index}
style={{
padding: '10px',
marginBottom: '10px',
backgroundColor: '#f1f1f1',
borderRadius: '8px'
}}
>
<div style={{ fontSize: '12px', color: '#666' }}>
{new Date(message.timestamp).toLocaleTimeString()}
</div>
<div>{message.content}</div>
</div>
))}
<div ref={messagesEndRef} />
</div>
<div style={{ display: 'flex' }}>
<input
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
placeholder="输入消息..."
style={{
flex: 1,
padding: '10px',
border: '1px solid #ddd',
borderRadius: '4px',
marginRight: '10px'
}}
onKeyPress={(e) => e.key === 'Enter' && sendMessage()}
/>
<button
onClick={sendMessage}
style={{
padding: '10px 20px',
backgroundColor: '#333',
color: '#fff',
border: 'none',
borderRadius: '4px',
cursor: 'pointer'
}}
>
发送
</button>
</div>
</div>
);
}
export default App;6. 总结
SockJS 是一个功能强大、兼容性好的实时通信库,为开发者提供了构建实时应用的完整解决方案。通过本教程的学习,你应该已经掌握了:
- SockJS 的核心概念和基本原理
- 如何安装和配置 SockJS
- 如何使用 SockJS 的基本功能,如连接管理、消息发送和接收
- 如何使用 SockJS 的高级特性,如与 Express 集成、认证和授权、广播功能
- 最佳实践和实用案例
SockJS 适用于各种实时应用场景,如聊天应用、实时游戏、实时协作工具等。它提供了类似 WebSocket 的 API,同时支持那些不支持 WebSocket 的浏览器,是构建实时应用的理想选择。
无论是构建小型应用还是大型企业系统,SockJS 都能满足你的实时通信需求。