You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何检测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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.12 03:57:34