WebSocket 聊天室实现方案
下面是一个基于 WebSocket 的简单聊天室实现方案,包含前端和后端代码。
方案概述
-
后端:使用 Node.js 和 ws 库创建 WebSocket 服务器
-
前端:使用原生 JavaScript 实现 WebSocket 客户端
-
功能:
- 用户连接/断开通知
- 发送和接收消息
- 显示在线用户列表
- 简单的用户名设置
完整实现代码
1. 后端代码 (server.js)
javascript
const WebSocket = require('ws');
const http = require('http');
// 创建HTTP服务器
const server = http.createServer();
const wss = new WebSocket.Server({ server });
// 存储所有连接的客户端和用户名
const clients = new Map();
wss.on('connection', (ws) => {
console.log('New client connected');
// 为新连接分配一个默认用户名
const defaultUsername = `User${Math.floor(Math.random() * 1000)}`;
clients.set(ws, { username: defaultUsername, color: getRandomColor() });
// 通知所有客户端有新用户加入
broadcastUserList();
ws.on('message', (message) => {
try {
const data = JSON.parse(message);
if (data.type === 'setUsername') {
// 更新用户名
const userData = clients.get(ws);
userData.username = data.username;
clients.set(ws, userData);
broadcastUserList();
// 发送欢迎消息
const welcomeMsg = {
type: 'message',
username: '系统',
content: `${data.username} 加入了聊天室`,
timestamp: new Date().toISOString(),
color: '#888'
};
broadcast(JSON.stringify(welcomeMsg));
} else if (data.type === 'message') {
// 广播普通消息
const userData = clients.get(ws);
const msg = {
type: 'message',
username: userData.username,
content: data.content,
timestamp: new Date().toISOString(),
color: userData.color
};
broadcast(JSON.stringify(msg));
}
} catch (e) {
console.error('Error parsing message:', e);
}
});
ws.on('close', () => {
console.log('Client disconnected');
const userData = clients.get(ws);
if (userData) {
// 通知所有客户端有用户离开
const leaveMsg = {
type: 'message',
username: '系统',
content: `${userData.username} 离开了聊天室`,
timestamp: new Date().toISOString(),
color: '#888'
};
broadcast(JSON.stringify(leaveMsg));
clients.delete(ws);
broadcastUserList();
}
});
});
// 广播消息给所有客户端
function broadcast(message) {
wss.clients.forEach(client => {
if (client.readyState === WebSocket.OPEN) {
client.send(message);
}
});
}
// 广播用户列表给所有客户端
function broadcastUserList() {
const userList = Array.from(clients.values()).map(user => ({
username: user.username,
color: user.color
}));
const message = {
type: 'userList',
users: userList
};
broadcast(JSON.stringify(message));
}
// 生成随机颜色
function getRandomColor() {
const letters = '0123456789ABCDEF';
let color = '#';
for (let i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
}
// 启动服务器
const PORT = process.env.PORT || 8080;
server.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
2. 前端代码 (index.html)
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebSocket 聊天室</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
background-color: #f5f5f5;
}
#chat-container {
background-color: white;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
overflow: hidden;
}
#messages {
height: 400px;
overflow-y: auto;
padding: 20px;
border-bottom: 1px solid #eee;
}
.message {
margin-bottom: 15px;
line-height: 1.4;
}
.message .username {
font-weight: bold;
margin-right: 5px;
}
.message .timestamp {
font-size: 0.8em;
color: #999;
margin-left: 5px;
}
#user-list {
width: 200px;
float: right;
background-color: #f9f9f9;
padding: 10px;
border-left: 1px solid #eee;
height: 400px;
overflow-y: auto;
}
.user-item {
margin-bottom: 5px;
padding: 5px;
border-radius: 4px;
}
#input-area {
padding: 15px;
background-color: #f9f9f9;
display: flex;
}
#message-input {
flex-grow: 1;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
margin-right: 10px;
}
#send-button {
padding: 10px 20px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
#send-button:hover {
background-color: #45a049;
}
#username-form {
margin-bottom: 20px;
padding: 15px;
background-color: #f9f9f9;
border-radius: 8px;
}
#username-input {
padding: 8px;
margin-right: 10px;
width: 200px;
}
#set-username {
padding: 8px 15px;
background-color: #2196F3;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
#set-username:hover {
background-color: #0b7dda;
}
.system-message {
color: #888;
font-style: italic;
}
</style>
</head>
<body>
<h1>WebSocket 聊天室</h1>
<div id="username-form">
<input type="text" id="username-input" placeholder="输入用户名">
<button id="set-username">设置用户名</button>
</div>
<div id="chat-container">
<div id="user-list">
<h3>在线用户</h3>
<div id="users"></div>
</div>
<div id="messages"></div>
<div id="input-area">
<input type="text" id="message-input" placeholder="输入消息..." disabled>
<button id="send-button" disabled>发送</button>
</div>
</div>
<script>
// 连接WebSocket服务器
const socket = new WebSocket('ws://localhost:8080');
let username = '';
// DOM元素
const messagesEl = document.getElementById('messages');
const messageInput = document.getElementById('message-input');
const sendButton = document.getElementById('send-button');
const usernameInput = document.getElementById('username-input');
const setUsernameButton = document.getElementById('set-username');
const usersEl = document.getElementById('users');
// WebSocket连接打开时
socket.addEventListener('open', (event) => {
console.log('Connected to WebSocket server');
});
// 接收消息
socket.addEventListener('message', (event) => {
const data = JSON.parse(event.data);
if (data.type === 'message') {
// 显示消息
const messageEl = document.createElement('div');
messageEl.className = 'message';
if (data.username === '系统') {
messageEl.classList.add('system-message');
}
const timestamp = new Date(data.timestamp).toLocaleTimeString();
messageEl.innerHTML = `
<span class="username" style="color: ${data.color}">${data.username}</span>
<span class="content">${data.content}</span>
<span class="timestamp">${timestamp}</span>
`;
messagesEl.appendChild(messageEl);
messagesEl.scrollTop = messagesEl.scrollHeight;
} else if (data.type === 'userList') {
// 更新用户列表
usersEl.innerHTML = '';
data.users.forEach(user => {
const userEl = document.createElement('div');
userEl.className = 'user-item';
userEl.innerHTML = `
<span class="username" style="color: ${user.color}">${user.username}</span>
`;
usersEl.appendChild(userEl);
});
}
});
// 设置用户名
setUsernameButton.addEventListener('click', () => {
const newUsername = usernameInput.value.trim();
if (newUsername) {
username = newUsername;
socket.send(JSON.stringify({
type: 'setUsername',
username: newUsername
}));
// 启用消息输入
messageInput.disabled = false;
sendButton.disabled = false;
// 禁用用户名设置
usernameInput.disabled = true;
setUsernameButton.disabled = true;
}
});
// 发送消息
sendButton.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: 'message',
content: message
}));
messageInput.value = '';
}
}
// 连接关闭时
socket.addEventListener('close', (event) => {
console.log('Disconnected from WebSocket server');
messageInput.disabled = true;
sendButton.disabled = true;
// 显示断开连接消息
const messageEl = document.createElement('div');
messageEl.className = 'message system-message';
messageEl.textContent = '与服务器的连接已断开';
messagesEl.appendChild(messageEl);
});
</script>
</body>
</html>
部署和使用说明
-
安装依赖:
bash
npm install ws -
启动服务器:
bash
node server.js -
访问客户端:
- 在浏览器中打开
index.html文件 - 或者将 HTML 文件放在静态服务器上访问
- 在浏览器中打开
-
使用聊天室:
- 首先设置用户名
- 然后可以开始发送和接收消息
- 右侧会显示当前在线的用户列表
功能扩展建议
- 消息持久化:将聊天消息存储在数据库中
- 私聊功能:允许用户之间发送私密消息
- 房间功能:支持创建和加入不同的聊天室
- 表情支持:添加表情选择器
- 文件分享:支持发送图片或其他文件
- 用户认证:添加登录系统
这个实现提供了一个基础的 WebSocket 聊天室功能,可以根据需要进一步扩展和完善。