"""UDP 通信层:socket 管理、收发线程、按消息 ID 分发。 线程安全:start/stop/reconfigure 可随时调用; 收包在独立线程解析,解析成功后回调 on_feedback / on_command, 原始帧经 on_raw 回调(用于转发/透传)。 """ import socket import threading import time from . import protocol class UdpComm: def __init__(self, logger=None): self.logger = logger self.sock = None self.running = False self._thread = None self._lock = threading.Lock() # 回调 self.on_feedback = None # callable(msg, addr) self.on_command = None # callable(msg, addr) self.on_raw = None # callable(data, addr) self.on_unknown = None # callable(data, addr) 解析失败/未知ID # 统计 self.rx_count = 0 self.tx_count = 0 self.last_rx_time = None def bind(self, ip, port): """创建并绑定监听 socket(带超时 + 地址复用)。失败抛 OSError。""" s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.settimeout(0.1) s.bind((ip, int(port))) with self._lock: old = self.sock self.sock = s if old: old.close() return self @property def bound(self): return self.sock is not None def start(self): if self.running: return if self.sock is None: raise RuntimeError("socket 未绑定") self.running = True self._thread = threading.Thread(target=self._recv_loop, daemon=True, name="udp-recv") self._thread.start() def stop(self): self.running = False t = self._thread self._thread = None if t: t.join(timeout=1.5) def _recv_loop(self): while self.running: try: data, addr = self.sock.recvfrom(2048) except socket.timeout: continue except OSError: break self.rx_count += 1 self.last_rx_time = time.time() try: self._dispatch(data, addr) except Exception as e: if self.logger: self.logger.error(f"分发消息异常: {e}") def _dispatch(self, data, addr): if self.on_raw: self.on_raw(data, addr) msg_id = protocol.msg_id_of(data) cls = protocol.ALL_MESSAGES.get(msg_id) msg = None if cls: msg = cls() if not msg.unpack(data): msg = None if msg is None: if self.on_unknown: self.on_unknown(data, addr) return if msg_id in protocol.CMD_MESSAGES: if self.on_command: self.on_command(msg, addr) else: if self.on_feedback: self.on_feedback(msg, addr) def sendto(self, data, ip, port): """发送原始字节。返回 True/False。""" if self.sock is None: return False try: self.sock.sendto(data, (ip, int(port))) self.tx_count += 1 return True except OSError as e: if self.logger: self.logger.error(f"发送到 {ip}:{port} 失败: {e}") return False def send_message(self, msg, ip, port): return self.sendto(msg.pack(), ip, port)