# 消息路由系统
## 🎯 学习目标
通过本章学习,您将能够:
- 理解多人聊天中的消息路由机制
- 掌握群组消息广播和私聊消息的实现
- 学会设计高效的消息分发系统
- 在Chat-Room项目中实现完整的消息路由功能
## 🚀 消息路由架构
### 路由系统概览
```mermaid
graph TB
subgraph "消息路由系统"
A[消息接收器
Message Receiver] --> B[消息解析器
Message Parser]
B --> C[路由决策器
Route Decider]
C --> D[群组路由
Group Router]
C --> E[私聊路由
Private Router]
C --> F[系统路由
System Router]
D --> G[群组广播
Group Broadcast]
E --> H[点对点发送
P2P Send]
F --> I[系统通知
System Notify]
G --> J[消息分发器
Message Dispatcher]
H --> J
I --> J
J --> K[客户端连接
Client Connections]
end
subgraph "路由策略"
L[负载均衡
Load Balance]
M[失败重试
Retry Logic]
N[消息缓存
Message Cache]
O[离线存储
Offline Storage]
end
J --> L
J --> M
J --> N
J --> O
style A fill:#e8f5e8
style C fill:#fff3cd
style J fill:#f8d7da
```
### 消息流转过程
```mermaid
sequenceDiagram
participant C1 as 客户端1
participant S as 服务器
participant R as 路由器
participant GM as 群组管理器
participant C2 as 客户端2
participant C3 as 客户端3
Note over C1,C3: 群组消息路由流程
C1->>S: 发送群组消息
S->>R: 消息路由请求
R->>R: 解析消息类型
R->>GM: 获取群组成员列表
GM->>R: 返回在线成员
par 并行广播
R->>C2: 转发消息
R->>C3: 转发消息
end
R->>S: 路由完成确认
S->>C1: 发送确认
Note over C1,C3: 私聊消息路由流程
C1->>S: 发送私聊消息
S->>R: 消息路由请求
R->>R: 验证目标用户
R->>C2: 直接发送
R->>S: 路由完成确认
S->>C1: 发送确认
```
## 📨 消息路由核心实现
### 消息路由器
```python
# server/chat/message_router.py - 消息路由器
import threading
import time
import queue
from typing import Dict, List, Set, Optional, Callable, Any
from dataclasses import dataclass
from enum import Enum
import json
class MessageType(Enum):
"""消息类型枚举"""
GROUP_MESSAGE = "group_message" # 群组消息
PRIVATE_MESSAGE = "private_message" # 私聊消息
SYSTEM_MESSAGE = "system_message" # 系统消息
BROADCAST = "broadcast" # 全局广播
NOTIFICATION = "notification" # 通知消息
class RouteResult(Enum):
"""路由结果枚举"""
SUCCESS = "success" # 路由成功
PARTIAL_SUCCESS = "partial" # 部分成功
FAILED = "failed" # 路由失败
NO_RECIPIENTS = "no_recipients" # 无接收者
@dataclass
class RouteTarget:
"""路由目标"""
user_id: int
connection: Any # Socket连接对象
is_online: bool = True
last_activity: float = 0
@dataclass
class RouteContext:
"""路由上下文"""
message_id: str
sender_id: int
message_type: MessageType
target_group: Optional[int] = None
target_user: Optional[int] = None
priority: int = 0 # 消息优先级
retry_count: int = 0
max_retries: int = 3
class MessageRouter:
"""
消息路由器
负责将消息路由到正确的目标:
1. 群组消息广播给所有群组成员
2. 私聊消息发送给指定用户
3. 系统消息按需分发
4. 处理离线用户的消息存储
"""
def __init__(self, group_manager, connection_manager, offline_storage=None):
self.group_manager = group_manager
self.connection_manager = connection_manager
self.offline_storage = offline_storage
# 路由统计
self.route_stats = {
'total_messages': 0,
'successful_routes': 0,
'failed_routes': 0,
'offline_stored': 0
}
# 消息队列
self.message_queue = queue.PriorityQueue()
self.retry_queue = queue.Queue()
# 路由处理器映射
self.route_handlers = {
MessageType.GROUP_MESSAGE: self._route_group_message,
MessageType.PRIVATE_MESSAGE: self._route_private_message,
MessageType.SYSTEM_MESSAGE: self._route_system_message,
MessageType.BROADCAST: self._route_broadcast_message,
MessageType.NOTIFICATION: self._route_notification
}
# 线程控制
self.routing_thread = None
self.retry_thread = None
self.running = False
# 线程锁
self.stats_lock = threading.Lock()
def start_routing(self):
"""启动路由服务"""
if self.running:
return
self.running = True
# 启动主路由线程
self.routing_thread = threading.Thread(
target=self._routing_worker,
name="MessageRouter",
daemon=True
)
self.routing_thread.start()
# 启动重试线程
self.retry_thread = threading.Thread(
target=self._retry_worker,
name="RetryWorker",
daemon=True
)
self.retry_thread.start()
print("消息路由服务已启动")
def stop_routing(self):
"""停止路由服务"""
self.running = False
# 发送停止信号
self.message_queue.put((0, None))
self.retry_queue.put(None)
# 等待线程结束
if self.routing_thread:
self.routing_thread.join(timeout=5)
if self.retry_thread:
self.retry_thread.join(timeout=5)
print("消息路由服务已停止")
def route_message(self, message: Dict[str, Any], context: RouteContext) -> RouteResult:
"""
路由消息
Args:
message: 消息内容
context: 路由上下文
Returns:
路由结果
"""
# 添加到路由队列
priority = -context.priority # 负数表示高优先级
self.message_queue.put((priority, (message, context)))
with self.stats_lock:
self.route_stats['total_messages'] += 1
return RouteResult.SUCCESS
def _routing_worker(self):
"""路由工作线程"""
print("路由工作线程启动")
while self.running:
try:
# 获取消息
priority, item = self.message_queue.get(timeout=1.0)
if item is None: # 停止信号
break
message, context = item
# 执行路由
result = self._execute_route(message, context)
# 处理路由结果
if result == RouteResult.FAILED and context.retry_count < context.max_retries:
# 添加到重试队列
context.retry_count += 1
self.retry_queue.put((message, context))
# 标记任务完成
self.message_queue.task_done()
except queue.Empty:
continue
except Exception as e:
print(f"路由工作线程异常: {e}")
print("路由工作线程结束")
def _retry_worker(self):
"""重试工作线程"""
print("重试工作线程启动")
while self.running:
try:
item = self.retry_queue.get(timeout=1.0)
if item is None: # 停止信号
break
message, context = item
# 等待一段时间后重试
time.sleep(min(2 ** context.retry_count, 30)) # 指数退避
# 重新执行路由
result = self._execute_route(message, context)
if result == RouteResult.FAILED and context.retry_count < context.max_retries:
# 继续重试
context.retry_count += 1
self.retry_queue.put((message, context))
self.retry_queue.task_done()
except queue.Empty:
continue
except Exception as e:
print(f"重试工作线程异常: {e}")
print("重试工作线程结束")
def _execute_route(self, message: Dict[str, Any], context: RouteContext) -> RouteResult:
"""执行消息路由"""
try:
# 获取路由处理器
handler = self.route_handlers.get(context.message_type)
if not handler:
print(f"未知消息类型: {context.message_type}")
return RouteResult.FAILED
# 执行路由
result = handler(message, context)
# 更新统计
with self.stats_lock:
if result == RouteResult.SUCCESS:
self.route_stats['successful_routes'] += 1
else:
self.route_stats['failed_routes'] += 1
return result
except Exception as e:
print(f"路由执行异常: {e}")
return RouteResult.FAILED
def _route_group_message(self, message: Dict[str, Any], context: RouteContext) -> RouteResult:
"""路由群组消息"""
if not context.target_group:
print("群组消息缺少目标群组")
return RouteResult.FAILED
# 获取群组成员
members = self.group_manager.get_group_members(context.target_group)
if not members:
print(f"群组 {context.target_group} 没有成员")
return RouteResult.NO_RECIPIENTS
# 过滤在线成员(排除发送者)
online_members = [
member for member in members
if member.user_id != context.sender_id and member.is_online
]
if not online_members:
print(f"群组 {context.target_group} 没有在线成员")
return RouteResult.NO_RECIPIENTS
# 广播消息
success_count = 0
total_count = len(online_members)
for member in online_members:
if self._send_to_user(member.user_id, message):
success_count += 1
else:
# 存储离线消息
self._store_offline_message(member.user_id, message)
# 判断路由结果
if success_count == total_count:
return RouteResult.SUCCESS
elif success_count > 0:
return RouteResult.PARTIAL_SUCCESS
else:
return RouteResult.FAILED
def _route_private_message(self, message: Dict[str, Any], context: RouteContext) -> RouteResult:
"""路由私聊消息"""
if not context.target_user:
print("私聊消息缺少目标用户")
return RouteResult.FAILED
# 检查目标用户是否在线
if self.connection_manager.is_user_online(context.target_user):
if self._send_to_user(context.target_user, message):
return RouteResult.SUCCESS
else:
# 发送失败,存储离线消息
self._store_offline_message(context.target_user, message)
return RouteResult.FAILED
else:
# 用户离线,存储消息
self._store_offline_message(context.target_user, message)
return RouteResult.SUCCESS # 离线存储成功也算成功
def _route_system_message(self, message: Dict[str, Any], context: RouteContext) -> RouteResult:
"""路由系统消息"""
# 系统消息可以发送给特定用户或群组
if context.target_user:
return self._route_private_message(message, context)
elif context.target_group:
return self._route_group_message(message, context)
else:
# 发送给所有在线用户
return self._route_broadcast_message(message, context)
def _route_broadcast_message(self, message: Dict[str, Any], context: RouteContext) -> RouteResult:
"""路由广播消息"""
online_users = self.connection_manager.get_online_users()
if not online_users:
return RouteResult.NO_RECIPIENTS
success_count = 0
total_count = len(online_users)
for user_id in online_users:
if user_id != context.sender_id: # 不发送给发送者
if self._send_to_user(user_id, message):
success_count += 1
# 判断路由结果
if success_count == total_count - (1 if context.sender_id in online_users else 0):
return RouteResult.SUCCESS
elif success_count > 0:
return RouteResult.PARTIAL_SUCCESS
else:
return RouteResult.FAILED
def _route_notification(self, message: Dict[str, Any], context: RouteContext) -> RouteResult:
"""路由通知消息"""
# 通知消息通常发送给特定用户
if context.target_user:
return self._route_private_message(message, context)
else:
print("通知消息缺少目标用户")
return RouteResult.FAILED
def _send_to_user(self, user_id: int, message: Dict[str, Any]) -> bool:
"""发送消息给指定用户"""
try:
# 获取用户连接
connection = self.connection_manager.get_user_connection(user_id)
if not connection:
return False
# 序列化消息
message_data = json.dumps(message, ensure_ascii=False)
message_bytes = message_data.encode('utf-8')
# 添加长度头
import struct
length_header = struct.pack('!I', len(message_bytes))
# 发送消息
connection.send(length_header + message_bytes)
return True
except Exception as e:
print(f"发送消息给用户 {user_id} 失败: {e}")
return False
def _store_offline_message(self, user_id: int, message: Dict[str, Any]):
"""存储离线消息"""
if self.offline_storage:
try:
self.offline_storage.store_message(user_id, message)
with self.stats_lock:
self.route_stats['offline_stored'] += 1
print(f"离线消息已存储给用户 {user_id}")
except Exception as e:
print(f"存储离线消息失败: {e}")
def get_route_stats(self) -> Dict[str, Any]:
"""获取路由统计信息"""
with self.stats_lock:
return self.route_stats.copy()
def clear_stats(self):
"""清除统计信息"""
with self.stats_lock:
self.route_stats = {
'total_messages': 0,
'successful_routes': 0,
'failed_routes': 0,
'offline_stored': 0
}
class OfflineMessageStorage:
"""
离线消息存储
负责存储和管理离线用户的消息
"""
def __init__(self, db_connection):
self.db = db_connection
self._create_offline_table()
def _create_offline_table(self):
"""创建离线消息表"""
try:
cursor = self.db.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS offline_messages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
message_data TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
is_delivered BOOLEAN DEFAULT 0
)
""")
cursor.execute("""
CREATE INDEX IF NOT EXISTS idx_offline_user
ON offline_messages(user_id, is_delivered)
""")
self.db.commit()
except Exception as e:
print(f"创建离线消息表失败: {e}")
def store_message(self, user_id: int, message: Dict[str, Any]):
"""存储离线消息"""
try:
cursor = self.db.cursor()
message_data = json.dumps(message, ensure_ascii=False)
cursor.execute("""
INSERT INTO offline_messages (user_id, message_data)
VALUES (?, ?)
""", (user_id, message_data))
self.db.commit()
except Exception as e:
print(f"存储离线消息失败: {e}")
def get_offline_messages(self, user_id: int, limit: int = 100) -> List[Dict[str, Any]]:
"""获取用户的离线消息"""
try:
cursor = self.db.cursor()
cursor.execute("""
SELECT id, message_data, created_at
FROM offline_messages
WHERE user_id = ? AND is_delivered = 0
ORDER BY created_at ASC
LIMIT ?
""", (user_id, limit))
messages = []
message_ids = []
for row in cursor.fetchall():
try:
message = json.loads(row['message_data'])
message['offline_id'] = row['id']
message['offline_time'] = row['created_at']
messages.append(message)
message_ids.append(row['id'])
except json.JSONDecodeError:
print(f"解析离线消息失败: {row['id']}")
# 标记消息为已投递
if message_ids:
placeholders = ','.join('?' * len(message_ids))
cursor.execute(f"""
UPDATE offline_messages
SET is_delivered = 1
WHERE id IN ({placeholders})
""", message_ids)
self.db.commit()
return messages
except Exception as e:
print(f"获取离线消息失败: {e}")
return []
def cleanup_delivered_messages(self, days_old: int = 7):
"""清理已投递的旧消息"""
try:
cursor = self.db.cursor()
cursor.execute("""
DELETE FROM offline_messages
WHERE is_delivered = 1
AND created_at < datetime('now', '-{} days')
""".format(days_old))
deleted_count = cursor.rowcount
self.db.commit()
print(f"清理了 {deleted_count} 条已投递的离线消息")
except Exception as e:
print(f"清理离线消息失败: {e}")
# 消息路由工厂
class MessageRouterFactory:
"""消息路由工厂"""
@staticmethod
def create_group_message_context(sender_id: int, group_id: int,
message_id: str = None) -> RouteContext:
"""创建群组消息路由上下文"""
if not message_id:
import uuid
message_id = str(uuid.uuid4())
return RouteContext(
message_id=message_id,
sender_id=sender_id,
message_type=MessageType.GROUP_MESSAGE,
target_group=group_id,
priority=1
)
@staticmethod
def create_private_message_context(sender_id: int, target_user_id: int,
message_id: str = None) -> RouteContext:
"""创建私聊消息路由上下文"""
if not message_id:
import uuid
message_id = str(uuid.uuid4())
return RouteContext(
message_id=message_id,
sender_id=sender_id,
message_type=MessageType.PRIVATE_MESSAGE,
target_user=target_user_id,
priority=2
)
@staticmethod
def create_system_message_context(message_id: str = None,
target_user: int = None,
target_group: int = None) -> RouteContext:
"""创建系统消息路由上下文"""
if not message_id:
import uuid
message_id = str(uuid.uuid4())
return RouteContext(
message_id=message_id,
sender_id=0, # 系统消息
message_type=MessageType.SYSTEM_MESSAGE,
target_user=target_user,
target_group=target_group,
priority=0 # 最高优先级
)
# 使用示例
def demo_message_routing():
"""消息路由演示"""
print("=== 消息路由演示 ===")
# 模拟组件
class MockGroupManager:
def get_group_members(self, group_id):
# 模拟群组成员
from dataclasses import dataclass
@dataclass
class MockMember:
user_id: int
is_online: bool
return [
MockMember(2, True),
MockMember(3, True),
MockMember(4, False)
]
class MockConnectionManager:
def __init__(self):
self.online_users = {2, 3}
def is_user_online(self, user_id):
return user_id in self.online_users
def get_user_connection(self, user_id):
if user_id in self.online_users:
return MockConnection()
return None
def get_online_users(self):
return list(self.online_users)
class MockConnection:
def send(self, data):
print(f"发送数据: {len(data)} 字节")
# 创建路由器
group_manager = MockGroupManager()
connection_manager = MockConnectionManager()
router = MessageRouter(group_manager, connection_manager)
# 启动路由服务
router.start_routing()
# 创建群组消息
message = {
"type": "group_message",
"content": "Hello everyone!",
"sender_name": "Alice",
"timestamp": time.time()
}
context = MessageRouterFactory.create_group_message_context(1, 1)
# 路由消息
result = router.route_message(message, context)
print(f"群组消息路由结果: {result}")
# 等待路由完成
time.sleep(1)
# 查看统计
stats = router.get_route_stats()
print(f"路由统计: {stats}")
# 停止路由服务
router.stop_routing()
if __name__ == "__main__":
demo_message_routing()
```
## 🔄 消息路由优化
### 性能优化策略
```mermaid
graph TD
subgraph "路由性能优化"
A[消息缓存
Message Cache] --> A1[内存缓存
Memory Cache]
A --> A2[Redis缓存
Redis Cache]
B[连接池管理
Connection Pool] --> B1[连接复用
Connection Reuse]
B --> B2[负载均衡
Load Balance]
C[异步处理
Async Processing] --> C1[协程并发
Coroutine Concurrency]
C --> C2[任务队列
Task Queue]
D[路由算法
Routing Algorithm] --> D1[哈希路由
Hash Routing]
D --> D2[权重路由
Weighted Routing]
end
style A fill:#e8f5e8
style B fill:#fff3cd
style C fill:#f8d7da
style D fill:#d1ecf1
```
### 路由监控和诊断
```python
# 路由性能监控
class RouteMonitor:
"""路由性能监控器"""
def __init__(self):
self.metrics = {
'total_messages': 0,
'success_rate': 0.0,
'average_latency': 0.0,
'error_count': 0
}
def record_route_success(self, latency):
"""记录路由成功"""
self.metrics['total_messages'] += 1
# 更新平均延迟
current_avg = self.metrics['average_latency']
total = self.metrics['total_messages']
self.metrics['average_latency'] = (current_avg * (total - 1) + latency) / total
def record_route_error(self):
"""记录路由错误"""
self.metrics['error_count'] += 1
self.metrics['total_messages'] += 1
def get_success_rate(self):
"""获取成功率"""
if self.metrics['total_messages'] == 0:
return 0.0
success_count = self.metrics['total_messages'] - self.metrics['error_count']
return success_count / self.metrics['total_messages']
```
## 📋 学习检查清单
完成本节学习后,请确认您能够:
- [ ] 理解消息路由系统的架构设计
- [ ] 实现群组消息和私聊消息的路由
- [ ] 处理离线消息存储和投递
- [ ] 设计消息路由的重试机制
- [ ] 实现路由性能监控
- [ ] 优化消息路由的性能
- [ ] 处理路由过程中的异常情况
- [ ] 设计可扩展的路由架构
## 🚀 下一步
完成消息路由学习后,请继续学习:
- [群组管理系统](group-management.md) - 群组功能实现
- [状态管理](state-management.md) - 系统状态维护
- [第6章:数据库集成](README.md)
---
## 📖 导航
⬅️ **上一节:** [Group Management](group-management.md)
➡️ **下一节:** [Concurrent Handling](concurrent-handling.md)
📚 **返回:** [第9章:多人聊天](README.md)
🏠 **主页:** [学习路径总览](../README.md)
**高效的消息路由是聊天系统的核心,确保消息准确快速地传达给目标用户!** 📨