class AsyncSocketManager:
"""异步Socket管理器 - 协调Reader和Writer线程"""
# 分批连接参数
BATCH_SIZE = 250 # 每批处理的连接数
BATCH_INTERVAL = 20.0 # 批次间隔时间(秒)
MAX_RETRIES = 5 # 增加最大重试次数
RECONNECT_INITIAL_DELAY = 20.0 # 初始重连延迟
RECONNECT_BACKOFF_FACTOR = 1.5 # 指数退避因子
MAX_RECONNECT_DELAY = 120.0 # 最大重连延迟(2分钟)
HEALTH_CHECK_INTERVAL = 5.0 # 健康检查间隔
def __init__(self):
self.running = True
self._shutdown_event = asyncio.Event()
self._cleanup_lock = asyncio.Lock()
self.shutdown_requested = asyncio.Event()
self.original_sigint_handler = None
self.shutdown_requested = asyncio.Event()
self.server_config = {'ip': 'charge.b-conn.com', 'port': 5455}
self.total = 0
self.writer = None
self.reader = None
self._active_tasks = set()
self.connections: Dict[str, Dict] = {}
self.batch_tasks: List[asyncio.Task] = [] # 批次任务列表
self._read_lock = asyncio.Lock() # 读取操作专用锁
self._write_lock = asyncio.Lock() # 写入操作专用锁
self._state_lock = asyncio.Lock() # 状态变更锁
self.logger = logging.getLogger(__name__)
self.setup_logging()
self.connection_pool = {} # 连接池
# 连接状态统计
self.stats = {
"success": 0,
"failed": 0,
"pending": 0,
"active": 0
}
# 每个连接一个专用的状态锁
self.connection_locks = defaultdict(asyncio.Lock)
def install_signal_handlers(self):
"""安装信号处理器"""
try:
loop = asyncio.get_running_loop()
# 保存原始信号处理器
self.original_sigint_handler = loop.add_signal_handler(
signal.SIGINT,
self._initiate_shutdown
)
self.logger.info("✅ 信号处理器安装完成")
except (NotImplementedError, RuntimeError):
# 在某些平台上可能不支持
self.logger.warning("⚠️ 当前平台不支持异步信号处理")
def _initiate_shutdown(self):
"""初始化关闭过程"""
self.logger.info("🔄 接收到关闭信号,开始优雅关闭...")
self.shutdown_requested.set()
def restore_signal_handlers(self):
"""恢复原始信号处理器"""
try:
loop = asyncio.get_running_loop()
if self.original_sigint_handler is not None:
loop.remove_signal_handler(signal.SIGINT)
# 重新安装默认处理器
loop.add_signal_handler(signal.SIGINT, signal.default_int_handler)
except Exception as e:
self.logger.debug(f"恢复信号处理器时发生异常: {e}")
async def close_all_connections(self):
"""异步关闭所有Socket连接"""
self.logger.info("正在关闭所有Socket连接...")
self.running = False
self._shutdown_event.set()
# 取消所有活跃任务
for task in self._active_tasks:
if not task.done():
task.cancel()
# 关闭所有连接
close_tasks = []
for pileCode in list(self.connections.keys()):
conn = self.connections[pileCode]
if 'writer' in conn and conn['writer'] is not None:
writer = conn['writer']
close_tasks.append(self.close_writer(writer, pileCode))
if close_tasks:
await asyncio.gather(*close_tasks, return_exceptions=True)
self.logger.info("所有连接已关闭")
async def close_writer(self, writer, pileCode: str):
"""安全关闭写入器"""
try:
if not writer.is_closing():
writer.close()
await writer.wait_closed()
self.logger.debug(f"关闭连接: {pileCode}")
except Exception as e:
self.logger.error(f"关闭连接失败: {pileCode} 错误: {e}")
async def _close_single_connection(self, conn_key: str):
"""关闭单个Socket连接"""
try:
if conn_key in self.connections:
conn_info = self.connections[conn_key]
# 实际的Socket关闭逻辑
if 'writer' in conn_info:
writer = conn_info['writer']
writer.close()
await writer.wait_closed()
self.logger.debug(f"🔌 关闭连接 {conn_key}")
del self.connections[conn_key]
except Exception as e:
self.logger.error(f"❌ 关闭连接 {conn_key} 时发生异常: {e}")
async def batch_create_connections(self, pile_codes: List[str]):
"""添加健康监控器"""
# 启动健康监控器
health_task = asyncio.create_task(
self.connection_health_monitor(),
name="connection_health_monitor"
)
self.batch_tasks.append(health_task)
"""批量创建Socket连接(分批处理)"""
total = len(pile_codes)
batches = math.ceil(total / self.BATCH_SIZE)
self.total_connections = total
self.logger.info(f"开始分批创建连接,共{total}个桩号,分{batches}批处理,每批{self.BATCH_SIZE}个")
for batch_num in range(batches):
if self.shutdown_requested.is_set():
self.logger.warning("关闭信号已触发,停止创建新批次")
break
start_idx = batch_num * self.BATCH_SIZE
end_idx = min(start_idx + self.BATCH_SIZE, total)
batch = pile_codes[start_idx:end_idx]
batch_id = batch_num + 1
self.logger.info(f"处理批次 {batch_id}/{batches} ({len(batch)}个桩号)")
# 创建批量连接任务
task = asyncio.create_task(
self.process_batch(batch, batch_id),
name=f"batch_{batch_id}"
)
self.batch_tasks.append(task)
# 添加批次间隔
if batch_num < batches - 1:
await asyncio.sleep(self.BATCH_INTERVAL)
# 等待所有批次完成
if self.batch_tasks:
await asyncio.gather(*self.batch_tasks)
self.logger.info(f"所有批次处理完成! 成功:{self.stats['success']} 失败:{self.stats['failed']}")
async def process_batch(self, pile_codes: List[str], batch_id: int):
"""处理单个批次的连接"""
tasks = []
for i, pileCode in enumerate(pile_codes):
message_id = f"batch{batch_id}-conn{i + 1}"
task = asyncio.create_task(
self.socket_worker(message_id, pileCode),
name=f"batch{batch_id}-{pileCode}"
)
tasks.append(task)
self.connection_pool[pileCode] = {"status": "pending"}
self.stats["pending"] += 1
# 等待批次内所有连接完成初始化
results = await asyncio.gather(*tasks, return_exceptions=True)
# 统计批次结果
for result in results:
if isinstance(result, Exception):
self.stats["failed"] += 1
else:
self.stats["success"] += 1
self.stats["pending"] -= len(pile_codes)
self.stats["active"] = len(self.connections)
async def socket_worker(self, message_id: str, pileCode: str):
global logger
"""异步Socket工作协程"""
retry_count = 0
last_reconnect_time = 0
# health_check_last = time.time()
while self.running and not self._shutdown_event.is_set():
try:
# 检查是否需要等待重连延迟
current_time = time.time()
if last_reconnect_time > 0:
delay = self.calculate_reconnect_delay(retry_count)
if current_time - last_reconnect_time < delay:
await asyncio.sleep(delay - (current_time - last_reconnect_time))
# 尝试创建连接
success = await self.create_socket_connection_with_retry(message_id, pileCode, retry_count)
if not success:
retry_count = min(retry_count + 1, self.MAX_RETRIES)
last_reconnect_time = time.time()
continue
# 重置重试计数(连接成功)
retry_count = 0
last_reconnect_time = 0
# 启动读写任务
await self.start_reader_writer(message_id, pileCode)
# 健康检查和状态报告
# current_time = time.time()
# if current_time - health_check_last > self.HEALTH_CHECK_INTERVAL:
# pending_count = self.stats["pending"]
# self.logger.info(
# f"等待中={pending_count} 重试={retry_count}"
# )
# health_check_last = current_time
except asyncio.CancelledError:
self.logger.info(f"⚠️ Socket工作协程被取消: {message_id}")
if pileCode in self.connections:
conn = self.connections[pileCode]
if 'writer' in conn and conn['writer'] is not None:
writer = conn['writer']
try:
if not writer.is_closing():
writer.close()
await writer.wait_closed()
except Exception:
pass
if pileCode in self.connections:
del self.connections[pileCode]
break
except Exception as e:
self.logger.error(f"❌ Socket工作异常: {message_id} 桩号: {pileCode} 错误: {e}")
retry_count = min(retry_count + 1, self.MAX_RETRIES)
last_reconnect_time = time.time()
# 清理当前连接
if pileCode in self.connections:
conn = self.connections[pileCode]
if 'writer' in conn and conn['writer'] is not None:
writer = conn['writer']
try:
if not writer.is_closing():
writer.close()
await writer.wait_closed()
except Exception:
pass
if pileCode in self.connections:
del self.connections[pileCode]
await asyncio.sleep(1) # 短暂暂停
finally:
# 更新连接池状态
if pileCode in self.connection_pool:
self.connection_pool[pileCode] = {
"status": "retrying" if retry_count > 0 else "active",
"retry_count": retry_count,
"last_retry": last_reconnect_time
}
def calculate_reconnect_delay(self, retry_count: int) -> float:
"""计算指数退避的重连延迟"""
delay = self.RECONNECT_INITIAL_DELAY * (self.RECONNECT_BACKOFF_FACTOR ** retry_count)
return min(delay, self.MAX_RECONNECT_DELAY)
def handle_task_completion(self, task: asyncio.Task):
"""任务完成回调处理"""
try:
# 首先检查任务是否被取消
if task.cancelled():
self.logger.debug(f"任务 {task.get_name()} 被取消")
return
if task.done() and task.exception():
self.logger.error(f"任务失败: {task.get_name()} {task.exception()}")
# 标记连接需要重启
pile_code = task.get_name().split("_")[1]
if pile_code in self.connections:
writer = self.connections[pile_code]["writer"]
if writer is not None:
writer.close()
if pile_code in self.connections:
del self.connections[pile_code]
except Exception as e:
self.logger.error(f"任务回调处理错误: {e}")
async def connection_health_monitor(self):
"""连接健康监控器"""
while self.running and not self._shutdown_event.is_set():
try:
real_active_count = 0
current_time = time.time()
connections_to_restart = []
for pile_code, conn in list(self.connections.items()):
# 检查是否超过最大重试次数
if conn.get("retry_count", 0) >= self.MAX_RETRIES:
self.logger.warning(f"❌ 连接超过最大重试次数: {pile_code}")
await self._close_single_connection(pile_code)
continue
if conn.get("isLogin") is True:
real_active_count += 1
# 检查最后活动时间
last_activity = conn.get("last_time_stamp", 0)
if current_time - last_activity > 60: # 1分钟无活动
self.logger.warning(f"桩号: {pile_code} 上次报文时间: {last_activity} 当前时间: {current_time} 时间间隔: {current_time - last_activity}")
self.logger.warning(f"🕒 连接无活动超时: {pile_code}")
connections_to_restart.append(pile_code)
# 检查是否需要重启
if conn.get("needs_restart", False):
connections_to_restart.append(pile_code)
# 重启需要重置的连接
for pile_code in connections_to_restart:
if pile_code in self.connections:
conn = self.connections[pile_code]
if conn['isLogin'] and conn['isLogin'] is not None:
connections_to_restart.remove(pile_code)
continue
else:
await self._close_single_connection(pile_code)
# 重新启动工作协程
self.logger.info(f"🔄 重启连接: {pile_code}")
task = asyncio.create_task(
self.socket_worker(f"restart-{pile_code}", pile_code),
name=f"restart_{pile_code}"
)
self.batch_tasks.append(task)
await asyncio.sleep(1)
# 定期报告
restart_count = len(connections_to_restart)
self.logger.info(
f"🔍 健康检查: 活跃连接={real_active_count} 待重启={restart_count}"
)
await asyncio.sleep(5)
except Exception as e:
self.logger.error(f"健康监控器异常: {e}")
await asyncio.sleep(10)
async def create_socket_connection_with_retry(self, message_id: str, pileCode: str, attempt: int) -> bool:
"""带重试机制的连接创建"""
try:
reader, writer = await asyncio.wait_for(
asyncio.open_connection(self.server_config['ip'], self.server_config['port']),
timeout=30.0 + min(attempt * 5, 30) # 随重试次数增加超时
)
async with self.connection_locks[pileCode]:
if pileCode not in self.connections:
self.connections[pileCode] = {
'reader': reader,
'writer': writer,
'pileCode': pileCode,
'message_id': message_id,
'retry_count': 0,
'last_heartbeat1': 0,
'last_heartbeat2': 0,
'last_time_stamp': 0,
'isLogin': False,
'timeout_count_login': 0,
'timeout_count': 0,
'heart_serialNum': "00 00",
'status': 'Offline',
'charging': False,
'priceModelList': []
}
self.logger.info(f"连接成功: {message_id} 桩号: {pileCode}")
return True
except asyncio.TimeoutError:
self.logger.warning(f"⛔ 连接超时: {message_id} 桩号: {pileCode} (尝试 #{attempt + 1})")
if pileCode in self.connections:
conn = self.connections[pileCode]
if 'writer' in conn and conn['writer'] is not None:
writer = conn['writer']
try:
if not writer.is_closing():
writer.close()
await writer.wait_closed()
except Exception:
pass
if pileCode in self.connections:
del self.connections[pileCode]
await asyncio.sleep(1) # 短暂暂停
except ConnectionRefusedError:
self.logger.warning(f"🚫 连接拒绝: {message_id} 桩号: {pileCode} (尝试 #{attempt + 1})")
if pileCode in self.connections:
conn = self.connections[pileCode]
if 'writer' in conn and conn['writer'] is not None:
writer = conn['writer']
try:
if not writer.is_closing():
writer.close()
await writer.wait_closed()
except Exception:
pass
if pileCode in self.connections:
del self.connections[pileCode]
await asyncio.sleep(1) # 短暂暂停
except Exception as e:
self.logger.error(
f"❌连接错误: {message_id} 桩号: {pileCode} 错误: {e} (尝试 #{attempt + 1})")
if pileCode in self.connections:
conn = self.connections[pileCode]
if 'writer' in conn and conn['writer'] is not None:
writer = conn['writer']
try:
if not writer.is_closing():
writer.close()
await writer.wait_closed()
except Exception:
pass
if pileCode in self.connections:
del self.connections[pileCode]
await asyncio.sleep(1) # 短暂暂停
return False
async def start_reader_writer(self, message_id: str, pileCode: str):
"""启动读写任务"""
if pileCode not in self.connections:
return
conn = self.connections[pileCode]
reader = conn['reader']
writer = conn['writer']
self.writer, self.reader = AsyncSocketWriter(writer, pileCode, message_id, self.total,
self.connections, self.connection_locks, self.logger), AsyncSocketReader(
reader, pileCode, message_id, self.connections, self.connection_locks, self.logger)
# 创建读写任务
read_task = asyncio.create_task(
self.reader.start_reading(),
name=f"reader_{pileCode}_{message_id}"
)
write_task = asyncio.create_task(
self.writer.start_write(),
name=f"writer_{pileCode}_{message_id}"
)
# 添加完成回调
read_task.add_done_callback(self.handle_task_completion)
write_task.add_done_callback(self.handle_task_completion)
# 添加到活跃任务集合
self._active_tasks.add(read_task)
self._active_tasks.add(write_task)
# 并行运行读写任务
try:
await asyncio.gather(
read_task,
write_task,
return_exceptions=True
)
except Exception as e:
self.logger.error(f"读写任务异常: {message_id} {pileCode}: {e}")
有无问题?