Unity通过UDP接收Anylogic数据后无法更新数据的问题
问题
原本Unity可通过UDP接收Anylogic的数据,给Anylogic添加某个库后出现数据接收不全的情况。调整UDPClient脚本中ReceiveData函数的Debug输出位置后,Unity的Debug显示已完整接收Anylogic发送的数据,但Unity内的数据仍无法更新。
附Unity端UDP通信代码
using UnityEngine; using System.Collections; using System; using System.Text; using System.Net; using System.Net.Sockets; using System.Threading; using System.Collections.Generic; using System.Globalization; using UnityEditor; using static JSONReader; [Serializable] public class RealTimeItem { public GameObject Object; public int NetID; public Vector3 Position; public Vector3 Target; public float TimeReach; public float TimeReachNext; public bool change; public Agente agent; } public class UDPClient : MonoBehaviour { public List<RealTimeItem> items; public static UDPClient instance; public int portListen = 4444; public string ipSend = ""; public int portSend = 5555; public GameObject[] notifyObjects; public string messageToNotify; private string received = ""; private UdpClient client; private Thread receiveThread; private IPEndPoint remoteEndPoint; private IPAddress ipAddressSend; public float TimeStamp = 1; public bool SetData(Agente ag) { if (getObjWithID(ag.id) == null) return false; RealTimeItem itemUse = items[GetID(ag.id)]; int id = Convert.ToInt32(ag.id); GameObject obj = getObjWithID(id); /* if (ag.coordenadas.Count > (ag.coordenadas.Count - 2)) { itemUse.Position = ag.coordenadas[ag.coordenadas.Count - 2]; } */ itemUse.Position = ag.coordenadas[ag.coordenadas.Count - 1]; itemUse.Target = ag.coordenadas[ag.coordenadas.Count - 1]; itemUse.TimeReach = ag.tiempos[ag.tiempos.Count - 1]; itemUse.TimeReachNext = ag.tiempos[ag.tiempos.Count - 1]; itemUse.change = true; // Debug.LogError("Checking: " + itemUse.NetID + "|||AGID: " + ag.id); // obj.transform.position = itemUse.Position; return true; } private void FixedUpdate() { foreach(var xItem in items) { // xItem.Object.transform.position = Vector3.MoveTowards(xItem.Object.transform.position, xItem.Position, TimeStamp * Time.deltaTime); // xItem.Object.transform.LookAt(xItem.Target); } } public int GetID(int SearchID) { foreach (var item in items) { if (item.NetID.Equals(SearchID)) return items.IndexOf(item); } return 0; } public GameObject getObjWithID(int SearchID) { foreach (var item in items) { if (item.NetID.Equals(SearchID)) return item.Object; } return null; } public void Awake () { instance = this; //Check if the ip address entered is valid. If not, sendMessage will broadcast to all ip addresses IPAddress ip; if (IPAddress.TryParse (ipSend, out ip)) { remoteEndPoint = new IPEndPoint (ip, portSend); } else { remoteEndPoint = new IPEndPoint (IPAddress.Broadcast, portSend); } //Initialize client and thread for receiving client = new UdpClient (portListen); receiveThread = new Thread (new ThreadStart (ReceiveData)); receiveThread.IsBackground = true; receiveThread.Start (); } void Update () { // Check if a message has been received if (received != "") { //Debug.Log("UDPClient: message received \'" + received + "\'"); // Notify each object defined in the array with the message received foreach (GameObject g in notifyObjects) { g.SendMessage(messageToNotify, received, SendMessageOptions.DontRequireReceiver); } // Clear message received = ""; } } // Call this method to send a message from this app to ipSend using portSend public void SendValue (string valueToSend) { try { if (valueToSend != "") { // Get bytes from string byte[] data = Encoding.UTF8.GetBytes (valueToSend); // Send bytes to remote client client.Send (data, data.Length, remoteEndPoint); Debug.Log ("UDPClient: send \'" + valueToSend + "\'"); // Clear message valueToSend = ""; } } catch (Exception err) { Debug.LogError ("Error udp send : " + err.Message); } } // This method checks if the app receives any message public void ReceiveData () { while (true) { try { // Bytes received IPEndPoint anyIP = new IPEndPoint (IPAddress.Any, 0); byte[] data = client.Receive (ref anyIP); // Bytes into text string text = ""; text = Encoding.UTF8.GetString (data); received = text; Debug.Log("UDPClient: message received \'" + received + "\'"); } catch (Exception err) { Debug.Log ("Error:" + err.ToString ()); } } } // Exit UDP client public void OnDisable () { if (receiveThread != null) { receiveThread.Abort (); receiveThread = null; } client.Close (); Debug.Log ("UDPClient: exit"); } }
排查分析
- 线程安全问题:
received变量在子线程(ReceiveData)和主线程(Update)间直接读写,无同步机制,易导致数据覆盖或丢失,比如主线程还没处理完上一条数据,子线程就覆盖了received的值。 - 数据流转断层:Update仅将接收到的字符串通过
SendMessage传递给其他对象,但未调用SetData将解析后的Agente数据更新到RealTimeItem列表,导致接收的数据无法作用到Unity物体上。 - 物体更新逻辑被禁用:FixedUpdate中负责更新物体位置和朝向的代码被注释,即使
RealTimeItem的Position和Target被正确设置,物体也不会有任何变化。 - GetID方法逻辑错误:找不到对应
NetID的项时直接返回0,会错误修改列表中第一个RealTimeItem的数据,而非跳过或报错。 - UDP数据完整性风险:虽然Debug显示数据完整,但Anylogic添加库后可能改变了数据包发送方式(如拆包/粘包),仅通过单次
Receive无法保证拿到完整的有效数据,可能导致解析失败。
解决方案
1. 修复线程安全问题
使用ConcurrentQueue<string>替代单个string变量存储接收的消息,避免多线程竞态条件:
// 替换原private string received = ""; private ConcurrentQueue<string> receivedMessages = new ConcurrentQueue<string>(); // ReceiveData中修改为入队 public void ReceiveData () { while (true) { try { IPEndPoint anyIP = new IPEndPoint (IPAddress.Any, 0); byte[] data = client.Receive (ref anyIP); string text = Encoding.UTF8.GetString (data); receivedMessages.Enqueue(text); // 入队 Debug.Log("UDPClient: message received \'" + text + "\'"); } catch (Exception err) { Debug.Log ("Error:" + err.ToString ()); } } } // Update中修改为循环出队处理 void Update () { while(receivedMessages.TryDequeue(out string received)) { // 保留原SendMessage逻辑 foreach (GameObject g in notifyObjects) { g.SendMessage(messageToNotify, received, SendMessageOptions.DontRequireReceiver); } // 新增解析与更新逻辑 Agente ag = JSONReader.ParseAgente(received); // 假设JSONReader有解析方法 if(ag != null) { SetData(ag); } } }
2. 启用物体更新逻辑
取消FixedUpdate中代码的注释,让物体根据RealTimeItem的数据更新:
private void FixedUpdate() { foreach(var xItem in items) { xItem.Object.transform.position = Vector3.MoveTowards(xItem.Object.transform.position, xItem.Position, TimeStamp * Time.deltaTime); xItem.Object.transform.LookAt(xItem.Target); } }
3. 修复GetID方法逻辑
找不到对应ID时返回-1,并在SetData中做判断,避免错误修改数据:
public int GetID(int SearchID) { for(int i=0; i<items.Count; i++) { if (items[i].NetID.Equals(SearchID)) return i; } return -1; // 找不到返回-1 } public bool SetData(Agente ag) { int itemIndex = GetID(ag.id); if (itemIndex == -1) return false; RealTimeItem itemUse = items[itemIndex]; GameObject obj = itemUse.Object; itemUse.Position = ag.coordenadas[ag.coordenadas.Count - 1]; itemUse.Target = ag.coordenadas[ag.coordenadas.Count - 1]; itemUse.TimeReach = ag.tiempos[ag.tiempos.Count - 1]; itemUse.TimeReachNext = ag.tiempos[ag.tiempos.Count - 1]; itemUse.change = true; return true; }
4. 增加UDP数据完整性校验
在Anylogic发送数据时,每条数据末尾添加固定分隔符(如\n),Unity端接收时拼接数据直到遇到分隔符,确保拿到完整的有效数据:
// 修改ReceiveData中的数据处理逻辑 private StringBuilder receivedBuffer = new StringBuilder(); public void ReceiveData () { while (true) { try { IPEndPoint anyIP = new IPEndPoint (IPAddress.Any, 0); byte[] data = client.Receive (ref anyIP); string text = Encoding.UTF8.GetString (data); receivedBuffer.Append(text); // 按分隔符拆分完整消息 while(receivedBuffer.ToString().Contains("\n")) { int splitIndex = receivedBuffer.ToString().IndexOf("\n"); string fullMessage = receivedBuffer.ToString().Substring(0, splitIndex); receivedBuffer.Remove(0, splitIndex + 1); receivedMessages.Enqueue(fullMessage); Debug.Log("UDPClient: full message received \'" + fullMessage + "\'"); } } catch (Exception err) { Debug.Log ("Error:" + err.ToString ()); } } }
内容的提问来源于stack exchange,提问作者ambioss
相关产品推荐
相关产品推荐

