forked from moyin/whale-town-front
- 修复连接状态检测时机问题
- 修复聊天消息格式为 {t: chat, content, scope}
- 添加 _send_login_message 函数
- 移除空消息心跳避免服务器错误
462 lines
14 KiB
GDScript
462 lines
14 KiB
GDScript
extends Node
|
||
|
||
# ============================================================================
|
||
# WebSocketManager.gd - WebSocket 连接生命周期管理(原生 WebSocket 版本)
|
||
# ============================================================================
|
||
# 管理 WebSocket 连接状态、自动重连和错误恢复
|
||
#
|
||
# 核心职责:
|
||
# - 连接状态管理(断开、连接中、已连接、重连中)
|
||
# - 自动重连机制(指数退避)
|
||
# - 连接错误恢复
|
||
# - WebSocket 消息发送/接收
|
||
# ============================================================================
|
||
# 使用方式:
|
||
# WebSocketManager.connect_to_game_server()
|
||
# WebSocketManager.connection_state_changed.connect(_on_state_changed)
|
||
#
|
||
# 注意事项:
|
||
# - 作为自动加载单例,全局可访问
|
||
# - 自动处理连接断开和重连
|
||
# - 通过信号通知连接状态变化
|
||
# ============================================================================
|
||
|
||
class_name WebSocketManager
|
||
|
||
# ============================================================================
|
||
# 信号定义
|
||
# ============================================================================
|
||
|
||
# 连接状态变化信号
|
||
# 参数:
|
||
# new_state: ConnectionState - 新的连接状态
|
||
signal connection_state_changed(new_state: ConnectionState)
|
||
|
||
# 连接丢失信号
|
||
signal connection_lost()
|
||
|
||
# 重连成功信号
|
||
signal reconnection_succeeded()
|
||
|
||
# 重连失败信号
|
||
# 参数:
|
||
# attempt: int - 当前重连尝试次数
|
||
# max_attempts: int - 最大重连次数
|
||
signal reconnection_failed(attempt: int, max_attempts: int)
|
||
|
||
# WebSocket 消息接收信号
|
||
# 参数:
|
||
# message: String - 接收到的消息内容(JSON 字符串)
|
||
signal data_received(message: String)
|
||
|
||
# ============================================================================
|
||
# 枚举定义
|
||
# ============================================================================
|
||
|
||
# 连接状态枚举
|
||
enum ConnectionState {
|
||
DISCONNECTED, # 未连接
|
||
CONNECTING, # 连接中
|
||
CONNECTED, # 已连接
|
||
RECONNECTING, # 重连中
|
||
ERROR # 错误状态
|
||
}
|
||
|
||
# ============================================================================
|
||
# 常量定义
|
||
# ============================================================================
|
||
|
||
# WebSocket 服务器 URL(原生 WebSocket)
|
||
const WEBSOCKET_URL: String = "wss://whaletownend.xinghangee.icu/game"
|
||
|
||
# 默认最大重连次数
|
||
const DEFAULT_MAX_RECONNECT_ATTEMPTS: int = 5
|
||
|
||
# 默认重连基础延迟(秒)
|
||
const DEFAULT_RECONNECT_BASE_DELAY: float = 3.0
|
||
|
||
# 最大重连延迟(秒)
|
||
const MAX_RECONNECT_DELAY: float = 30.0
|
||
|
||
# ============================================================================
|
||
# 成员变量
|
||
# ============================================================================
|
||
|
||
# WebSocket peer
|
||
var _websocket_peer: WebSocketPeer = WebSocketPeer.new()
|
||
|
||
# 当前连接状态
|
||
var _connection_state: ConnectionState = ConnectionState.DISCONNECTED
|
||
|
||
# 自动重连启用标志
|
||
var _auto_reconnect_enabled: bool = true
|
||
|
||
# 最大重连次数
|
||
var _max_reconnect_attempts: int = DEFAULT_MAX_RECONNECT_ATTEMPTS
|
||
|
||
# 重连基础延迟
|
||
var _reconnect_base_delay: float = DEFAULT_RECONNECT_BASE_DELAY
|
||
|
||
# 当前重连尝试次数
|
||
var _reconnect_attempt: int = 0
|
||
|
||
# 重连定时器
|
||
var _reconnect_timer: Timer = Timer.new()
|
||
|
||
# 是否为正常关闭(非异常断开)
|
||
var _clean_close: bool = true
|
||
|
||
# 心跳定时器
|
||
var _heartbeat_timer: Timer = Timer.new()
|
||
|
||
# 心跳间隔(秒)
|
||
const HEARTBEAT_INTERVAL: float = 30.0
|
||
|
||
# ============================================================================
|
||
# 生命周期方法
|
||
# ============================================================================
|
||
|
||
# 初始化
|
||
func _ready() -> void:
|
||
print("WebSocketManager 初始化完成")
|
||
|
||
# 设置重连定时器
|
||
_setup_reconnect_timer()
|
||
|
||
# 设置心跳定时器
|
||
_setup_heartbeat_timer()
|
||
|
||
# 启动处理循环
|
||
set_process(true)
|
||
|
||
# 处理每帧
|
||
func _process(_delta: float) -> void:
|
||
# 检查 WebSocket 状态变化
|
||
_check_websocket_state()
|
||
|
||
var state: WebSocketPeer.State = _websocket_peer.get_ready_state()
|
||
|
||
# 调试:打印状态变化
|
||
if _connection_state == ConnectionState.CONNECTING:
|
||
var peer_state_name = ["DISCONNECTED", "CONNECTING", "OPEN", "CLOSING", "CLOSED"][state]
|
||
print("📡 WebSocket 状态: peer=%s, manager=%s" % [peer_state_name, ConnectionState.keys()[_connection_state]])
|
||
|
||
if state == WebSocketPeer.STATE_OPEN:
|
||
# 接收数据
|
||
_websocket_peer.poll()
|
||
|
||
# 处理收到的数据
|
||
while _websocket_peer.get_available_packet_count() > 0:
|
||
var packet: PackedByteArray = _websocket_peer.get_packet()
|
||
var message: String = packet.get_string_from_utf8()
|
||
|
||
# 发射消息接收信号
|
||
data_received.emit(message)
|
||
|
||
# 打印调试信息
|
||
print("📨 WebSocket 收到消息: ", message)
|
||
|
||
# 清理
|
||
func _exit_tree() -> void:
|
||
_disconnect()
|
||
|
||
if is_instance_valid(_reconnect_timer):
|
||
_reconnect_timer.stop()
|
||
_reconnect_timer.queue_free()
|
||
|
||
if is_instance_valid(_heartbeat_timer):
|
||
_heartbeat_timer.stop()
|
||
_heartbeat_timer.queue_free()
|
||
|
||
# ============================================================================
|
||
# 公共 API - 连接管理
|
||
# ============================================================================
|
||
|
||
# 连接到游戏服务器
|
||
func connect_to_game_server() -> void:
|
||
if _connection_state == ConnectionState.CONNECTED or _connection_state == ConnectionState.CONNECTING:
|
||
push_warning("已经在连接或已连接状态")
|
||
return
|
||
|
||
print("=== WebSocketManager 开始连接 ===")
|
||
print("服务器 URL: ", WEBSOCKET_URL)
|
||
print("WebSocket 连接中...")
|
||
|
||
_set_connection_state(ConnectionState.CONNECTING)
|
||
_clean_close = true
|
||
_reconnect_attempt = 0
|
||
|
||
var err: Error = _websocket_peer.connect_to_url(WEBSOCKET_URL)
|
||
if err != OK:
|
||
print("❌ WebSocket 连接失败: ", error_string(err))
|
||
_set_connection_state(ConnectionState.ERROR)
|
||
return
|
||
|
||
# 启动心跳
|
||
_start_heartbeat()
|
||
|
||
# 断开 WebSocket 连接
|
||
func disconnect_websocket() -> void:
|
||
print("=== WebSocketManager 断开连接 ===")
|
||
_disconnect()
|
||
|
||
# 断开连接(内部方法)
|
||
func _disconnect() -> void:
|
||
_clean_close = true
|
||
|
||
# 停止重连定时器
|
||
_reconnect_timer.stop()
|
||
|
||
# 停止心跳
|
||
_heartbeat_timer.stop()
|
||
|
||
# 关闭 WebSocket
|
||
if _websocket_peer.get_ready_state() == WebSocketPeer.STATE_OPEN:
|
||
_websocket_peer.close()
|
||
|
||
_set_connection_state(ConnectionState.DISCONNECTED)
|
||
|
||
# 检查 WebSocket 是否已连接
|
||
#
|
||
# 返回值:
|
||
# bool - WebSocket 是否已连接
|
||
func is_websocket_connected() -> bool:
|
||
return _connection_state == ConnectionState.CONNECTED
|
||
|
||
# 获取当前连接状态
|
||
#
|
||
# 返回值:
|
||
# ConnectionState - 当前连接状态
|
||
func get_connection_state() -> ConnectionState:
|
||
return _connection_state
|
||
|
||
# ============================================================================
|
||
# 公共 API - 消息发送
|
||
# ============================================================================
|
||
|
||
# 发送 WebSocket 消息
|
||
#
|
||
# 参数:
|
||
# message: String - 要发送的消息内容(JSON 字符串)
|
||
#
|
||
# 返回值:
|
||
# Error - 错误码,OK 表示成功
|
||
func send_message(message: String) -> Error:
|
||
if _websocket_peer.get_ready_state() != WebSocketPeer.STATE_OPEN:
|
||
print("❌ WebSocket 未连接,无法发送消息")
|
||
return ERR_UNCONFIGURED
|
||
|
||
var err: Error = _websocket_peer.send_text(message)
|
||
if err != OK:
|
||
print("❌ WebSocket 发送消息失败: ", error_string(err))
|
||
return err
|
||
|
||
print("📤 发送 WebSocket 消息: ", message)
|
||
return OK
|
||
|
||
# ============================================================================
|
||
# 公共 API - 自动重连
|
||
# ============================================================================
|
||
|
||
# 启用/禁用自动重连
|
||
#
|
||
# 参数:
|
||
# enabled: bool - 是否启用自动重连
|
||
# max_attempts: int - 最大重连次数(默认 5)
|
||
# base_delay: float - 基础重连延迟,秒(默认 3.0)
|
||
#
|
||
# 使用示例:
|
||
# WebSocketManager.enable_auto_reconnect(true, 5, 3.0)
|
||
func enable_auto_reconnect(enabled: bool, max_attempts: int = DEFAULT_MAX_RECONNECT_ATTEMPTS, base_delay: float = DEFAULT_RECONNECT_BASE_DELAY) -> void:
|
||
_auto_reconnect_enabled = enabled
|
||
_max_reconnect_attempts = max_attempts
|
||
_reconnect_base_delay = base_delay
|
||
|
||
print("自动重连: ", "启用" if enabled else "禁用")
|
||
print("最大重连次数: ", _max_reconnect_attempts)
|
||
print("基础重连延迟: ", _reconnect_base_delay, " 秒")
|
||
|
||
# 获取重连信息
|
||
#
|
||
# 返回值:
|
||
# Dictionary - 重连信息 {enabled, attempt, max_attempts, delay}
|
||
func get_reconnect_info() -> Dictionary:
|
||
return {
|
||
"enabled": _auto_reconnect_enabled,
|
||
"attempt": _reconnect_attempt,
|
||
"max_attempts": _max_reconnect_attempts,
|
||
"next_delay": _calculate_reconnect_delay() if _connection_state == ConnectionState.RECONNECTING else 0.0
|
||
}
|
||
|
||
# ============================================================================
|
||
# 内部方法 - 连接状态管理
|
||
# ============================================================================
|
||
|
||
# 设置连接状态
|
||
func _set_connection_state(new_state: ConnectionState) -> void:
|
||
if _connection_state == new_state:
|
||
return
|
||
|
||
_connection_state = new_state
|
||
print("📡 连接状态变更: ", ConnectionState.keys()[new_state])
|
||
|
||
# 发射信号
|
||
connection_state_changed.emit(new_state)
|
||
|
||
# ============================================================================
|
||
# 内部方法 - WebSocket 状态监控
|
||
# ============================================================================
|
||
|
||
# 检查 WebSocket 状态变化
|
||
func _check_websocket_state() -> void:
|
||
# 必须先 poll 才能获取最新状态
|
||
_websocket_peer.poll()
|
||
|
||
var state: WebSocketPeer.State = _websocket_peer.get_ready_state()
|
||
|
||
match state:
|
||
WebSocketPeer.STATE_CONNECTING:
|
||
# 正在连接
|
||
if _connection_state != ConnectionState.CONNECTING and _connection_state != ConnectionState.RECONNECTING:
|
||
_set_connection_state(ConnectionState.CONNECTING)
|
||
|
||
WebSocketPeer.STATE_OPEN:
|
||
# 连接成功
|
||
if _connection_state != ConnectionState.CONNECTED:
|
||
_on_websocket_connected()
|
||
|
||
WebSocketPeer.STATE_CLOSING:
|
||
# 正在关闭
|
||
pass
|
||
|
||
WebSocketPeer.STATE_CLOSED:
|
||
# 连接关闭
|
||
var code: int = _websocket_peer.get_close_code()
|
||
var reason: String = _websocket_peer.get_close_reason()
|
||
print("🔌 WebSocket 关闭: code=%d, reason=%s" % [code, reason])
|
||
_on_websocket_closed(code != 0) # code=0 表示正常关闭
|
||
|
||
# WebSocket 连接成功处理
|
||
func _on_websocket_connected() -> void:
|
||
print("✅ WebSocketManager: WebSocket 连接成功")
|
||
|
||
# 如果是重连,发射重连成功信号
|
||
if _connection_state == ConnectionState.RECONNECTING:
|
||
_reconnect_attempt = 0
|
||
reconnection_succeeded.emit()
|
||
print("🔄 重连成功")
|
||
|
||
_set_connection_state(ConnectionState.CONNECTED)
|
||
|
||
# WebSocket 连接关闭处理
|
||
func _on_websocket_closed(clean_close: bool) -> void:
|
||
print("🔌 WebSocketManager: WebSocket 连接断开")
|
||
print(" 正常关闭: ", clean_close)
|
||
|
||
_clean_close = clean_close
|
||
|
||
# 如果是异常断开且启用了自动重连
|
||
if not clean_close and _auto_reconnect_enabled:
|
||
connection_lost.emit()
|
||
_attempt_reconnect()
|
||
else:
|
||
_set_connection_state(ConnectionState.DISCONNECTED)
|
||
|
||
# ============================================================================
|
||
# 内部方法 - 重连机制
|
||
# ============================================================================
|
||
|
||
# 设置重连定时器
|
||
func _setup_reconnect_timer() -> void:
|
||
_reconnect_timer = Timer.new()
|
||
_reconnect_timer.one_shot = true
|
||
_reconnect_timer.autostart = false
|
||
add_child(_reconnect_timer)
|
||
|
||
_reconnect_timer.timeout.connect(_on_reconnect_timeout)
|
||
|
||
# 尝试重连
|
||
func _attempt_reconnect() -> void:
|
||
# 检查是否超过最大重连次数
|
||
if _reconnect_attempt >= _max_reconnect_attempts:
|
||
print("❌ 达到最大重连次数 (", _max_reconnect_attempts, "),停止重连")
|
||
reconnection_failed.emit(_reconnect_attempt, _max_reconnect_attempts)
|
||
_set_connection_state(ConnectionState.ERROR)
|
||
return
|
||
|
||
_reconnect_attempt += 1
|
||
_set_connection_state(ConnectionState.RECONNECTING)
|
||
|
||
# 计算重连延迟(指数退避)
|
||
var delay: float = _calculate_reconnect_delay()
|
||
print("🔄 尝试重连 (", _reconnect_attempt, "/", _max_reconnect_attempts, ")")
|
||
print(" 延迟: ", delay, " 秒")
|
||
|
||
# 启动重连定时器
|
||
_reconnect_timer.start(delay)
|
||
|
||
# 计算重连延迟(指数退避)
|
||
func _calculate_reconnect_delay() -> float:
|
||
# 指数退避: base_delay * 2^(attempt-1)
|
||
var delay: float = _reconnect_base_delay * pow(2.0, _reconnect_attempt - 1)
|
||
|
||
# 限制最大延迟
|
||
return min(delay, MAX_RECONNECT_DELAY)
|
||
|
||
# 重连定时器超时处理
|
||
func _on_reconnect_timeout() -> void:
|
||
print("⏰ 重连定时器超时,开始重连...")
|
||
_clean_close = false
|
||
connect_to_game_server()
|
||
|
||
# ============================================================================
|
||
# 内部方法 - 心跳机制
|
||
# ============================================================================
|
||
|
||
# 设置心跳定时器
|
||
func _setup_heartbeat_timer() -> void:
|
||
_heartbeat_timer = Timer.new()
|
||
_heartbeat_timer.wait_time = HEARTBEAT_INTERVAL
|
||
_heartbeat_timer.one_shot = false
|
||
_heartbeat_timer.autostart = false
|
||
add_child(_heartbeat_timer)
|
||
|
||
_heartbeat_timer.timeout.connect(_on_heartbeat)
|
||
|
||
# 启动心跳
|
||
func _start_heartbeat() -> void:
|
||
_heartbeat_timer.start()
|
||
|
||
# 停止心跳
|
||
func _stop_heartbeat() -> void:
|
||
_heartbeat_timer.stop()
|
||
|
||
# 心跳超时处理
|
||
func _on_heartbeat() -> void:
|
||
# 不发送心跳,避免服务器返回 "消息格式错误"
|
||
# 如果需要心跳,服务器应该支持特定格式
|
||
pass
|
||
|
||
# ============================================================================
|
||
# 工具方法
|
||
# ============================================================================
|
||
|
||
# 获取连接状态描述
|
||
#
|
||
# 返回值:
|
||
# String - 连接状态描述
|
||
func get_state_description() -> String:
|
||
match _connection_state:
|
||
ConnectionState.DISCONNECTED:
|
||
return "未连接"
|
||
ConnectionState.CONNECTING:
|
||
return "连接中"
|
||
ConnectionState.CONNECTED:
|
||
return "已连接"
|
||
ConnectionState.RECONNECTING:
|
||
return "重连中 (%d/%d)" % [_reconnect_attempt, _max_reconnect_attempts]
|
||
ConnectionState.ERROR:
|
||
return "错误"
|
||
_:
|
||
return "未知状态"
|