Coverage for src\p2p_network.py: 0%
203 statements
« prev ^ index » next coverage.py v7.3.4, created at 2026-04-16 16:31 +0800
« prev ^ index » next coverage.py v7.3.4, created at 2026-04-16 16:31 +0800
1"""
2P2P Network Module - P2P网络优化模块
4提供连接池管理、断线重连、消息队列等功能
5"""
7import asyncio
8import logging
9import time
10from typing import Dict, Optional, Set
11from dataclasses import dataclass, field
12from enum import Enum
13from collections import deque
14import uuid
16logger = logging.getLogger(__name__)
19class ConnectionState(Enum):
20 """连接状态"""
21 DISCONNECTED = "disconnected"
22 CONNECTING = "connecting"
23 CONNECTED = "connected"
24 RECONNECTING = "reconnecting"
25 FAILED = "failed"
28@dataclass
29class ConnectionInfo:
30 """连接信息"""
31 peer_id: str
32 peer_name: str
33 state: ConnectionState = ConnectionState.DISCONNECTED
34 connected_at: float = 0
35 last_heartbeat: float = 0
36 reconnect_attempts: int = 0
37 max_reconnect_attempts: int = 5
38 latency_ms: float = 0
41@dataclass
42class Message:
43 """消息结构"""
44 msg_id: str
45 msg_type: str
46 payload: dict
47 created_at: float = field(default_factory=time.time)
48 retry_count: int = 0
49 max_retries: int = 3
52class ConnectionPool:
53 """连接池管理器"""
55 def __init__(self, max_connections: int = 10):
56 self.max_connections = max_connections
57 self.connections: Dict[str, ConnectionInfo] = {}
58 self.connection_lock = asyncio.Lock()
59 self._callbacks: Dict[str, callable] = {}
61 async def add_connection(self, peer_id: str, peer_name: str) -> ConnectionInfo:
62 """添加新连接"""
63 async with self.connection_lock:
64 if peer_id in self.connections:
65 return self.connections[peer_id]
67 if len(self.connections) >= self.max_connections:
68 # 移除最老的连接
69 oldest = min(
70 self.connections.values(),
71 key=lambda x: x.connected_at
72 )
73 await self.remove_connection(oldest.peer_id)
75 conn_info = ConnectionInfo(
76 peer_id=peer_id,
77 peer_name=peer_name,
78 state=ConnectionState.CONNECTING,
79 connected_at=time.time()
80 )
81 self.connections[peer_id] = conn_info
82 logger.info(f"添加连接到池: {peer_id}")
83 return conn_info
85 async def update_state(self, peer_id: str, state: ConnectionState) -> None:
86 """更新连接状态"""
87 async with self.connection_lock:
88 if peer_id in self.connections:
89 self.connections[peer_id].state = state
90 if state == ConnectionState.CONNECTED:
91 self.connections[peer_id].connected_at = time.time()
92 self.connections[peer_id].reconnect_attempts = 0
94 async def remove_connection(self, peer_id: str) -> None:
95 """移除连接"""
96 async with self.connection_lock:
97 if peer_id in self.connections:
98 del self.connections[peer_id]
99 logger.info(f"从池中移除连接: {peer_id}")
101 async def get_connection(self, peer_id: str) -> Optional[ConnectionInfo]:
102 """获取连接信息"""
103 return self.connections.get(peer_id)
105 async def get_all_connections(self) -> Dict[str, ConnectionInfo]:
106 """获取所有连接"""
107 return self.connections.copy()
109 def register_callback(self, event: str, callback: callable) -> None:
110 """注册状态变化回调"""
111 self._callbacks[event] = callback
113 async def trigger_callback(self, event: str, *args, **kwargs) -> None:
114 """触发回调"""
115 if event in self._callbacks:
116 try:
117 await self._callbacks[event](*args, **kwargs)
118 except Exception as e:
119 logger.error(f"回调执行失败: {e}")
122class ReconnectionManager:
123 """断线重连管理器"""
125 def __init__(self, base_delay: float = 1.0, max_delay: float = 60.0):
126 self.base_delay = base_delay
127 self.max_delay = max_delay
128 self._reconnect_tasks: Dict[str, asyncio.Task] = {}
129 self._running = False
131 def calculate_delay(self, attempt: int) -> float:
132 """计算重连延迟(指数退避)"""
133 delay = min(self.base_delay * (2 ** attempt), self.max_delay)
134 # 添加随机抖动
135 import random
136 return delay * (0.5 + random.random() * 0.5)
138 async def start_reconnect(
139 self,
140 peer_id: str,
141 connect_func: callable,
142 max_attempts: int = 5
143 ) -> bool:
144 """启动重连"""
145 if peer_id in self._reconnect_tasks:
146 logger.warning(f"重连任务已存在: {peer_id}")
147 return False
149 self._running = True
150 task = asyncio.create_task(
151 self._reconnect_loop(peer_id, connect_func, max_attempts)
152 )
153 self._reconnect_tasks[peer_id] = task
154 return True
156 async def stop_reconnect(self, peer_id: str) -> None:
157 """停止重连"""
158 if peer_id in self._reconnect_tasks:
159 self._reconnect_tasks[peer_id].cancel()
160 del self._reconnect_tasks[peer_id]
161 logger.info(f"停止重连任务: {peer_id}")
163 async def _reconnect_loop(
164 self,
165 peer_id: str,
166 connect_func: callable,
167 max_attempts: int
168 ) -> None:
169 """重连循环"""
170 attempt = 0
171 while attempt < max_attempts and self._running:
172 delay = self.calculate_delay(attempt)
173 logger.info(f"等待 {delay:.1f}秒后重连 {peer_id} (尝试 {attempt + 1}/{max_attempts})")
175 await asyncio.sleep(delay)
177 try:
178 success = await connect_func(peer_id)
179 if success:
180 logger.info(f"重连成功: {peer_id}")
181 return
182 except Exception as e:
183 logger.error(f"重连失败: {peer_id}, {e}")
185 attempt += 1
187 logger.warning(f"重连次数耗尽: {peer_id}")
189 def stop_all(self) -> None:
190 """停止所有重连任务"""
191 self._running = False
192 for task in self._reconnect_tasks.values():
193 task.cancel()
194 self._reconnect_tasks.clear()
197class MessageQueue:
198 """消息队列"""
200 def __init__(self, max_size: int = 1000):
201 self.max_size = max_size
202 self._queue: deque = deque(maxlen=max_size)
203 self._pending: Dict[str, Message] = {}
204 self._lock = asyncio.Lock()
205 self._callbacks: Dict[str, callable] = {}
207 async def enqueue(self, msg_type: str, payload: dict) -> str:
208 """入队消息"""
209 async with self._lock:
210 msg_id = str(uuid.uuid4())
211 msg = Message(msg_id=msg_id, msg_type=msg_type, payload=payload)
212 self._queue.append(msg)
213 self._pending[msg_id] = msg
214 logger.debug(f"消息入队: {msg_id}, 类型: {msg_type}")
215 return msg_id
217 async def dequeue(self) -> Optional[Message]:
218 """出队消息"""
219 async with self._lock:
220 if self._queue:
221 msg = self._queue.popleft()
222 return msg
223 return None
225 async def get_pending(self, msg_id: str) -> Optional[Message]:
226 """获取待确认消息"""
227 return self._pending.get(msg_id)
229 async def confirm(self, msg_id: str) -> bool:
230 """确认消息已送达"""
231 async with self._lock:
232 if msg_id in self._pending:
233 del self._pending[msg_id]
234 logger.debug(f"消息已确认: {msg_id}")
235 return True
236 return False
238 async def retry(self, msg_id: str) -> bool:
239 """重试消息"""
240 async with self._lock:
241 if msg_id in self._pending:
242 msg = self._pending[msg_id]
243 if msg.retry_count < msg.max_retries:
244 msg.retry_count += 1
245 self._queue.append(msg)
246 logger.info(f"消息重试: {msg_id}, 次数: {msg.retry_count}")
247 return True
248 else:
249 del self._pending[msg_id]
250 logger.warning(f"消息重试次数耗尽: {msg_id}")
251 return False
253 async def size(self) -> int:
254 """获取队列大小"""
255 return len(self._queue)
257 async def pending_count(self) -> int:
258 """获取待确认消息数"""
259 return len(self._pending)
262class P2PNetworkManager:
263 """P2P网络管理器(整合模块)"""
265 def __init__(self):
266 self.connection_pool = ConnectionPool()
267 self.reconnect_manager = ReconnectionManager()
268 self.message_queue = MessageQueue()
269 self._running = False
271 async def start(self) -> None:
272 """启动P2P网络"""
273 self._running = True
274 logger.info("P2P网络管理器已启动")
276 async def stop(self) -> None:
277 """停止P2P网络"""
278 self._running = False
279 self.reconnect_manager.stop_all()
280 logger.info("P2P网络管理器已停止")
282 async def connect(self, peer_id: str, peer_name: str) -> bool:
283 """连接到对等节点"""
284 conn_info = await self.connection_pool.add_connection(peer_id, peer_name)
286 try:
287 # 模拟连接建立
288 await asyncio.sleep(0.1)
289 await self.connection_pool.update_state(peer_id, ConnectionState.CONNECTED)
290 conn_info.last_heartbeat = time.time()
291 logger.info(f"已连接到: {peer_id}")
292 return True
293 except Exception as e:
294 logger.error(f"连接失败: {peer_id}, {e}")
295 await self.connection_pool.update_state(peer_id, ConnectionState.FAILED)
296 return False
298 async def disconnect(self, peer_id: str) -> None:
299 """断开连接"""
300 self.reconnect_manager.stop_reconnect(peer_id)
301 await self.connection_pool.remove_connection(peer_id)
302 logger.info(f"已断开: {peer_id}")
304 async def send_message(self, peer_id: str, msg_type: str, payload: dict) -> Optional[str]:
305 """发送消息"""
306 conn_info = await self.connection_pool.get_connection(peer_id)
307 if not conn_info or conn_info.state != ConnectionState.CONNECTED:
308 # 消息入队等待发送
309 return await self.message_queue.enqueue(msg_type, payload)
311 msg_id = await self.message_queue.enqueue(msg_type, payload)
312 # 模拟发送
313 await asyncio.sleep(0.01)
314 await self.message_queue.confirm(msg_id)
315 return msg_id
317 async def get_status(self) -> dict:
318 """获取网络状态"""
319 connections = await self.connection_pool.get_all_connections()
320 return {
321 "running": self._running,
322 "connections": {
323 peer_id: {
324 "state": conn.state.value,
325 "latency_ms": conn.latency_ms,
326 "last_heartbeat": conn.last_heartbeat
327 }
328 for peer_id, conn in connections.items()
329 },
330 "queue_size": await self.message_queue.size(),
331 "pending_messages": await self.message_queue.pending_count()
332 }