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.App/MainWindow.cs |  311 +++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 311 insertions(+), 0 deletions(-)

diff --git a/CanSimulatorServer/CanSimulatorServer.App/MainWindow.cs b/CanSimulatorServer/CanSimulatorServer.App/MainWindow.cs
new file mode 100644
index 0000000..2e87f52
--- /dev/null
+++ b/CanSimulatorServer/CanSimulatorServer.App/MainWindow.cs
@@ -0,0 +1,311 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Configuration;
+using System.Linq;
+using System.Threading;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Media;
+using System.Windows.Threading;
+using CanSimulator;
+using log4net;
+
+namespace CanSimulatorServer.App
+{
+    /// <summary>
+    /// 妯℃嫙鍣ㄦ湇鍔$洃鎺х獥鍙o細鐘舵�佹爮 + 杩炴帴鑺傜偣鏍� + 瀹炴椂甯ц〃 + 鏃ュ織鍖恒��
+    /// 绾唬鐮佹瀯寤� UI锛堜笉璧� XAML 鏍囪缂栬瘧閾捐矾锛夛紱甯ц〃缁� 100ms 瀹氭椂鍣ㄦ壒閲忓埛鏂帮紝涓婇檺 2000 琛屻��
+    /// </summary>
+    public class MainWindow : Window
+    {
+        private static readonly ILog Log = LogManager.GetLogger(typeof(MainWindow));
+
+        private const int MaxFrameRows = 2000;
+
+        private readonly TcpSimulatorHost _host = new();
+        private readonly ConcurrentQueue<FrameRow> _pendingRows = new();
+        private readonly ObservableCollection<FrameRow> _rows = new();
+        private readonly DispatcherTimer _uiTimer = new();
+        private int _tickCount;
+
+        private TextBlock _statusText = null!;
+        private TextBlock _statsText = null!;
+        private TreeView _nodeTree = null!;
+        private ListView _frameList = null!;
+        private TextBox _logBox = null!;
+        private Button _startBtn = null!;
+        private Button _stopBtn = null!;
+        private CheckBox _echoChk = null!;
+        private bool _echoWanted;
+        private readonly int _port;
+        private readonly bool _listenAll;
+
+        private sealed class FrameRow
+        {
+            public string Time { get; set; } = "";
+            public string Bus { get; set; } = "";
+            public string Src { get; set; } = "";
+            public string FrameId { get; set; } = "";
+            public string Data { get; set; } = "";
+            public string Kind { get; set; } = "TX";
+        }
+
+        public MainWindow()
+        {
+            _port = ReadIntConfig("ServerPort", 9000);
+            _listenAll = string.Equals(ConfigurationManager.AppSettings["ListenOnAllInterfaces"],
+                "true", StringComparison.OrdinalIgnoreCase);
+
+            BuildUi();
+            HookHostEvents();
+
+            _uiTimer.Interval = TimeSpan.FromMilliseconds(100);
+            _uiTimer.Tick += (s, e) => DrainPendingRows();
+            _uiTimer.Start();
+
+            Loaded += async (s, e) => await StartHostAsync();
+            Closing += (s, e) => { _host.Dispose(); };
+        }
+
+        // ==================== 瀹夸富鐢熷懡鍛ㄦ湡 ====================
+
+        private async Task StartHostAsync()
+        {
+            try
+            {
+                await _host.StartAsync(_port, _listenAll);
+                _statusText.Text = $"鐩戝惉涓�  {(_listenAll ? "0.0.0.0" : "127.0.0.1")}:{_port}";
+                _statusText.Foreground = Brushes.ForestGreen;
+                _startBtn.IsEnabled = false;
+                _stopBtn.IsEnabled = true;
+                AppendLog($"鏈嶅姟绔凡鍚姩锛岀洃鍚� {_port}锛坽(_listenAll ? "鍏ㄩ儴鎺ュ彛" : "浠呮湰鏈�")}锛�");
+            }
+            catch (Exception ex)
+            {
+                _statusText.Text = "鍚姩澶辫触锛氱鍙h鍗犵敤锛�";
+                _statusText.Foreground = Brushes.Firebrick;
+                AppendLog($"[FATAL] 鍚姩澶辫触: {ex.Message}");
+                _startBtn.IsEnabled = true;
+                Log.Error("瀹夸富鍚姩澶辫触", ex);
+            }
+        }
+
+        private void HookHostEvents()
+        {
+            _host.SessionAttached += session =>
+            {
+                Dispatcher.BeginInvoke(new Action(() =>
+                {
+                    AppendLog($"鑺傜偣鎺ュ叆锛歿session.ClientTag}");
+                    RefreshTree();
+                }));
+            };
+        }
+
+        /// <summary>鎬荤嚎甯т簨浠舵寕鎺ワ紙瀹夸富鍚姩鍚庢�荤嚎鎵嶅瓨鍦紝鏁呭湪瀹氭椂鍣ㄩ噷鎯版�цˉ鎸傦級</summary>
+        private void EnsureBusSubscriptions()
+        {
+            foreach (var bus in _host.Manager.AllBuses())
+            {
+                if (_subscribedBuses.Add(bus.BusName))
+                {
+                    bus.FrameTransmitted += (s, e) => EnqueueBusRow(bus.BusName, e.Frame, e.SourceNodeId);
+                    bus.EchoResponderToSender = _echoWanted; // 鏂板缓鎬荤嚎缁ф壙褰撳墠搴旂瓟鍣ㄥ紑鍏�
+                }
+            }
+        }
+        private readonly HashSet<string> _subscribedBuses = new(StringComparer.OrdinalIgnoreCase);
+
+        // ==================== UI 鏋勫缓 ====================
+
+        private void BuildUi()
+        {
+            var root = new Grid { Margin = new Thickness(10) };
+            root.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
+            root.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
+            root.RowDefinitions.Add(new RowDefinition { Height = new GridLength(160) });
+
+            // 鈹�鈹� 椤堕儴鐘舵��/鎸夐挳鏍� 鈹�鈹�
+            var bar = new StackPanel { Orientation = Orientation.Horizontal };
+            _statusText = new TextBlock
+            {
+                Text = "鏈惎鍔�",
+                FontWeight = FontWeights.Bold,
+                VerticalAlignment = VerticalAlignment.Center,
+                Margin = new Thickness(0, 0, 16, 0)
+            };
+            _statsText = new TextBlock { VerticalAlignment = VerticalAlignment.Center, Margin = new Thickness(0, 0, 16, 0) };
+            _startBtn = new Button { Content = "鍚姩鐩戝惉", Width = 90, Height = 28, Margin = new Thickness(0, 0, 6, 0) };
+            _startBtn.Click += async (s, e) => await StartHostAsync();
+            _stopBtn = new Button { Content = "鍋滄鐩戝惉", Width = 90, Height = 28, Margin = new Thickness(0, 0, 6, 0), IsEnabled = false };
+            _stopBtn.Click += (s, e) =>
+            {
+                _host.Dispose();
+                _statusText.Text = "宸插仠姝�";
+                _statusText.Foreground = Brushes.Gray;
+                _startBtn.IsEnabled = true;
+                _stopBtn.IsEnabled = false;
+                AppendLog("鏈嶅姟绔凡鍋滄锛堟敞鎰忥細宸茶繍琛屽鎴风灏嗘柇绾块噸杩烇級");
+            };
+            var clearBtn = new Button { Content = "娓呯┖甯ц〃", Width = 90, Height = 28 };
+            clearBtn.Click += (s, e) => { _rows.Clear(); while (_pendingRows.TryDequeue(out _)) { } };
+            _echoChk = new CheckBox
+            {
+                Content = "鎬荤嚎搴旂瓟鍣�(鍥炴樉鍙戦�佽��)",
+                VerticalAlignment = VerticalAlignment.Center,
+                Margin = new Thickness(12, 0, 0, 0),
+                IsChecked = false
+            };
+            _echoChk.Click += (s, e) =>
+            {
+                _echoWanted = _echoChk.IsChecked == true;
+                foreach (var bus in _host.Manager.AllBuses())
+                    bus.EchoResponderToSender = _echoWanted;
+                AppendLog($"鎬荤嚎搴旂瓟鍣ㄥ凡{(_echoWanted ? "寮�鍚�" : "鍏抽棴")}锛堜綔鐢ㄤ簬褰撳墠鎵�鏈夋�荤嚎锛�");
+            };
+            bar.Children.Add(_statusText);
+            bar.Children.Add(_statsText);
+            bar.Children.Add(_startBtn);
+            bar.Children.Add(_stopBtn);
+            bar.Children.Add(clearBtn);
+            bar.Children.Add(_echoChk);
+            Grid.SetRow(bar, 0);
+            root.Children.Add(bar);
+
+            // 鈹�鈹� 涓儴锛氬乏鑺傜偣鏍� + 鍙冲抚琛� 鈹�鈹�
+            var split = new Grid();
+            split.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(280) });
+            split.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
+
+            _nodeTree = new TreeView();
+            var treeBorder = new Border
+            {
+                Child = _nodeTree,
+                BorderBrush = Brushes.LightGray,
+                BorderThickness = new Thickness(1),
+                Margin = new Thickness(0, 8, 8, 0)
+            };
+            Grid.SetColumn(treeBorder, 0);
+
+            _frameList = new ListView { View = BuildGridView() };
+            _frameList.ItemsSource = _rows;
+            var listBorder = new Border
+            {
+                Child = _frameList,
+                BorderBrush = Brushes.LightGray,
+                BorderThickness = new Thickness(1),
+                Margin = new Thickness(0, 8, 0, 0)
+            };
+            Grid.SetColumn(listBorder, 1);
+
+            split.Children.Add(treeBorder);
+            split.Children.Add(listBorder);
+            Grid.SetRow(split, 1);
+            root.Children.Add(split);
+
+            // 鈹�鈹� 搴曢儴鏃ュ織 鈹�鈹�
+            _logBox = new TextBox
+            {
+                IsReadOnly = true,
+                VerticalScrollBarVisibility = ScrollBarVisibility.Auto,
+                FontFamily = new FontFamily("Consolas"),
+                FontSize = 11,
+                Margin = new Thickness(0, 8, 0, 0)
+            };
+            Grid.SetRow(_logBox, 2);
+            root.Children.Add(_logBox);
+
+            Content = root;
+        }
+
+        private static GridView BuildGridView()
+        {
+            var gv = new GridView();
+            AddCol(gv, "鏃堕棿", 85, nameof(FrameRow.Time));
+            AddCol(gv, "鎬荤嚎", 95, nameof(FrameRow.Bus));
+            AddCol(gv, "婧愯妭鐐�", 60, nameof(FrameRow.Src));
+            AddCol(gv, "甯D", 70, nameof(FrameRow.FrameId));
+            AddCol(gv, "鏁版嵁", 200, nameof(FrameRow.Data));
+            AddCol(gv, "绫诲瀷", 70, nameof(FrameRow.Kind));
+            return gv;
+        }
+
+        private static void AddCol(GridView gv, string header, double width, string binding)
+        {
+            gv.Columns.Add(new GridViewColumn
+            {
+                Header = header,
+                Width = width,
+                DisplayMemberBinding = new System.Windows.Data.Binding(binding)
+            });
+        }
+
+        // ==================== 甯ц〃涓庢爲鍒锋柊 ====================
+
+        /// <summary>鎬荤嚎绾ц瀵燂細璺敱鍙戠敓鍗宠涓�琛岋紙鍚嚜鍔ㄥ搷搴斿抚锛�</summary>
+        private void EnqueueBusRow(string busName, CanFrame frame, string srcNodeId)
+        {
+            _pendingRows.Enqueue(new FrameRow
+            {
+                Time = frame.Timestamp.ToString("HH:mm:ss.fff"),
+                Bus = busName,
+                Src = srcNodeId,
+                FrameId = frame.FrameIdHex,
+                Data = frame.DataHex,
+                Kind = "TX璺敱"
+            });
+        }
+
+        private void DrainPendingRows()
+        {
+            EnsureBusSubscriptions();
+
+            int n = 0;
+            while (_pendingRows.TryDequeue(out var row) && n < 50)
+            {
+                _rows.Insert(0, row);
+                n++;
+            }
+            while (_rows.Count > MaxFrameRows)
+                _rows.RemoveAt(_rows.Count - 1);
+
+            RefreshStatsLine();
+
+            // 姣忕鍒锋柊涓�娆¤妭鐐规爲锛堝弽鏄犳柇寮�浼氳瘽锛岄伩鍏嶅彧鍦ㄦ帴鍏ヤ簨浠朵腑鍒锋柊锛�
+            if (++_tickCount % 10 == 0) RefreshTree();
+        }
+
+        private void RefreshStatsLine()
+        {
+            var buses = _host.Manager.AllBuses().ToList();
+            long tx = buses.Sum(b => b.TotalFramesTransmitted);
+            long rx = buses.Sum(b => b.TotalFramesReceived);
+            _statsText.Text = $"浼氳瘽={_host.ActiveSessionCount} 绱={_host.AcceptedCount} " +
+                              $"鎬荤嚎={buses.Count} TX={tx} RX={rx}";
+        }
+
+        private void RefreshTree()
+        {
+            _nodeTree.Items.Clear();
+            foreach (var bus in _host.Manager.AllBuses())
+            {
+                var busNode = new TreeViewItem { Header = bus.BusName + $"锛坽bus.State}锛�" };
+                foreach (var node in bus.GetAllNodes())
+                    busNode.Items.Add(new TreeViewItem { Header = $"鑺傜偣 {node.NodeId} 路 {node.Name}" });
+                _nodeTree.Items.Add(busNode);
+                busNode.IsExpanded = true;
+            }
+        }
+
+        private void AppendLog(string msg)
+        {
+            _logBox.AppendText($"{DateTime.Now:HH:mm:ss.fff} {msg}{Environment.NewLine}");
+            _logBox.ScrollToEnd();
+        }
+
+        private static int ReadIntConfig(string key, int fallback)
+            => int.TryParse(ConfigurationManager.AppSettings[key], out int v) ? v : fallback;
+    }
+}

--
Gitblit v1.9.1