- 核心功能:发送/接收消息、用户列表、在线状态、消息历史。
- 技术栈:
- 前端: HTML, CSS, jQuery
- 后端: Python (使用 Flask 框架,因为它轻量且易于上手)
- 实时通信: WebSocket (使用
Flask-SocketIO库,它封装了 WebSocket,简化了实现)
- 代码结构:清晰的前后端分离代码。
- 详细步骤:从环境搭建到运行测试的全过程。
为什么选择这个技术栈?
- jQuery: 虽然现在主流是 Vue/React,但 jQuery 在处理 DOM、事件绑定、AJAX 请求方面依然非常高效和简洁,非常适合快速构建此类交互应用。
- Python + Flask: Python 语法简洁,Flask 是一个“微框架”,不会给你带来太多束缚,让你能专注于核心业务逻辑。
- WebSocket: 这是实现实时聊天室的关键,传统的 HTTP 轮询(Polling)效率低下,而 WebSocket 允许服务器主动向客户端推送消息,延迟极低,体验流畅。
第 1 步:环境准备
你需要安装 Python 和 pip,安装必要的 Python 库:

(图片来源网络,侵删)
pip install flask flask-socketio python-socketio eventlet
flask: Web 框架。flask-socketio: Flask 的 SocketIO 扩展。python-socketio: SocketIO 的核心库。eventlet: 一个高性能的 Python 异步网络库,SocketIO 推荐使用它作为 WSGI 服务器。
第 2 步:后端代码 (Python Flask-SocketIO)
创建一个名为 app.py 的文件,这是聊天室的大脑。
# app.py
from flask import Flask, render_template
from flask_socketio import SocketIO, emit
# 创建 Flask 应用实例
app = Flask(__name__)
# 设置一个密钥,用于 session 加密
app.config['SECRET_KEY'] = 'your-secret-key!'
# 创建 SocketIO 实例,并传入 Flask 应用
socketio = SocketIO(app, cors_allowed_origins="*")
# 存储在线用户和聊天历史的字典
users = {}
chat_history = []
# 当客户端连接时触发
@socketio.on('connect')
def handle_connect():
print('Client connected')
# 将聊天历史发送给新连接的客户端
emit('load_history', chat_history)
# 当客户端断开连接时触发
@socketio.on('disconnect')
def handle_disconnect():
print('Client disconnected')
# 从用户列表中移除断开连接的用户
if request.sid in users:
username = users[request.sid]
del users[request.sid]
# 广播用户下线消息
emit('user_list', list(users.values()), broadcast=True)
emit('message', {
'username': 'System',
'text': f'{username} has left the chat.',
'is_system': True
}, broadcast=True)
# 当客户端发送 'message' 事件时触发
@socketio.on('message')
def handle_message(data):
# data 是一个字典,包含 'username' 和 'text'
username = data['username']
text = data['text']
# 创建消息字典
message = {
'username': username,
'text': text,
'timestamp': data.get('timestamp') # 可以在前端生成时间戳
}
# 将消息添加到聊天历史
chat_history.append(message)
# 广播消息给所有连接的客户端
emit('message', message, broadcast=True)
# 当客户端发送 'join' 事件时触发(用户加入)
@socketio.on('join')
def handle_join(data):
username = data['username']
# 将用户名与当前会话ID (session id) 关联
users[request.sid] = username
# 广播新的用户列表给所有客户端
emit('user_list', list(users.values()), broadcast=True)
# 广播用户加入消息
join_message = {
'username': 'System',
'text': f'{username} has joined the chat.',
'is_system': True
}
emit('message', join_message, broadcast=True)
# 定义路由,当用户访问根路径时,渲染聊天室页面
@app.route('/')
def index():
return render_template('index.html')
# 主程序入口
if __name__ == '__main__':
# 使用 eventlet 作为 WSGI 服务器运行应用
socketio.run(app, debug=True, host='0.0.0.0', port=5000)
第 3 步:前端代码 (HTML, CSS, jQuery)
在你的项目目录下,创建一个 templates 文件夹,然后在里面创建 index.html 文件。
your_project/
├── app.py
└── templates/
└── index.html
└── style.css
templates/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">jQuery Chat Room</title>
<!-- 引入 jQuery -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<!-- 引入 Socket.IO 客户端库 -->
<script src="https://cdn.socket.io/4.5.0/socket.io.min.js"></script>
<!-- 引入自定义样式 -->
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
<div class="chat-container">
<div class="chat-header">
<h1>jQuery Chat Room</h1>
</div>
<div class="chat-main">
<div class="chat-sidebar">
<h2>Online Users</h2>
<ul id="user-list">
<!-- 用户列表将通过 jQuery 动态添加 -->
</ul>
</div>
<div class="chat-messages">
<div id="message-container">
<!-- 消息将通过 jQuery 动态添加 -->
</div>
<div class="chat-input-area">
<input type="text" id="username-input" placeholder="Enter your name">
<button id="join-btn">Join</button>
</div>
<div class="chat-input-area hidden" id="chat-area">
<input type="text" id="message-input" placeholder="Type a message...">
<button id="send-btn">Send</button>
</div>
</div>
</div>
</div>
<script>
// 连接到后端的 SocketIO 服务器
const socket = io();
// DOM 元素
const $messageContainer = $('#message-container');
const $messageInput = $('#message-input');
const $sendBtn = $('#send-btn');
const $userList = $('#user-list');
const $usernameInput = $('#username-input');
const $joinBtn = $('#join-btn');
const $chatArea = $('#chat-area');
const $chatInputArea = $('.chat-input-area');
// 发送消息
function sendMessage() {
const text = $messageInput.val().trim();
if (text) {
const username = $usernameInput.val().trim();
const timestamp = new Date().toLocaleTimeString();
// 发送 'message' 事件到服务器
socket.emit('message', { username, text, timestamp });
$messageInput.val(''); // 清空输入框
}
}
// 加入聊天室
function joinChat() {
const username = $usernameInput.val().trim();
if (username) {
// 发送 'join' 事件到服务器
socket.emit('join', { username });
$usernameInput.prop('disabled', true);
$joinBtn.hide();
$chatArea.removeClass('hidden');
}
}
// 绑定发送按钮点击事件
$sendBtn.on('click', sendMessage);
// 绑定回车键发送消息
$messageInput.on('keypress', function(e) {
if (e.which === 13) { // 13 是回车键的 keyCode
sendMessage();
}
});
// 绑定加入按钮点击事件
$joinBtn.on('click', joinChat);
// 监听服务器发送的 'message' 事件
socket.on('message', function(data) {
// 如果是系统消息,样式不同
const messageClass = data.is_system ? 'system-message' : 'user-message';
const usernameDisplay = data.is_system ? '' : `<strong>${data.username}:</strong> `;
const messageHtml = `
<div class="message ${messageClass}">
<span class="timestamp">${data.timestamp}</span>
<span class="text">${usernameDisplay}${data.text}</span>
</div>
`;
$messageContainer.append(messageHtml);
// 自动滚动到底部
$messageContainer.scrollTop($messageContainer[0].scrollHeight);
});
// 监听服务器发送的 'user_list' 事件
socket.on('user_list', function(users) {
$userList.empty(); // 清空现有列表
users.forEach(user => {
$userList.append(`<li>${user}</li>`);
});
});
// 监听服务器发送的 'load_history' 事件
socket.on('load_history', function(history) {
$messageContainer.empty();
history.forEach(data => {
const messageClass = data.is_system ? 'system-message' : 'user-message';
const usernameDisplay = data.is_system ? '' : `<strong>${data.username}:</strong> `;
const messageHtml = `
<div class="message ${messageClass}">
<span class="timestamp">${data.timestamp}</span>
<span class="text">${usernameDisplay}${data.text}</span>
</div>
`;
$messageContainer.append(messageHtml);
});
$messageContainer.scrollTop($messageContainer[0].scrollHeight);
});
</script>
</body>
</html>
static/style.css
在 static 文件夹下创建 style.css 文件。
/* static/style.css */
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #f0f2f5;
margin: 0;
padding: 20px;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
}
.chat-container {
width: 90%;
max-width: 900px;
background: #ffffff;
border-radius: 10px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
overflow: hidden;
display: flex;
flex-direction: column;
height: 80vh;
}
.chat-header {
background: #007bff;
color: white;
padding: 15px 20px;
text-align: center;
}
.chat-main {
display: flex;
flex: 1;
overflow: hidden;
}
.chat-sidebar {
width: 200px;
background: #f8f9fa;
border-right: 1px solid #dee2e6;
padding: 15px;
overflow-y: auto;
}
.chat-sidebar h2 {
margin-top: 0;
font-size: 1.1em;
color: #495057;
}
#user-list {
list-style: none;
padding: 0;
margin: 10px 0 0 0;
}
#user-list li {
padding: 8px;
background: #e9ecef;
margin-bottom: 5px;
border-radius: 5px;
text-align: center;
}
.chat-messages {
flex: 1;
display: flex;
flex-direction: column;
}
#message-container {
flex: 1;
padding: 20px;
overflow-y: auto;
background: white;
}
.message {
margin-bottom: 15px;
padding: 10px 15px;
border-radius: 18px;
max-width: 70%;
word-wrap: break-word;
}
.user-message {
background: #e3f2fd;
align-self: flex-end;
text-align: right;
border-bottom-right-radius: 5px;
}
.system-message {
background: #f1f1f1;
text-align: center;
font-style: italic;
color: #6c757d;
max-width: 100%;
margin: 10px auto;
}
.message .timestamp {
font-size: 0.75em;
color: #6c757d;
margin-right: 8px;
}
.message .text {
display: block;
}
.chat-input-area {
display: flex;
padding: 15px;
border-top: 1px solid #dee2e6;
background: #f8f9fa;
}
.chat-input-area.hidden {
display: none;
}
.chat-input-area input[type="text"] {
flex: 1;
padding: 10px 15px;
border: 1px solid #ced4da;
border-radius: 20px;
outline: none;
font-size: 1em;
}
.chat-input-area button {
margin-left: 10px;
padding: 10px 20px;
border: none;
border-radius: 20px;
background: #007bff;
color: white;
cursor: pointer;
font-size: 1em;
transition: background-color 0.2s;
}
.chat-input-area button:hover {
background: #0056b3;
}
第 4 步:运行和测试
-
确保目录结构正确:
(图片来源网络,侵删)my_chat_app/ ├── app.py ├── static/ │ └── style.css └── templates/ └── index.html -
启动服务器: 在你的项目根目录(
my_chat_app/)下,打开终端或命令提示符,运行:python app.py
你应该会看到类似以下的输出:
* Serving Flask app 'app' * Debug mode: on * Running on all addresses (0.0.0.0) * Running on http://127.0.0.1:5000 * Running on http://192.168.x.x:5000 -
打开浏览器测试:
- 打开两个或更多的浏览器窗口(或标签页)。
- 在每个窗口中,访问
http://127.0.0.1:5000。 - 在第一个窗口中输入一个用户名,点击 "Join"。
- 你会看到 "用户列表" 中出现了你的用户名,并且消息区域显示了一条 "XXX has joined the chat" 的系统消息。
- 在第二个窗口中输入另一个不同的用户名,点击 "Join"。
- 两个用户都应该出现在彼此的 "用户列表" 中,并且都收到了新用户加入的系统消息。
- 在任意一个窗口的输入框中输入消息,点击 "Send" 或按回车。
- 你会看到这条消息立即出现在所有窗口的消息区域中。
- 关闭任意一个浏览器窗口,其他窗口会立即收到 "XXX has left the chat" 的系统消息,并且该用户会从 "用户列表" 中消失。
- 实时消息:使用 WebSocket 实现了毫秒级的消息传递。
- 用户管理:用户可以加入,系统会广播更新用户列表和加入/离开消息。
- 聊天历史:新用户加入时会自动加载之前的聊天记录。
- 响应式设计:使用 Flexbox 布局,界面在不同屏幕尺寸下表现良好。
- 用户体验:支持回车发送、自动滚动、区分普通消息和系统消息。
这个项目是一个非常好的起点,你可以在此基础上继续扩展,比如添加私聊功能、文件发送、消息持久化到数据库等。

(图片来源网络,侵删)
