From 559890c482f5973f06aafdfec9550fee5ba3c7f2 Mon Sep 17 00:00:00 2001
From: wenxiangchao <wenxiangchao@ihengxin.com.cn>
Date: 星期一, 09 十一月 2026 17:02:15 +0800
Subject: [PATCH] feat: CAN 模拟器 TCP 化 + 多设备并发调试 + 后台生命周期管理

---
 CanSimulatorServer/CanSimulatorServer.Core/ServerSession.cs |  238 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 238 insertions(+), 0 deletions(-)

diff --git a/CanSimulatorServer/CanSimulatorServer.Core/ServerSession.cs b/CanSimulatorServer/CanSimulatorServer.Core/ServerSession.cs
new file mode 100644
index 0000000..1cc4931
--- /dev/null
+++ b/CanSimulatorServer/CanSimulatorServer.Core/ServerSession.cs
@@ -0,0 +1,238 @@
+using System;
+using System.IO;
+using System.Net.Sockets;
+using System.Threading;
+using System.Threading.Tasks;
+using CanSimulator;
+using log4net;
+
+namespace CanSimulatorServer
+{
+    /// <summary>
+    /// 鍗曡繛鎺ヤ細璇濓細HELLO 鐘舵�佹満 + 鍛戒护鎵ц锛堜繚搴忓搷搴旓級 + 甯ф帹閫侊紙鍐欓攣涓茶鍖栦繚搴忥級銆�
+    /// 璇诲惊鐜崟绾跨▼澶勭悊璇锋眰锛涘搷搴斾笌鎺ㄩ�佺粡鍚屼竴鍐欓攣鍙戝嚭锛屽鎴风鎸夊埌杈鹃『搴忓垎娴併��
+    /// </summary>
+    public sealed class ServerSession : IDisposable
+    {
+        private static readonly ILog Log = LogManager.GetLogger(typeof(ServerSession));
+
+        private readonly TcpClient _client;
+        private readonly CanBusManager _manager;
+        private readonly Action<ServerSession> _onClosed;
+        private readonly CancellationTokenSource _cts = new();
+        private readonly SemaphoreSlim _writeLock = new(1, 1);
+
+        private string? _busName;
+        private CanNode? _node;
+        private CanCommandExecutor? _executor;
+        private long _droppedPushes;
+
+        /// <summary>鎺ㄩ�佹殏瀛橀棬锛氬懡浠ゅ鐞嗘湡闂磋矾鐢变骇鐢熺殑甯у厛鍏ラ槦锛屽搷搴斿啓瀹屽悗鍐嶅啿锛�
+        /// 淇濊瘉瀹㈡埛绔�滈琛屽繀涓哄搷搴斻�佹帹閫侀殢鍚庘�濈殑淇濆簭濂戠害</summary>
+        private readonly object _pushGate = new();
+        private bool _inCommand;
+        private readonly System.Collections.Generic.List<string> _heldPushes = new();
+        private const int HeldPushCapacity = 1000;
+
+        public string ClientTag { get; private set; } = "";
+        public int NodeIndex { get; private set; } = -1;
+        public bool Attached => _node != null;
+        public long DroppedPushes => Interlocked.Read(ref _droppedPushes);
+
+        /// <summary>鑺傜偣甯у埌杈句簨浠讹紙UI 甯ц〃璁㈤槄锛�</summary>
+        public event Action<string, string, int>? FramePushed; // (bus, pushLine, nodeIndex)
+
+        /// <summary>鎬荤嚎瑙傚療绾у抚浜嬩欢锛堣矾鐢卞彂鐢熷悗鍚� UI 鎶ュ憡锛岀敱瀹夸富鎸傛帴锛�</summary>
+        internal ServerSession(TcpClient client, CanBusManager manager, Action<ServerSession> onClosed)
+        {
+            _client = client;
+            _manager = manager;
+            _onClosed = onClosed;
+        }
+
+        public void Start()
+        {
+            _ = Task.Run(() => RunAsync(_cts.Token));
+        }
+
+        private async Task RunAsync(CancellationToken ct)
+        {
+            Stream? stream = null;
+            try
+            {
+                stream = _client.GetStream();
+                while (!ct.IsCancellationRequested)
+                {
+                    string? line = await SimulatorFraming.ReadFrameAsync(stream, ct).ConfigureAwait(false);
+                    if (line == null) break; // EOF / 鎴柇 鈫� 浼氳瘽缁撴潫
+
+                    string? response = Process(line);
+                    if (response != null)
+                    {
+                        await WriteAsync(response, ct).ConfigureAwait(false);
+                        await FlushHeldPushesAsync(ct).ConfigureAwait(false); // 鍝嶅簲鍏堣锛岃ˉ鍙戝鐞嗘湡闂存殏瀛樼殑甯�
+                    }
+                }
+            }
+            catch (InvalidDataException)
+            {
+                // 鍗忚澶辨锛氭柇寮�璇ヤ細璇濓紝瀹夸富缁х画鐩戝惉
+            }
+            catch (IOException) { }
+            catch (OperationCanceledException) { }
+            catch (Exception ex)
+            {
+                Log.Warn($"[SimServer] 浼氳瘽寮傚父缁撴潫: {ex.Message}");
+            }
+            finally
+            {
+                Close();
+            }
+        }
+
+        /// <summary>澶勭悊涓�琛岃姹傦紝杩斿洖鍝嶅簲鏂囨湰锛堟棤闇�鍝嶅簲鏃惰繑鍥� null锛夈��</summary>
+        private string? Process(string line)
+        {
+            if (line.StartsWith("HELLO:", StringComparison.OrdinalIgnoreCase))
+                return HandleHello(line);
+
+            if (line.StartsWith("DISCONNECT", StringComparison.OrdinalIgnoreCase))
+            {
+                // 鏄惧紡鏂紑锛氬洖 OK 鍚庣敱 EOF/鍏抽棴鏀跺熬
+                return "OK:DISCONNECTED";
+            }
+
+            if (_node == null)
+                return "ERR:NOT_HANDSHAKED";
+
+            if (line.StartsWith("SEND:", StringComparison.OrdinalIgnoreCase) ||
+                line.StartsWith("QUERY:", StringComparison.OrdinalIgnoreCase))
+            {
+                string resp;
+                lock (_pushGate) { _inCommand = true; }   // 澶勭悊鏈熼棿鐨勬帹閫佹敼璧版殏瀛�
+                try
+                {
+                    resp = _executor!.Execute(line);
+                }
+                finally
+                {
+                    lock (_pushGate) { _inCommand = false; }
+                }
+                // 瑁告暟鎹紙闈� OK/ERR 鍓嶇紑锛夎ˉ OK:
+                if (!resp.StartsWith("OK", StringComparison.Ordinal) &&
+                    !resp.StartsWith("ERR", StringComparison.Ordinal))
+                    resp = "OK:" + resp;
+                return resp;
+            }
+
+            return $"ERR:UNKNOWN_CMD:{line}";
+        }
+
+        private string? HandleHello(string line)
+        {
+            if (_node != null)
+                return "ERR:ALREADY_HELLO";
+
+            var parts = line.Split(':'); // HELLO:bus:node:tag
+            if (parts.Length < 4 || !int.TryParse(parts[2], out int nodeIndex) || nodeIndex < 0)
+                return "ERR:BAD_FRAME:HELLO 鏍煎紡搴斾负 HELLO:bus:nodeIndex:tag";
+
+            string bus = parts[1];
+            ClientTag = parts[3];
+            bool ok = _manager.TryAttach(bus, nodeIndex, ClientTag, OnNodeFrame, out var node, out var busInst);
+            if (!ok) return "ERR:NODE_BUSY";
+
+            _node = node;
+            _busName = bus;
+            NodeIndex = nodeIndex;
+            _executor = new CanCommandExecutor(node!, busInst!);
+            return $"OK:HELLO:{bus}:{nodeIndex}";
+        }
+
+        /// <summary>鎬荤嚎璺敱鍒版湰鑺傜偣鐨勫抚 鈫� 鎺ㄩ�侊紙鍛戒护澶勭悊涓殏瀛橈紝鍚﹀垯鍗冲彂鍗宠蛋锛�</summary>
+        private void OnNodeFrame(CanFrame frame)
+        {
+            string push = CanCommandExecutor.FormatPush(frame);
+            FramePushed?.Invoke(_busName ?? "?", push, NodeIndex);
+
+            lock (_pushGate)
+            {
+                if (_inCommand)
+                {
+                    if (_heldPushes.Count >= HeldPushCapacity)
+                    {
+                        _heldPushes.RemoveAt(0); // 婊″垯涓㈡渶鑰侊紝鐢卞鎴风 RECV_ALL 鍏滃簳琛ヨ处
+                        Interlocked.Increment(ref _droppedPushes);
+                    }
+                    _heldPushes.Add(push);
+                    return;
+                }
+            }
+
+            // 绌洪棽鏈燂細鍗冲彂鍗宠蛋锛屽啓閿佷覆琛屽寲淇濆抚搴忥紱澶辫触璁″叆涓㈠抚
+            _ = WriteAsync(push, CancellationToken.None).ContinueWith(t =>
+            {
+                if (t.IsFaulted) Interlocked.Increment(ref _droppedPushes);
+            }, TaskScheduler.Default);
+        }
+
+        private async Task FlushHeldPushesAsync(CancellationToken ct)
+        {
+            string[] pending;
+            lock (_pushGate)
+            {
+                if (_heldPushes.Count == 0) return;
+                pending = _heldPushes.ToArray();
+                _heldPushes.Clear();
+            }
+            foreach (var p in pending)
+                await WriteAsync(p, ct).ConfigureAwait(false);
+        }
+
+        private async Task WriteAsync(string text, CancellationToken ct)
+        {
+            byte[] frame = SimulatorFraming.Encode(text);
+            await _writeLock.WaitAsync(ct).ConfigureAwait(false);
+            try
+            {
+                if (_client.Connected)
+                {
+                    var s = _client.GetStream();
+                    await s.WriteAsync(frame, 0, frame.Length, ct).ConfigureAwait(false);
+                    await s.FlushAsync(ct).ConfigureAwait(false);
+                }
+            }
+            finally
+            {
+                _writeLock.Release();
+            }
+        }
+
+        public void Close()
+        {
+            try
+            {
+                if (_node != null && _busName != null)
+                    _manager.Detach(_busName, _node);
+                _node = null;
+                _cts.Cancel();
+                _client.Close();
+            }
+            catch (Exception ex)
+            {
+                Log.Debug($"[SimServer] 浼氳瘽鍏抽棴寮傚父: {ex.Message}");
+            }
+            finally
+            {
+                _onClosed(this);
+            }
+        }
+
+        public void Dispose()
+        {
+            Close();
+            _cts.Dispose();
+            _writeLock.Dispose();
+        }
+    }
+}

--
Gitblit v1.9.1