""" Socket客户端 """ import socket import json import time from voice_drone.core.configuration import SYSTEM_SOCKET_SERVER_CONFIG from voice_drone.core.command import Command from voice_drone.logging_ import get_logger logger = get_logger("socket.client") class SocketClient: # 初始化Socket客户端 def __init__(self, config: dict): self.host = config.get("host") self.port = config.get("port") self.connect_timeout = config.get("connect_timeout") self.send_timeout = config.get("send_timeout") self.reconnect_interval = float(config.get("reconnect_interval") or 3.0) # max_retries:-1 表示断线后持续重连并发送,直到成功(不视为致命错误) _mr = config.get("max_retries", -1) try: self.max_reconnect_attempts = int(_mr) except (TypeError, ValueError): self.max_reconnect_attempts = -1 self.sock = None self.connected = False # 连接到socket服务器 def connect(self) -> bool: if self.connected and self.sock is not None: return True try: self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.sock.settimeout(self.connect_timeout) self.sock.connect((self.host, self.port)) self.sock.settimeout(self.send_timeout) self.connected = True print( f"[SocketClient] 连接成功: {self.host}:{self.port}", flush=True, ) logger.info("Socket 已连接 %s:%s", self.host, self.port) return True except socket.timeout: logger.warning( "Socket 连接超时 host=%s port=%s timeout=%s", self.host, self.port, self.connect_timeout, ) print( "[SocketClient] connect: 连接超时 " f"(host={self.host!r}, port={self.port!r}, timeout={self.connect_timeout!r})", flush=True, ) self._cleanup() return False except ConnectionRefusedError as e: logger.warning("Socket 连接被拒绝: %s", e) print( f"[SocketClient] connect: 连接被拒绝: {e!r}", flush=True, ) self._cleanup() return False except OSError as e: logger.warning("Socket connect OSError (%s): %s", type(e).__name__, e) print( f"[SocketClient] connect: OSError ({type(e).__name__}): {e!r}", flush=True, ) self._cleanup() return False except Exception as e: print( f"[SocketClient] connect: 未预期异常 ({type(e).__name__}): {e!r}", flush=True, ) self._cleanup() return False # 断开与socket服务器的连接 def disconnect(self) -> None: self._cleanup() # 清理资源 def _cleanup(self) -> None: if self.sock is not None: try: self.sock.close() except Exception: pass self.sock = None self.connected = False # 确保连接已建立 def _ensure_connected(self) -> bool: if self.connected and self.sock is not None: return True return self.connect() # 发送命令 def send_command(self, command) -> bool: print("[SocketClient] 正在发送命令…", flush=True) if not self._ensure_connected(): logger.warning( "Socket 未连接且 connect 失败,跳过本次发送 host=%s port=%s", self.host, self.port, ) print( "[SocketClient] 未连接或 connect 失败,跳过发送", flush=True, ) return False try: command_dict = command.to_dict() json_str = json.dumps(command_dict, ensure_ascii=False) # 添加换行符(根据 JSON格式说明.md,命令以换行符分隔) message = json_str + "\n" # 发送数据 self.sock.sendall(message.encode("utf-8")) print("[SocketClient] sendall 成功", flush=True) return True except socket.timeout: logger.warning("Socket send 超时,将断开以便重连") print("[SocketClient] send_command: socket 超时", flush=True) self._cleanup() return False except ConnectionResetError as e: logger.warning("Socket 连接被重置(将重连): %s", e) print( f"[SocketClient] send_command: 连接被重置 ({type(e).__name__}): {e!r}", flush=True, ) self._cleanup() return False except BrokenPipeError as e: logger.warning("Socket 管道破裂(将重连): %s", e) print( f"[SocketClient] send_command: 管道破裂 ({type(e).__name__}): {e!r}", flush=True, ) self._cleanup() return False except OSError as e: # 断网、对端关闭等:可恢复,不当作未捕获致命错误 logger.warning("Socket send OSError (%s): %s(将重连)", type(e).__name__, e) print( f"[SocketClient] send_command: OSError ({type(e).__name__}): {e!r}", flush=True, ) self._cleanup() return False except Exception as e: logger.warning( "Socket send 异常 (%s): %s(将重连)", type(e).__name__, e ) print( f"[SocketClient] send_command: 异常 ({type(e).__name__}): {e!r}", flush=True, ) self._cleanup() return False # 发送命令并重试 def send_command_with_retry(self, command) -> bool: """失败后清理连接并按 reconnect_interval 重试;max_retries=-1 时直到发送成功。""" unlimited = self.max_reconnect_attempts < 0 cap = max(1, self.max_reconnect_attempts) if not unlimited else None attempt = 0 while True: attempt += 1 self._cleanup() if self.send_command(command): if attempt > 1: print( f"[SocketClient] 重试后发送成功(第 {attempt} 次)", flush=True, ) logger.info("Socket 重连后命令已发送(第 %s 次尝试)", attempt) return True if not unlimited and cap is not None and attempt >= cap: logger.warning( "Socket 已达 max_retries=%s,本次命令未送达,稍后可再试", self.max_reconnect_attempts, ) print( "[SocketClient] 已达最大重试次数,本次命令未送达(可稍后重试)", flush=True, ) return False # 无限重试时每 10 次打一条日志,避免刷屏 if unlimited and attempt % 10 == 1: logger.warning( "Socket 发送失败,%ss 后第 %s 次重连重试…", self.reconnect_interval, attempt, ) print( f"[SocketClient] 发送失败,{self.reconnect_interval}s 后重试 " f"(第 {attempt} 次)", flush=True, ) time.sleep(self.reconnect_interval) # 上下文管理器入口 def __enter__(self): self.connect() return self # 上下文管理器出口 def __exit__(self, exc_type, exc_val, exc_tb): self.disconnect() if __name__ == "__main__": from voice_drone.core.configuration import SYSTEM_SOCKET_SERVER_CONFIG from voice_drone.core.command import Command config = SYSTEM_SOCKET_SERVER_CONFIG client = SocketClient(config) client.connect() command = Command.create("takeoff", 1) client.send_command(command) client.disconnect()