如何检测Socket.IO Unity客户端未收到服务端确认(Acknowledge)?
解决Unity Socket.IO客户端未收到服务端确认(Acknowledge)的通知问题
问题的核心在于:Socket.IO的默认ack回调只有在服务端主动调用对应的确认函数时才会触发,如果服务端未响应(比如网络中断、服务端崩溃、代码遗漏了ack调用),客户端的回调永远不会执行,所以我们需要手动添加超时检测机制来处理这种情况。
下面是两种实用的解决方案,结合Unity的特性来实现:
方法一:封装带超时检测的Emit方法(推荐)
我们可以自己封装一个Emit方法,通过维护待确认的ack列表+Unity协程来实现超时检测。具体步骤如下:
1. 定义变量维护待确认的ack
在你的Socket管理类中添加以下变量:
// 线程安全的字典,存储待确认的ack回调(key为唯一标识) private readonly object _ackLock = new object(); private Dictionary<string, Action<JSONObject>> _pendingAcks = new Dictionary<string, Action<JSONObject>>();
2. 封装带超时的Emit方法
添加一个通用方法,支持自定义超时时间和超时回调:
/// <summary> /// 发送带超时检测的Socket.IO事件 /// </summary> /// <param name="eventName">事件名称</param> /// <param name="data">发送的数据</param> /// <param name="ackCallback">收到服务端确认后的回调</param> /// <param name="timeoutCallback">超时未收到确认的回调</param> /// <param name="timeoutSeconds">超时时间(默认5秒)</param> public void EmitWithAckTimeout(string eventName, JSONObject data, Action<JSONObject> ackCallback, Action timeoutCallback, float timeoutSeconds = 5f) { // 生成唯一的ack标识(避免重复) string ackKey = $"{eventName}_{System.Guid.NewGuid()}"; // 线程安全地添加到待确认列表 lock (_ackLock) { _pendingAcks.Add(ackKey, ackCallback); } // 发送事件,重写ack回调:先移除待确认记录,再执行原回调 socket.Emit(eventName, data, (JSONObject ackData) => { lock (_ackLock) { if (_pendingAcks.ContainsKey(ackKey)) { _pendingAcks.Remove(ackKey); ackCallback?.Invoke(ackData); } } }); // 启动超时检测协程 StartCoroutine(CheckAckTimeoutCoroutine(ackKey, timeoutSeconds, timeoutCallback)); } // 超时检测协程 private IEnumerator CheckAckTimeoutCoroutine(string ackKey, float timeoutSeconds, Action timeoutCallback) { yield return new WaitForSeconds(timeoutSeconds); lock (_ackLock) { if (_pendingAcks.ContainsKey(ackKey)) { // 超时未收到确认,移除记录并触发超时回调 _pendingAcks.Remove(ackKey); timeoutCallback?.Invoke(); Debug.LogError($"[SocketIO] Ack超时:事件 {ackKey.Split('_')[0]} 未收到服务端确认"); } } }
3. 使用示例
调用封装后的方法,自定义超时处理逻辑:
// 发送testClient事件,5秒未收到确认则触发超时处理 EmitWithAckTimeout( "testClient", data, (ackData) => Debug.Log("收到服务端确认:" + ackData), () => { Debug.LogError("服务端未响应,触发超时处理"); // 这里可以添加重试逻辑、提示用户等操作 }, 5f );
方法二:处理Socket断开事件
当客户端与服务端断开连接时,所有待确认的ack都不可能再收到响应,此时可以清空待确认列表并统一触发通知:
// 监听Socket断开事件 socket.On("disconnect", () => { lock (_ackLock) { if (_pendingAcks.Count > 0) { Debug.LogWarning($"[SocketIO] 连接断开,{_pendingAcks.Count}个待确认事件失效"); _pendingAcks.Clear(); // 这里可以触发全局的断开回调,通知所有未完成的请求 } } });
服务端注意事项
别忘了在Node.js服务端必须主动调用ack函数,否则客户端永远收不到确认:
socket.on('testClient', (data, ack) => { // 处理业务逻辑 console.log("收到客户端testClient事件:", data); // 必须调用ack来发送确认 ack({ status: "success", message: "已处理你的请求" }); });
额外优化点
- 可以给超时回调添加事件名称参数,方便定位具体是哪个事件超时;
- 如果需要重试机制,可以在超时回调中重新调用
EmitWithAckTimeout(注意添加重试次数限制,避免无限重试); - 对于频繁发送的事件,可以复用ack标识的生成逻辑,提升性能。
内容的提问来源于stack exchange,提问作者saman.firouzi
相关产品推荐
相关产品推荐

