refactor: 重构底层代码,抽离conn,调整消息处理器并创建传输层接口。

feature: 支持mqtt非桥接版本。
This commit is contained in:
caixypromise
2025-09-14 03:00:50 +08:00
parent d04ec9d510
commit 1ba556988f
44 changed files with 6455 additions and 66 deletions
@@ -0,0 +1,313 @@
import asyncio
import socket
import time
from typing import Dict, Any, Set
from config.logger import setup_logging
from core.protocols.mqtt_connection import MQTTConnection
from core.transport.mqtt_transport import MQTTTransport, UDPAudioHandler
from core.services.connection_service import ConnectionService
logger = setup_logging()
class MQTTServer:
"""
原生MQTT服务器:直接处理MQTT协议连接
集成到xiaozhi-server架构中
"""
def __init__(self, config: Dict[str, Any]):
self.config = config
self.logger = setup_logging()
# 服务器配置
server_config = config.get('mqtt_server', {})
self.mqtt_port = server_config.get('port', 1883)
self.udp_port = server_config.get('udp_port', self.mqtt_port)
self.host = server_config.get('host', '0.0.0.0')
self.public_ip = server_config.get('public_ip', 'localhost')
# 连接管理
self.connections: Dict[int, MQTTConnection] = {}
self.udp_handlers: Dict[int, UDPAudioHandler] = {}
self.connection_id_counter = 0
# 服务器实例
self.mqtt_server = None
self.udp_server = None
# 连接服务
self.connection_service = ConnectionService(config)
# 活跃连接管理
self.active_transports: Set[MQTTTransport] = set()
# 心跳检查
self.heartbeat_task = None
self.heartbeat_interval = 30 # 30秒检查一次
async def start(self):
"""启动MQTT服务器"""
try:
# 启动MQTT TCP服务器
await self._start_mqtt_server()
# 启动UDP服务器
await self._start_udp_server()
# 启动心跳检查
self.heartbeat_task = asyncio.create_task(self._heartbeat_check())
logger.info(f"MQTT服务器启动成功: {self.host}:{self.mqtt_port}")
logger.info(f"UDP服务器启动成功: {self.host}:{self.udp_port}")
except Exception as e:
logger.error(f"启动MQTT服务器失败: {e}")
raise
async def _start_mqtt_server(self):
"""启动MQTT TCP服务器"""
self.mqtt_server = await asyncio.start_server(
self._handle_mqtt_connection,
self.host,
self.mqtt_port
)
async def _start_udp_server(self):
"""启动UDP服务器"""
loop = asyncio.get_event_loop()
# 创建UDP socket
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind((self.host, self.udp_port))
sock.setblocking(False)
# 创建UDP协议处理器
transport, protocol = await loop.create_datagram_endpoint(
lambda: UDPProtocol(self),
sock=sock
)
self.udp_server = (transport, protocol)
async def _handle_mqtt_connection(self, reader, writer):
"""处理新的MQTT连接"""
connection_id = self._generate_connection_id()
try:
# 获取客户端地址
client_addr = writer.get_extra_info('peername')
logger.info(f"新MQTT连接: {client_addr}, connection_id: {connection_id}")
# 创建MQTT连接处理器
mqtt_connection = MQTTConnection(
writer.get_extra_info('socket'),
connection_id,
self
)
# 创建UDP音频处理器
udp_handler = UDPAudioHandler(
connection_id,
self,
{} # 加密配置将在hello消息中设置
)
# 创建MQTT传输层
transport = MQTTTransport(mqtt_connection, udp_handler)
# 注册连接
self.connections[connection_id] = mqtt_connection
self.udp_handlers[connection_id] = udp_handler
self.active_transports.add(transport)
# 提取连接头信息
headers = {
'x-real-ip': client_addr[0] if client_addr else 'unknown',
'connection-type': 'mqtt'
}
try:
# 使用ConnectionService处理连接
await self.connection_service.handle_connection(transport, headers)
except Exception as e:
logger.error(f"ConnectionService处理MQTT连接失败: {e}")
except Exception as e:
logger.error(f"处理MQTT连接失败: {e}")
finally:
# 清理连接
await self._cleanup_connection(connection_id)
async def _cleanup_connection(self, connection_id: int):
"""清理连接资源"""
try:
# 移除连接
if connection_id in self.connections:
connection = self.connections.pop(connection_id)
await connection.close()
# 移除UDP处理器
if connection_id in self.udp_handlers:
udp_handler = self.udp_handlers.pop(connection_id)
await udp_handler.close()
# 移除传输层(通过连接ID查找)
transports_to_remove = []
for transport in self.active_transports:
if hasattr(transport, '_mqtt_connection') and \
transport._mqtt_connection.connection_id == connection_id:
transports_to_remove.append(transport)
for transport in transports_to_remove:
self.active_transports.discard(transport)
await transport.close()
logger.info(f"MQTT连接清理完成: {connection_id}")
except Exception as e:
logger.error(f"清理MQTT连接失败: {e}")
def _generate_connection_id(self) -> int:
"""生成连接ID"""
self.connection_id_counter += 1
return self.connection_id_counter
async def on_client_connected(self, mqtt_connection: MQTTConnection):
"""客户端连接回调"""
logger.info(f"MQTT客户端已连接: {mqtt_connection.client_id}")
async def on_client_disconnected(self, mqtt_connection: MQTTConnection):
"""客户端断开连接回调"""
logger.info(f"MQTT客户端已断开: {mqtt_connection.client_id}")
async def send_udp_message(self, data: bytes, remote_addr: tuple):
"""发送UDP消息"""
if self.udp_server:
transport, protocol = self.udp_server
transport.sendto(data, remote_addr)
async def send_encrypted_audio(self, connection_id: int, audio_data: bytes,
timestamp: int, remote_addr: tuple, encryption_config: Dict[str, Any]):
"""发送加密音频数据"""
try:
# 这里应该实现音频数据加密逻辑
# 暂时直接发送原始数据
header = self._generate_udp_header(connection_id, len(audio_data), timestamp, 0)
message = header + audio_data
await self.send_udp_message(message, remote_addr)
except Exception as e:
logger.error(f"发送加密音频失败: {e}")
def _generate_udp_header(self, connection_id: int, length: int, timestamp: int, sequence: int) -> bytes:
"""生成UDP消息头"""
header = bytearray(16)
header[0] = 1 # type
header[2:4] = length.to_bytes(2, 'big') # payload length
header[4:8] = connection_id.to_bytes(4, 'big') # connection id
header[8:12] = timestamp.to_bytes(4, 'big') # timestamp
header[12:16] = sequence.to_bytes(4, 'big') # sequence
return bytes(header)
async def _heartbeat_check(self):
"""心跳检查任务"""
try:
while True:
await asyncio.sleep(self.heartbeat_interval)
# 检查所有连接的状态
dead_connections = []
for connection_id, connection in self.connections.items():
if not connection.is_connected():
dead_connections.append(connection_id)
# 清理死连接
for connection_id in dead_connections:
logger.info(f"清理死连接: {connection_id}")
await self._cleanup_connection(connection_id)
# 记录活跃连接数
active_count = len(self.connections)
if active_count > 0:
logger.info(f"MQTT活跃连接数: {active_count}")
except asyncio.CancelledError:
pass
except Exception as e:
logger.error(f"心跳检查任务出错: {e}")
async def stop(self):
"""停止MQTT服务器"""
logger.info("正在停止MQTT服务器...")
# 停止心跳检查
if self.heartbeat_task and not self.heartbeat_task.done():
self.heartbeat_task.cancel()
try:
await self.heartbeat_task
except asyncio.CancelledError:
pass
# 关闭所有连接
for connection_id in list(self.connections.keys()):
await self._cleanup_connection(connection_id)
# 关闭UDP服务器
if self.udp_server:
transport, protocol = self.udp_server
transport.close()
# 关闭MQTT服务器
if self.mqtt_server:
self.mqtt_server.close()
await self.mqtt_server.wait_closed()
logger.info("MQTT服务器已停止")
def get_server_status(self) -> Dict[str, Any]:
"""获取服务器状态"""
return {
'type': 'mqtt',
'host': self.host,
'mqtt_port': self.mqtt_port,
'udp_port': self.udp_port,
'active_connections': len(self.connections),
'active_transports': len(self.active_transports)
}
class UDPProtocol(asyncio.DatagramProtocol):
"""UDP协议处理器"""
def __init__(self, mqtt_server: MQTTServer):
self.mqtt_server = mqtt_server
self.transport = None
def connection_made(self, transport):
self.transport = transport
def datagram_received(self, data: bytes, addr: tuple):
"""接收UDP数据报"""
try:
# 解析UDP消息头
if len(data) < 16:
return
connection_id = int.from_bytes(data[4:8], 'big')
timestamp = int.from_bytes(data[8:12], 'big')
sequence = int.from_bytes(data[12:16], 'big')
payload = data[16:]
# 找到对应的UDP处理器
udp_handler = self.mqtt_server.udp_handlers.get(connection_id)
if udp_handler:
udp_handler.on_udp_message(payload, timestamp, addr)
except Exception as e:
logger.error(f"处理UDP数据报失败: {e}")
def error_received(self, exc):
logger.error(f"UDP协议错误: {exc}")
@@ -0,0 +1,318 @@
import asyncio
from typing import Dict, Any, List, Optional
from config.logger import setup_logging
from core.websocket_server_new import NewWebSocketServer
from core.servers.mqtt_server import MQTTServer
logger = setup_logging()
class MultiProtocolServer:
"""
多协议服务器管理器:统一管理WebSocket和MQTT服务器
提供统一的启动、停止和状态监控接口
"""
def __init__(self, config: Dict[str, Any]):
self.config = config
self.logger = setup_logging()
# 服务器实例
self.servers: Dict[str, Any] = {}
self.server_tasks: Dict[str, asyncio.Task] = {}
# 服务器状态
self.is_running = False
self.startup_complete = False
# 初始化服务器
self._initialize_servers()
def _initialize_servers(self):
"""初始化所有协议服务器"""
try:
# 检查配置中启用的协议
enabled_protocols = self.config.get('enabled_protocols', ['websocket'])
# 初始化WebSocket服务器
if 'websocket' in enabled_protocols:
self.servers['websocket'] = NewWebSocketServer(self.config)
logger.info("WebSocket服务器已初始化")
# 初始化MQTT服务器
if 'mqtt' in enabled_protocols:
self.servers['mqtt'] = MQTTServer(self.config)
logger.info("MQTT服务器已初始化")
if not self.servers:
logger.warning("没有启用任何协议服务器")
except Exception as e:
logger.error(f"初始化服务器失败: {e}")
raise
async def start(self):
"""启动所有服务器"""
if self.is_running:
logger.warning("服务器已经在运行中")
return
try:
logger.info("开始启动多协议服务器...")
self.is_running = True
# 启动所有服务器
for protocol, server in self.servers.items():
try:
logger.info(f"启动{protocol}服务器...")
task = asyncio.create_task(server.start())
self.server_tasks[protocol] = task
# 等待一小段时间确保服务器启动
await asyncio.sleep(0.1)
logger.info(f"{protocol}服务器启动成功")
except Exception as e:
logger.error(f"启动{protocol}服务器失败: {e}")
# 继续启动其他服务器
continue
self.startup_complete = True
logger.info("多协议服务器启动完成")
# 启动监控任务
asyncio.create_task(self._monitor_servers())
# 等待所有服务器任务
if self.server_tasks:
await asyncio.gather(*self.server_tasks.values(), return_exceptions=True)
except Exception as e:
logger.error(f"启动多协议服务器失败: {e}")
await self.stop()
raise
async def stop(self):
"""停止所有服务器"""
if not self.is_running:
return
logger.info("开始停止多协议服务器...")
self.is_running = False
# 停止所有服务器
for protocol, server in self.servers.items():
try:
logger.info(f"停止{protocol}服务器...")
await server.stop()
logger.info(f"{protocol}服务器已停止")
except Exception as e:
logger.error(f"停止{protocol}服务器失败: {e}")
# 取消所有服务器任务
for protocol, task in self.server_tasks.items():
if not task.done():
task.cancel()
try:
await task
except asyncio.CancelledError:
pass
self.server_tasks.clear()
logger.info("多协议服务器已停止")
async def restart(self):
"""重启所有服务器"""
logger.info("重启多协议服务器...")
await self.stop()
await asyncio.sleep(1) # 等待清理完成
await self.start()
async def restart_server(self, protocol: str):
"""重启指定协议的服务器"""
if protocol not in self.servers:
logger.error(f"未找到协议服务器: {protocol}")
return False
try:
logger.info(f"重启{protocol}服务器...")
# 停止指定服务器
server = self.servers[protocol]
await server.stop()
# 取消任务
if protocol in self.server_tasks:
task = self.server_tasks[protocol]
if not task.done():
task.cancel()
try:
await task
except asyncio.CancelledError:
pass
# 重新启动
task = asyncio.create_task(server.start())
self.server_tasks[protocol] = task
logger.info(f"{protocol}服务器重启成功")
return True
except Exception as e:
logger.error(f"重启{protocol}服务器失败: {e}")
return False
async def update_config(self, new_config: Dict[str, Any]):
"""更新配置"""
try:
logger.info("更新多协议服务器配置...")
# 检查配置变化
config_changed = self._check_config_changes(self.config, new_config)
# 更新配置
self.config = new_config
# 如果配置有重大变化,重新初始化服务器
if config_changed:
logger.info("配置有重大变化,重新初始化服务器...")
await self.stop()
self._initialize_servers()
if self.is_running:
await self.start()
else:
# 更新各个服务器的配置
for protocol, server in self.servers.items():
if hasattr(server, 'update_config'):
await server.update_config(new_config)
logger.info("配置更新完成")
return True
except Exception as e:
logger.error(f"更新配置失败: {e}")
return False
def _check_config_changes(self, old_config: Dict[str, Any], new_config: Dict[str, Any]) -> bool:
"""检查配置是否有重大变化"""
# 检查启用的协议是否变化
old_protocols = set(old_config.get('enabled_protocols', ['websocket']))
new_protocols = set(new_config.get('enabled_protocols', ['websocket']))
if old_protocols != new_protocols:
logger.info(f"启用协议发生变化: {old_protocols} -> {new_protocols}")
return True
# 检查服务器端口配置
server_configs = ['server', 'mqtt_server']
for config_key in server_configs:
old_server_config = old_config.get(config_key, {})
new_server_config = new_config.get(config_key, {})
# 检查端口和主机配置
for key in ['port', 'host', 'ip']:
if old_server_config.get(key) != new_server_config.get(key):
logger.info(f"服务器配置{config_key}.{key}发生变化")
return True
return False
async def _monitor_servers(self):
"""监控服务器状态"""
try:
while self.is_running:
await asyncio.sleep(30) # 每30秒检查一次
# 检查服务器任务状态
for protocol, task in self.server_tasks.items():
if task.done():
exception = task.exception()
if exception:
logger.error(f"{protocol}服务器异常退出: {exception}")
# 尝试重启服务器
await self.restart_server(protocol)
except asyncio.CancelledError:
pass
except Exception as e:
logger.error(f"服务器监控任务出错: {e}")
def get_server_status(self) -> Dict[str, Any]:
"""获取所有服务器状态"""
status = {
'is_running': self.is_running,
'startup_complete': self.startup_complete,
'enabled_protocols': list(self.servers.keys()),
'servers': {}
}
# 获取各个服务器的状态
for protocol, server in self.servers.items():
try:
if hasattr(server, 'get_server_status'):
server_status = server.get_server_status()
else:
server_status = {'type': protocol, 'status': 'unknown'}
# 添加任务状态
task = self.server_tasks.get(protocol)
if task:
server_status['task_status'] = 'running' if not task.done() else 'stopped'
if task.done() and task.exception():
server_status['task_error'] = str(task.exception())
status['servers'][protocol] = server_status
except Exception as e:
status['servers'][protocol] = {
'type': protocol,
'status': 'error',
'error': str(e)
}
return status
def get_active_connections_count(self) -> Dict[str, int]:
"""获取各协议的活跃连接数"""
connections = {}
for protocol, server in self.servers.items():
try:
if hasattr(server, 'get_active_connections_count'):
connections[protocol] = server.get_active_connections_count()
elif hasattr(server, 'connections'):
connections[protocol] = len(server.connections)
else:
connections[protocol] = 0
except Exception as e:
logger.error(f"获取{protocol}连接数失败: {e}")
connections[protocol] = -1
return connections
async def broadcast_message(self, message: Dict[str, Any], protocol: Optional[str] = None):
"""向所有连接广播消息"""
try:
if protocol:
# 向指定协议广播
if protocol in self.servers:
server = self.servers[protocol]
if hasattr(server, 'broadcast_message'):
await server.broadcast_message(message)
else:
# 向所有协议广播
for server in self.servers.values():
if hasattr(server, 'broadcast_message'):
await server.broadcast_message(message)
except Exception as e:
logger.error(f"广播消息失败: {e}")
def get_supported_protocols(self) -> List[str]:
"""获取支持的协议列表"""
return ['websocket', 'mqtt']
def is_protocol_enabled(self, protocol: str) -> bool:
"""检查协议是否启用"""
return protocol in self.servers