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方法内,这会导致:
- 只有点击过发送按钮的游戏实例才会监听消息,未点击的实例无法接收推送
- 重复点击发送按钮会重复注册事件,可能引发异常或导致回调失效
- SignalR事件注册必须在连接启动前完成,否则无法捕获后续的服务器推送
修复步骤
- 将事件注册代码移至
Start方法中,放在connection.Start()之前执行 - 异步处理连接启动操作,避免同步调用导致的时序问题
- 处理SignalR回调的子线程问题,确保UI更新在Unity主线程执行
- 添加异常捕获与资源清理逻辑,提升稳定性
修复后的完整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
相关产品推荐
相关产品推荐

