UDPResultReceiver.cs 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. using System;
  2. using System.Collections.Concurrent;
  3. using System.Net;
  4. using System.Net.Sockets;
  5. using System.Threading;
  6. using UnityEngine;
  7. namespace LightGlue.Unity.Networking
  8. {
  9. /// <summary>
  10. /// UDP结果接收器(用于接收Python发送的LightGlue算法结果)
  11. /// - 接收二进制格式的结果数据包
  12. /// - 在后台线程接收,主线程通过队列消费结果
  13. /// - 协议格式:版本(2) + 长度(2) + 有效性(1) + 匹配数(2) + 内点比例(4) + 相机X(4) + 相机Y(4) + 校验和(1) = 20字节
  14. /// </summary>
  15. public sealed class UDPResultReceiver : IDisposable
  16. {
  17. private const ushort ProtocolVersion = 0x0001;
  18. private const int PacketSize = 20; // 包含校验和的完整包大小
  19. private const int DataSize = 15; // 数据段大小(不包括版本和长度字段)
  20. private readonly string _bindIp;
  21. private readonly int _port;
  22. private readonly int _maxQueueSize;
  23. private UdpClient _client;
  24. private Thread _thread;
  25. private volatile bool _running;
  26. private readonly ConcurrentQueue<LightGlueResult> _resultQueue = new ConcurrentQueue<LightGlueResult>();
  27. private int _queuedCount;
  28. public UDPResultReceiver(string bindIp, int port, int maxQueueSize = 10)
  29. {
  30. _bindIp = string.IsNullOrWhiteSpace(bindIp) ? "0.0.0.0" : bindIp;
  31. _port = port;
  32. _maxQueueSize = Mathf.Max(1, maxQueueSize);
  33. }
  34. public void Start()
  35. {
  36. if (_running) return;
  37. var ip = IPAddress.Parse(_bindIp);
  38. _client = new UdpClient(new IPEndPoint(ip, _port));
  39. _client.Client.ReceiveTimeout = 100; // 100ms超时,用于定期检查
  40. _running = true;
  41. _thread = new Thread(ReceiveLoop)
  42. {
  43. IsBackground = true,
  44. Name = "UDPResultReceiver"
  45. };
  46. _thread.Start();
  47. Debug.Log($"[UDP] Result receiver started on {_bindIp}:{_port}");
  48. }
  49. public void Stop()
  50. {
  51. _running = false;
  52. if (_client != null)
  53. {
  54. try
  55. {
  56. _client.Close();
  57. _client.Dispose();
  58. }
  59. catch { /* ignore */ }
  60. _client = null;
  61. }
  62. if (_thread != null && _thread.IsAlive)
  63. {
  64. if (!_thread.Join(1000))
  65. {
  66. try { _thread.Interrupt(); } catch { /* ignore */ }
  67. }
  68. }
  69. _thread = null;
  70. // 清空队列
  71. while (_resultQueue.TryDequeue(out _))
  72. {
  73. Interlocked.Decrement(ref _queuedCount);
  74. }
  75. Debug.Log("[UDP] Result receiver stopped");
  76. }
  77. /// <summary>
  78. /// 尝试从队列中获取最新结果(非阻塞)
  79. /// </summary>
  80. public bool TryDequeueResult(out LightGlueResult result)
  81. {
  82. if (_resultQueue.TryDequeue(out result))
  83. {
  84. Interlocked.Decrement(ref _queuedCount);
  85. return true;
  86. }
  87. return false;
  88. }
  89. private void ReceiveLoop()
  90. {
  91. IPEndPoint remoteEp = new IPEndPoint(IPAddress.Any, 0);
  92. while (_running)
  93. {
  94. try
  95. {
  96. byte[] data = _client.Receive(ref remoteEp);
  97. if (data != null && data.Length >= PacketSize)
  98. {
  99. ProcessPacket(data);
  100. }
  101. }
  102. catch (SocketException ex)
  103. {
  104. // timeout or other socket errors - just continue
  105. if (!_running) break;
  106. continue;
  107. }
  108. catch (ObjectDisposedException)
  109. {
  110. break;
  111. }
  112. catch (ThreadInterruptedException)
  113. {
  114. break;
  115. }
  116. catch (Exception ex)
  117. {
  118. Debug.LogError($"[UDP] Result receive error: {ex}");
  119. break;
  120. }
  121. }
  122. }
  123. private void ProcessPacket(byte[] data)
  124. {
  125. try
  126. {
  127. // 解析协议版本(小端序)
  128. ushort version = (ushort)(data[0] | (data[1] << 8));
  129. if (version != ProtocolVersion)
  130. {
  131. Debug.LogWarning($"[UDP] Invalid protocol version: {version}, expected {ProtocolVersion}");
  132. return;
  133. }
  134. // 解析数据长度(小端序)
  135. ushort dataLength = (ushort)(data[2] | (data[3] << 8));
  136. if (dataLength != DataSize)
  137. {
  138. Debug.LogWarning($"[UDP] Invalid data length: {dataLength}, expected {DataSize}");
  139. return;
  140. }
  141. // 解析数据段(从索引4开始)
  142. // 格式:is_valid(1) + num_matches(2) + inliers_ratio(4) + camera_x(4) + camera_y(4) + checksum(1)
  143. bool isValid = data[4] != 0;
  144. ushort numMatches = (ushort)(data[5] | (data[6] << 8));
  145. // 解析浮点数(小端序)
  146. float inliersRatio = BitConverter.ToSingle(data, 7);
  147. float cameraX = BitConverter.ToSingle(data, 11);
  148. float cameraY = BitConverter.ToSingle(data, 15);
  149. // 可选:验证校验和
  150. byte checksum = data[19];
  151. if (checksum != 0) // 如果校验和不为0,则验证
  152. {
  153. byte calculatedChecksum = 0;
  154. for (int i = 4; i < 19; i++)
  155. {
  156. calculatedChecksum = (byte)((calculatedChecksum + data[i]) % 256);
  157. }
  158. if (calculatedChecksum != checksum)
  159. {
  160. Debug.LogWarning($"[UDP] Checksum mismatch: calculated={calculatedChecksum}, received={checksum}");
  161. return;
  162. }
  163. }
  164. // 创建结果对象
  165. LightGlueResult result = new LightGlueResult(isValid, numMatches, inliersRatio, cameraX, cameraY);
  166. // 入队(只保留最新N个结果)
  167. EnqueueLatest(result);
  168. }
  169. catch (Exception ex)
  170. {
  171. Debug.LogError($"[UDP] Failed to parse result packet: {ex}");
  172. }
  173. }
  174. private void EnqueueLatest(LightGlueResult result)
  175. {
  176. // 只保留最新N个结果
  177. while (Volatile.Read(ref _queuedCount) >= _maxQueueSize && _resultQueue.TryDequeue(out _))
  178. {
  179. Interlocked.Decrement(ref _queuedCount);
  180. }
  181. _resultQueue.Enqueue(result);
  182. Interlocked.Increment(ref _queuedCount);
  183. }
  184. public void Dispose()
  185. {
  186. Stop();
  187. }
  188. }
  189. }