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

Unity与Hololens 2通信故障:DatagramSocket_MessageReceived事件未触发求助

Unity HoloLens 2 UDP 接收数据不触发场景变化问题排查

问题描述

我做了一个Unity + HoloLens 2的网络项目,目标是给HoloLens发送数据,收到数据后触发场景变化。场景内有两个立方体,预期逻辑:

  • 代码启动时,第一个立方体变为绿色;
  • Socket连接建立后,该立方体变为红色;
  • 收到数据时,两个立方体都变为黄色,但ClientDatagramSocket_MessageReceived方法完全未生效。

在HoloLens上运行时,仅能看到立方体变红,无任何报错,接收数据的逻辑始终没触发。

原代码

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
#if !UNITY_EDITOR
using Windows.Networking;
using Windows.Networking.Sockets;
#endif

public class CubeControllerUWP : MonoBehaviour
{
    public int listenPort = 5001;

    private Vector3 targetPosition;
    public GameObject cube;
    public GameObject cube2;
    private Material m;

    void Start()
    {
        m = new Material(cube.GetComponent<Renderer>().material);
        m.color = Color.green;
#if !UNITY_EDITOR
        StartClient();
#endif
    }

#if !UNITY_EDITOR
    private async void StartClient()
    {
        m.color = Color.green;
        cube.GetComponent<Renderer>().material = m;
        try
        {
            // Create the DatagramSocket and establish a connection to the echo server.
            var clientDatagramSocket = new Windows.Networking.Sockets.DatagramSocket();

            clientDatagramSocket.MessageReceived += ClientDatagramSocket_MessageReceived;
            await clientDatagramSocket.BindServiceNameAsync("12345");
            m.color = Color.red;
            cube.GetComponent<Renderer>().material = m;
        }
        catch (Exception ex)
        {
            Windows.Networking.Sockets.SocketErrorStatus webErrorStatus = Windows.Networking.Sockets.SocketError.GetStatus(ex.GetBaseException().HResult);
        }
}
#endif

#if !UNITY_EDITOR
    private async void ClientDatagramSocket_MessageReceived(Windows.Networking.Sockets.DatagramSocket sender, Windows.Networking.Sockets.DatagramSocketMessageReceivedEventArgs args)
    {
        m.color = Color.yellow;
        cube2.GetComponent<Renderer>().material = m;
        cube.GetComponent<Renderer>().material = m;
        sender.Dispose();

/*        string response;
        using (DataReader dataReader = args.GetDataReader())
        {
            response = dataReader.ReadString(dataReader.UnconsumedBufferLength).Trim();
        }
*/
//        await this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => this.clientListBox.Items.Add(string.Format("client received the response: \"{0}\"", response)));
//        await this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => this.clientListBox.Items.Add("client closed its socket"));


}
#endif

  /*  private Vector3 ParseDataToPosition(string data)
    {
        string[] parts = data.Split(',');
        float x = float.Parse(parts[0]);
        float y = float.Parse(parts[1]);
        float z = float.Parse(parts[2]);

        return new Vector3(x, y, z);
    }

    void Update()
    {
        transform.position = targetPosition;
    }

    void OnDestroy()
    {
 //       udpClient.Close();
    }
  */
}

问题定位

  1. 主线程限制:MessageReceived回调在后台线程触发,Unity所有GameObject、Material操作必须在主线程执行,直接修改会失效,且无报错提示。
  2. Socket生命周期错误:回调里直接调用sender.Dispose(),会立即销毁Socket,导致后续无法接收数据,甚至第一次接收也可能因Socket提前释放而失效。
  3. 端口不匹配:代码里listenPort设为5001,但绑定的是硬编码的"12345",如果发送端往5001发数据,完全收不到。
  4. 异常处理无日志:catch块仅获取错误状态但未输出日志,无法排查Socket绑定阶段的隐性错误。
  5. Material复用问题:两个立方体共用同一个Material实例,虽不影响当前颜色修改,但后续可能引发材质状态混乱。

修复方案

核心修复点

  • 新增主线程调度队列,把UI操作切回Unity主线程执行;
  • 移除回调内的sender.Dispose(),保证Socket持续监听;
  • 统一端口配置,用listenPort变量绑定,避免硬编码;
  • 添加日志输出,方便排查各阶段问题;
  • 为每个立方体创建独立Material实例,避免状态冲突。

修复后代码

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
#if !UNITY_EDITOR
using Windows.Networking;
using Windows.Networking.Sockets;
#endif

public class CubeControllerUWP : MonoBehaviour
{
    public int listenPort = 5001;

    public GameObject cube;
    public GameObject cube2;
    private Material cubeMat;
    private Material cube2Mat;

    // 主线程操作队列
    private readonly Queue<Action> _mainThreadActions = new Queue<Action>();

    void Start()
    {
        // 为每个立方体创建独立Material实例
        cubeMat = new Material(cube.GetComponent<Renderer>().material);
        cube2Mat = new Material(cube2.GetComponent<Renderer>().material);
        
        // 初始绿色
        cubeMat.color = Color.green;
        cube.GetComponent<Renderer>().material = cubeMat;

#if !UNITY_EDITOR
        StartClient();
#endif
    }

    void Update()
    {
        // 主线程处理队列中的操作
        lock (_mainThreadActions)
        {
            while (_mainThreadActions.Count > 0)
            {
                _mainThreadActions.Dequeue().Invoke();
            }
        }
    }

#if !UNITY_EDITOR
    private async void StartClient()
    {
        try
        {
            var clientDatagramSocket = new DatagramSocket();
            clientDatagramSocket.MessageReceived += ClientDatagramSocket_MessageReceived;
            
            // 使用配置的端口绑定
            await clientDatagramSocket.BindServiceNameAsync(listenPort.ToString());
            
            // 绑定成功后切主线程修改颜色并打日志
            DispatchToMainThread(() => 
            {
                cubeMat.color = Color.red;
                cube.GetComponent<Renderer>().material = cubeMat;
                Debug.Log($"Socket绑定成功,监听端口:{listenPort}");
            });
        }
        catch (Exception ex)
        {
            var errorStatus = SocketError.GetStatus(ex.GetBaseException().HResult);
            DispatchToMainThread(() => 
            {
                Debug.LogError($"Socket初始化失败:{ex.Message},错误状态:{errorStatus}");
            });
        }
    }

    private void ClientDatagramSocket_MessageReceived(DatagramSocket sender, DatagramSocketMessageReceivedEventArgs args)
    {
        // 将颜色修改操作调度到主线程执行
        DispatchToMainThread(() => 
        {
            cubeMat.color = Color.yellow;
            cube2Mat.color = Color.yellow;
            cube.GetComponent<Renderer>().material = cubeMat;
            cube2.GetComponent<Renderer>().material = cube2Mat;
            Debug.Log("收到UDP数据,立方体已切换为黄色");
        });

        // 禁止在此销毁Socket,否则会停止监听
        // sender.Dispose();
    }

    // 主线程调度方法
    private void DispatchToMainThread(Action action)
    {
        lock (_mainThreadActions)
        {
            _mainThreadActions.Enqueue(action);
        }
    }
#endif
}

额外检查项

  • 确认发送端是往HoloLens的IP地址和listenPort(5001)发送UDP数据;
  • 确保HoloLens防火墙允许该端口的UDP入站连接;
  • 可在Unity编辑器用UDP工具(如NetAssist)模拟发送数据,先验证逻辑正确性;
  • 通过HoloLens设备门户查看日志,排查隐性错误。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 02:33:14