Node.js实现WebSocket教程
1. WebSocket简介
WebSocket是一种在单个TCP连接上提供全双工通信的协议,允许服务器和客户端之间进行实时、双向通信。本教程将详细讲解如何在Node.js中实现WebSocket。
2. 技术选型
我们将使用ws
库来实现WebSocket服务器,并结合express
创建Web应用。
2.1 安装依赖
# 创建项目目录
mkdir nodejs-websocket-demo
cd nodejs-websocket-demo
# 初始化项目
npm init -y
# 安装依赖
npm install express ws uuid
依赖说明:
express
: Web应用框架ws
: WebSocket服务器实现uuid
: 生成唯一标识符
3. 项目结构
nodejs-websocket-demo/
│
├── public/
│ ├── index.html
│ └── client.js
├── server.js
└── package.json
4. 详细实现
4.1 WebSocket服务器 (server.js)
const express = require('express');
const http = require('http');
const WebSocket = require('ws');
const { v4: uuidv4 } = require('uuid');
const app = express();
const server = http.createServer(app);
const wss = new WebSocket.Server({ server });
// 存储客户端连接
const clients = new Map();
// 静态文件服务
app.use(express.static('public'));
// WebSocket连接处理
wss.on('connection', (ws) => {
// 为每个客户端分配唯一ID
const clientId = uuidv4();
// 存储客户端连接
clients.set(clientId, ws);
// 发送客户端ID
ws.send(JSON.stringify({
type: 'connection',
clientId: clientId
}));
// 广播新用户连接
broadcast({
type: 'userJoined',
clientId: clientId,
message: `用户 ${clientId} 已加入聊天`
}, clientId);
// 消息处理
ws.on('message', (message) => {
try {
const parsedMessage = JSON.parse(message);
switch(parsedMessage.type) {
case 'chat':
handleChatMessage(clientId, parsedMessage);
break;
case 'typing':
handleTypingNotification(clientId, parsedMessage);
break;
default:
console.log('未知消息类型:', parsedMessage.type);
}
} catch (error) {
console.error('消息解析错误:', error);
}
});
// 连接关闭处理
ws.on('close', () => {
// 移除客户端
clients.delete(clientId);
// 广播用户离开
broadcast({
type: 'userLeft',
clientId: clientId,
message: `用户 ${clientId} 已离开聊天`
}, clientId);
});
});
// 聊天消息处理
function handleChatMessage(senderId, message) {
broadcast({
type: 'chat',
clientId: senderId,
message: message.message
}, senderId);
}
// 输入状态通知
function handleTypingNotification(senderId, message) {
broadcast({
type: 'typing',
clientId: senderId,
isTyping: message.isTyping
}, senderId);
}
// 广播消息
function broadcast(message, excludeClientId = null) {
clients.forEach((client, clientId) => {
if (client.readyState === WebSocket.OPEN && clientId !== excludeClientId) {
client.send(JSON.stringify(message));
}
});
}
// 服务器启动
const PORT = process.env.PORT || 3000;
server.listen(PORT, () => {
console.log(`WebSocket服务器运行在 ${PORT} 端口`);
});
4.2 客户端HTML (public/index.html)
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>WebSocket聊天室</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 600px;
margin: 0 auto;
padding: 20px;
}
#chat-messages {
height: 300px;
overflow-y: scroll;
border: 1px solid #ccc;
padding: 10px;
margin-bottom: 10px;
}
</style>
</head>
<body>
<div id="chat-messages"></div>
<input type="text" id="message-input" placeholder="输入消息">
<button id="send-btn">发送</button>
<script src="client.js"></script>
</body>
</html>
4.3 客户端JavaScript (public/client.js)
const socket = new WebSocket('ws://localhost:3000');
let clientId = null;
const chatMessages = document.getElementById('chat-messages');
const messageInput = document.getElementById('message-input');
const sendBtn = document.getElementById('send-btn');
// WebSocket事件处理
socket.addEventListener('open', (event) => {
appendMessage('系统', '连接成功');
});
socket.addEventListener('message', (event) => {
const message = JSON.parse(event.data);
switch(message.type) {
case 'connection':
clientId = message.clientId;
appendMessage('系统', `您的ID是: ${clientId}`);
break;
case 'chat':
appendMessage(message.clientId, message.message);
break;
case 'userJoined':
case 'userLeft':
appendMessage('系统', message.message);
break;
case 'typing':
handleTypingNotification(message);
break;
}
});
socket.addEventListener('close', (event) => {
appendMessage('系统', '连接已关闭');
});
// 发送消息
sendBtn.addEventListener('click', sendMessage);
messageInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
sendMessage();
}
});
function sendMessage() {
const message = messageInput.value.trim();
if (message) {
socket.send(JSON.stringify({
type: 'chat',
message: message
}));
appendMessage(clientId, message);
messageInput.value = '';
}
}
// 消息追加到聊天窗口
function appendMessage(sender, text) {
const messageEl = document.createElement('div');
messageEl.innerHTML = `<strong>${sender}:</strong> ${text}`;
chatMessages.appendChild(messageEl);
chatMessages.scrollTop = chatMessages.scrollHeight;
}
// 处理打字状态通知
function handleTypingNotification(message) {
// 可以在这里实现打字状态提示
}
5. 运行项目
# 启动服务器
node server.js
# 访问 http://localhost:3000
6. 功能特点
- 实时双向通信
- 唯一客户端标识
- 广播消息机制
- 连接/断开事件处理
- 聊天消息和系统通知
7. 性能与扩展建议
- 生产环境考虑使用Redis等外部存储管理WebSocket连接
- 增加身份验证机制
- 实现消息持久化
- 使用负载均衡
8. 安全注意事项
- 使用WSS(WebSocket Secure)
- 实现连接速率限制
- 验证和过滤消息内容
- 防止跨站WebSocket劫持