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

Unity接入ASP.NET SignalR自托管服务器:消息接收回调未触发问题排查

问题:Unity中ASP.NET SignalR 2无法接收服务器推送消息

背景与需求

正在Unity中开发带聊天功能的游戏,要求单个实例发送消息后,所有运行中的游戏实例都能接收。已成功搭建ASP.NET SignalR 2自托管服务器,服务器控制台代码如下:

using System;
using Microsoft.AspNet.SignalR;
using Microsoft.Owin.Hosting;
using Owin;
using Microsoft.Owin.Cors;

namespace SignalRSelfHost
{
    class Program
    {
        static void Main(string[] args)
        {
            string url = "http://localhost:8080";
            using (WebApp.Start(url))
            {
                Console.WriteLine("Server running on {0}", url);
                Console.ReadLine();
            }
        }
    }
    class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            app.UseCors(CorsOptions.AllowAll);
            app.MapSignalR();
        }
    }
    public class MyHub : Hub
    {
        public void Send(string name, string message)
        {
            Clients.All.addMessage(name, message);
        }
    }
}

Unity侧已实现服务器连接与消息发送,但OnReceivedMessageFromServer回调始终未触发,无法接收服务器推送的消息,更新后的Unity代码如下:

using System.Collections.Generic;
using UnityEngine;
using Microsoft.AspNet.SignalR.Client;
using UnityEngine.UI;
using System;
using TMPro;

public class SignalRManager : MonoBehaviour
{
    string url = "http://localhost:8080";
    HubConnection connection;
    IHubProxy hubProxy;
    [SerializeField] private GameObject ChatMessage;
    [SerializeField] private GameObject UserName;
    [SerializeField] private TextMeshProUGUI OutputText;

    void Start()
    {
        connection = new HubConnection(url);
        hubProxy = connection.CreateHubProxy("MyHub");
        connection.Start();
        connection.StateChanged += connection_StateChanged;
        OutputText.text = "";
    }

    private void OnReceivedMessageFromServer(string name, string message)
    {
        OutputText.text = OutputText.text+name + ":" + message + "\n";
        Debug.Log(OutputText.text + name + ":" + message + "\n");
    }

    private void connection_StateChanged(StateChange state)
    {
       if(state.NewState== ConnectionState.Connected)
        {
            Debug.Log("Connected to Server");
         }
       if(state.NewState == ConnectionState.Disconnected)
        {
            Debug.Log("Disconnected from Server");
        }
    }

    public void OnClickSendChatButton()
    {
        string message= ChatMessage.GetComponent<TMP_InputField>().text;
        string userName= UserName.GetComponent<TMP_InputField>().text;
        hubProxy.On<string, string>("addMessage", OnReceivedMessageFromServer);
        hubProxy.Invoke("Send", userName, message);
     }
   
    private void OnDisable()
    {
        connection.StateChanged -= connection_StateChanged;
    }
}

问题原因与修复方案

核心问题

你将hubProxy.On的事件注册逻辑放在了OnClickSendChatButton方法内,这会导致:

  1. 只有点击过发送按钮的游戏实例才会监听消息,未点击的实例无法接收推送
  2. 重复点击发送按钮会重复注册事件,可能引发异常或导致回调失效
  3. SignalR事件注册必须在连接启动前完成,否则无法捕获后续的服务器推送

修复步骤

  1. 将事件注册代码移至Start方法中,放在connection.Start()之前执行
  2. 异步处理连接启动操作,避免同步调用导致的时序问题
  3. 处理SignalR回调的子线程问题,确保UI更新在Unity主线程执行
  4. 添加异常捕获与资源清理逻辑,提升稳定性

修复后的完整Unity代码

using UnityEngine;
using Microsoft.AspNet.SignalR.Client;
using TMPro;
using System;
using System.Threading.Tasks;

public class SignalRManager : MonoBehaviour
{
    string url = "http://localhost:8080";
    HubConnection connection;
    IHubProxy hubProxy;
    [SerializeField] private TMP_InputField chatMessageInput;
    [SerializeField] private TMP_InputField userNameInput;
    [SerializeField] private TextMeshProUGUI outputText;

    async void Start()
    {
        connection = new HubConnection(url);
        hubProxy = connection.CreateHubProxy("MyHub");

        // 提前注册消息接收事件,必须在连接启动前完成
        hubProxy.On<string, string>("addMessage", HandleReceivedMessage);

        connection.StateChanged += OnConnectionStateChanged;

        // 异步启动连接,确保连接成功后再进行操作
        try
        {
            await connection.Start();
            Debug.Log("已连接到SignalR服务器");
        }
        catch (Exception ex)
        {
            Debug.LogError($"连接失败: {ex.Message}");
        }

        outputText.text = "";
    }

    private void HandleReceivedMessage(string name, string message)
    {
        // SignalR回调在子线程执行,需切换到Unity主线程更新UI
        UnityMainThreadDispatcher.Instance().Enqueue(() =>
        {
            outputText.text += $"{name}: {message}\n";
            Debug.Log($"收到消息: {name}: {message}");
        });
    }

    private void OnConnectionStateChanged(StateChange state)
    {
        switch (state.NewState)
        {
            case ConnectionState.Connected:
                Debug.Log("已连接到服务器");
                break;
            case ConnectionState.Disconnected:
                Debug.Log("已断开与服务器的连接");
                break;
        }
    }

    public void OnSendButtonClicked()
    {
        string message = chatMessageInput.text.Trim();
        string userName = userNameInput.text.Trim();

        if (string.IsNullOrEmpty(userName) || string.IsNullOrEmpty(message))
        {
            Debug.LogWarning("用户名或消息不能为空");
            return;
        }

        // 异步调用服务器Send方法,添加异常处理
        hubProxy.Invoke("Send", userName, message)
            .ContinueWith(task =>
            {
                if (task.IsFaulted)
                {
                    Debug.LogError($"发送消息失败: {task.Exception.InnerException.Message}");
                }
                else
                {
                    // 清空输入框
                    UnityMainThreadDispatcher.Instance().Enqueue(() => chatMessageInput.text = "");
                }
            });
    }
   
    private void OnDisable()
    {
        // 注销事件与连接,避免内存泄漏
        connection.StateChanged -= OnConnectionStateChanged;
        hubProxy.Off<string, string>("addMessage", HandleReceivedMessage);
        connection.Stop();
        connection.Dispose();
    }
}

补充:Unity主线程调度器实现

由于SignalR回调在子线程执行,直接更新UI会报错,需要实现一个简单的主线程调度器:

using UnityEngine;
using System.Collections.Generic;
using System;

public class UnityMainThreadDispatcher : MonoBehaviour
{
    private static UnityMainThreadDispatcher instance;
    private Queue<Action> actionQueue = new Queue<Action>();

    public static UnityMainThreadDispatcher Instance()
    {
        if (instance == null)
        {
            instance = FindObjectOfType<UnityMainThreadDispatcher>();
            if (instance == null)
            {
                GameObject dispatcherObj = new GameObject("MainThreadDispatcher");
                instance = dispatcherObj.AddComponent<UnityMainThreadDispatcher>();
                DontDestroyOnLoad(dispatcherObj);
            }
        }
        return instance;
    }

    public void Enqueue(Action action)
    {
        lock (actionQueue)
        {
            actionQueue.Enqueue(action);
        }
    }

    void Update()
    {
        lock (actionQueue)
        {
            while (actionQueue.Count > 0)
            {
                actionQueue.Dequeue().Invoke();
            }
        }
    }
}

额外注意事项

  • 服务器地址:多设备测试时,将服务器代码中的url改为http://*:8080,并使用服务器的局域网IP替换Unity中的localhost
  • 权限配置:确保服务器防火墙允许8080端口的TCP连接
  • 版本兼容:Unity中使用的SignalR客户端版本需与服务器的ASP.NET SignalR 2版本一致

内容的提问来源于stack exchange,提问作者Chethan Venkataramaiah

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 07:50:26