如何在C# WPF中通过TCPClient读写序列化与反序列化JSON对象
WPF TCP回显客户端实现方案
需求说明
点击窗口的Start按钮后,启动以下回显流程,每500ms重复执行:
- 创建包含初始化GUID的对象
- 将对象序列化为JSON字符串
- 通过TCP Socket将JSON发送至echotool(该工具会回传所有接收的数据)
- 接收并解析回传的JSON数据包,反序列化为对象
- 将回显结果添加至GUI Grid的对应列:
- Started:回显流程启动时间戳
- Processed:反序列化完成时间戳
- Elapsed:两者间耗时(毫秒)
现有参考代码
微软文档提供的TCP客户端示例
static void Connect(String server, String message) { try { // 创建TcpClient // 注意:客户端需要连接到相同地址和端口的TcpServer Int32 port = 13000; TcpClient client = new TcpClient(server, port); // 将消息转换为ASCII字节数组 Byte[] data = System.Text.Encoding.ASCII.GetBytes(message); // 获取读写流 NetworkStream stream = client.GetStream(); // 发送消息到服务器 stream.Write(data, 0, data.Length); Console.WriteLine("Sent: {0}", message); // 接收服务器响应 data = new Byte[256]; String responseData = String.Empty; Int32 bytes = stream.Read(data, 0, data.Length); responseData = System.Text.Encoding.ASCII.GetString(data, 0, bytes); Console.WriteLine("Received: {0}", responseData); // 关闭资源 stream.Close(); client.Close(); } catch (ArgumentNullException e) { Console.WriteLine("ArgumentNullException: {0}", e); } catch (SocketException e) { Console.WriteLine("SocketException: {0}", e); } Console.WriteLine("\n Press Enter to continue..."); Console.Read(); }
现有前端XAML代码
<StackPanel Grid.Row="1" Orientation="Horizontal"> <Button Padding="15 5" Margin="5" Content="Start" Command="{x:Static local:WpfTimerWindow.StartCommand}" CommandParameter="{x:Static local:WpfTimerWindow.Default}"/>
现有后端C# WPF代码
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; using System.Windows.Threading; using System.Diagnostics; using System.Timers; using System.ComponentModel; namespace PingApplication { /// <summary> /// MainWindow.xaml 的交互逻辑 /// </summary> public partial class WpfTimerWindow : INotifyPropertyChanged { public event PropertyChangedEventHandler? PropertyChanged; private readonly Timer timer = new Timer(); private readonly Stopwatch stopwatch = new Stopwatch(); public DateTime Now => DateTime.Now; public TimeSpan Elasped => stopwatch.Elapsed; public WpfTimerWindow() { //InitializeComponent(); timer.Interval = 50; timer.Elapsed += OnTick; timer.Start(); stopwatch.Start(); } private void OnTick(object? sender, ElapsedEventArgs e) { if (PropertyChanged is PropertyChangedEventHandler propertyChanged) { propertyChanged(this, NowArgs); propertyChanged(this, ElaspedArgs); } } public static PropertyChangedEventArgs NowArgs { get; } = new PropertyChangedEventArgs(nameof(Now)); public static PropertyChangedEventArgs ElaspedArgs { get; } = new PropertyChangedEventArgs(nameof(Elasped)); public static RoutedUICommand StartCommand { get; } = new RoutedUICommand("Timer Start", "TimerStart", typeof(WpfTimerWindow)); public static RoutedUICommand ResetCommand { get; } = new RoutedUICommand("Timer Stop", "TimerStop", typeof(WpfTimerWindow)); public static ExecutedRoutedEventHandler ExecuteCommand { get; } = (_, e) => { if (e.Parameter is WpfTimerWindow timer) { if (e.Command == StartCommand) { timer.stopwatch.Start(); } else if (e.Command == ResetCommand) { timer.stopwatch.Stop(); } else return; timer.OnTick(null, null); } }; public static CanExecuteRoutedEventHandler CanExecuteCommand { get; } = (_, e) => { if (e.Parameter is WpfTimerWindow timer) { if (e.Command == StartCommand) { e.CanExecute = !timer.stopwatch.IsRunning; } else if (e.Command == ResetCommand) { e.CanExecute = timer.stopwatch.IsRunning; } } }; public static WpfTimerWindow Default { get; } = new WpfTimerWindow(); } }
改造后的完整实现
1. 数据模型定义
创建两个类:一个用于TCP传输的请求对象,一个用于存储回显结果(绑定到UI)。
using System; namespace PingApplication { // TCP传输的请求对象 public class EchoRequest { public Guid RequestId { get; set; } = Guid.NewGuid(); } // 回显结果(用于UI绑定) public class EchoResult { public DateTime Started { get; set; } public DateTime Processed { get; set; } public long ElapsedMs { get; set; } } }
2. 后端C#代码改造
替换原有后端代码,添加TCP异步操作、JSON序列化/反序列化、UI线程安全更新逻辑:
using System; using System.Collections.ObjectModel; using System.ComponentModel; using System.Net.Sockets; using System.Text; using System.Text.Json; using System.Threading.Tasks; using System.Windows; using System.Windows.Input; using System.Windows.Threading; namespace PingApplication { public partial class WpfTimerWindow : INotifyPropertyChanged { public event PropertyChangedEventHandler? PropertyChanged; // 用于UI更新的DispatcherTimer(避免跨线程问题) private readonly DispatcherTimer _uiTimer = new(); // 用于定时执行TCP回显的DispatcherTimer private readonly DispatcherTimer _echoTimer = new(); // 存储回显结果的集合(自动通知UI更新) public ObservableCollection<EchoResult> EchoResults { get; } = new(); // TCP服务器配置 private const string TcpServer = "localhost"; private const int TcpPort = 13000; public WpfTimerWindow() { InitializeComponent(); DataContext = this; // 初始化UI刷新定时器(50ms更新一次时间) _uiTimer.Interval = TimeSpan.FromMilliseconds(50); _uiTimer.Tick += OnUiTick; _uiTimer.Start(); // 初始化回显定时器(500ms执行一次) _echoTimer.Interval = TimeSpan.FromMilliseconds(500); _echoTimer.Tick += EchoTimer_Tick; } private void OnUiTick(object? sender, EventArgs e) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Now))); } public DateTime Now => DateTime.Now; // 回显定时器触发逻辑 private async void EchoTimer_Tick(object? sender, EventArgs e) { await ExecuteEchoFlow(); } // 核心回显流程 private async Task ExecuteEchoFlow() { var startedTime = DateTime.Now; TcpClient? client = null; NetworkStream? stream = null; try { // 创建请求对象 var request = new EchoRequest(); // 序列化为JSON var jsonStr = JsonSerializer.Serialize(request); byte[] sendData = Encoding.UTF8.GetBytes(jsonStr); // 连接TCP服务器 client = new TcpClient(); await client.ConnectAsync(TcpServer, TcpPort); stream = client.GetStream(); // 发送数据 await stream.WriteAsync(sendData, 0, sendData.Length); await stream.FlushAsync(); // 接收响应 var buffer = new byte[1024]; int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length); var responseJson = Encoding.UTF8.GetString(buffer, 0, bytesRead); // 反序列化响应 var response = JsonSerializer.Deserialize<EchoRequest>(responseJson); // 计算耗时并添加结果到UI集合 var processedTime = DateTime.Now; var elapsedMs = (processedTime - startedTime).TotalMilliseconds; // UI线程安全更新集合 Application.Current.Dispatcher.Invoke(() => { EchoResults.Add(new EchoResult { Started = startedTime, Processed = processedTime, ElapsedMs = (long)Math.Round(elapsedMs) }); }); } catch (Exception ex) { // 异常处理(可添加到UI提示) Application.Current.Dispatcher.Invoke(() => { MessageBox.Show($"回显失败:{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); }); } finally { // 释放资源 stream?.Close(); client?.Close(); } } // 命令定义 public static RoutedUICommand StartEchoCommand { get; } = new("启动回显", "StartEcho", typeof(WpfTimerWindow)); public static RoutedUICommand StopEchoCommand { get; } = new("停止回显", "StopEcho", typeof(WpfTimerWindow)); // 命令执行逻辑 public static ExecutedRoutedEventHandler ExecuteEchoCommand { get; } = (_, e) => { if (e.Parameter is WpfTimerWindow window) { if (e.Command == StartEchoCommand) { window._echoTimer.Start(); } else if (e.Command == StopEchoCommand) { window._echoTimer.Stop(); } } }; // 命令可用性逻辑 public static CanExecuteRoutedEventHandler CanExecuteEchoCommand { get; } = (_, e) => { if (e.Parameter is WpfTimerWindow window) { if (e.Command == StartEchoCommand) { e.CanExecute = !window._echoTimer.IsEnabled; } else if (e.Command == StopEchoCommand) { e.CanExecute = window._echoTimer.IsEnabled; } } }; protected virtual void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } }
3. 前端XAML代码改造
更新XAML,添加回显结果Grid和停止按钮:
<Window x:Class="PingApplication.WpfTimerWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:PingApplication" mc:Ignorable="d" Title="TCP回显客户端" Height="450" Width="800"> <Window.CommandBindings> <CommandBinding Command="{x:Static local:WpfTimerWindow.StartEchoCommand}" Executed="{x:Static local:WpfTimerWindow.ExecuteEchoCommand}" CanExecute="{x:Static local:WpfTimerWindow.CanExecuteEchoCommand}"/> <CommandBinding Command="{x:Static local:WpfTimerWindow.StopEchoCommand}" Executed="{x:Static local:WpfTimerWindow.ExecuteEchoCommand}" CanExecute="{x:Static local:WpfTimerWindow.CanExecuteEchoCommand}"/> </Window.CommandBindings> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <!-- 控制按钮区域 --> <StackPanel Grid.Row="0" Orientation="Horizontal" Margin="10"> <Button Padding="15 5" Margin="5" Content="启动回显" Command="{x:Static local:WpfTimerWindow.StartEchoCommand}" CommandParameter="{Binding}"/> <Button Padding="15 5" Margin="5" Content="停止回显" Command="{x:Static local:WpfTimerWindow.StopEchoCommand}" CommandParameter="{Binding}"/> <TextBlock Margin="10 0" VerticalAlignment="Center" Text="当前时间:{Binding Now, StringFormat='yyyy-MM-dd HH:mm:ss.fff'}"/> </StackPanel> <!-- 回显结果Grid --> <DataGrid Grid.Row="1" Margin="10" ItemsSource="{Binding EchoResults}" AutoGenerateColumns="False"> <DataGrid.Columns> <DataGridTextColumn Header="Started" Binding="{Binding Started, StringFormat='yyyy-MM-dd HH:mm:ss.fff'}" Width="*"/> <DataGridTextColumn Header="Processed" Binding="{Binding Processed, StringFormat='yyyy-MM-dd HH:mm:ss.fff'}" Width="*"/> <DataGridTextColumn Header="Elapsed(ms)" Binding="{Binding ElapsedMs}" Width="120"/> </DataGrid.Columns> </DataGrid> </Grid> </Window>
关键注意事项
- 线程安全:使用
DispatcherTimer而非System.Timers.Timer,确保定时器回调在UI线程执行;更新UI集合时通过Application.Current.Dispatcher.Invoke确保线程安全。 - JSON序列化:使用
System.Text.Json进行序列化/反序列化,默认支持GUID类型;若需要更复杂的配置可添加JsonSerializerOptions。 - TCP资源释放:在
finally块中关闭NetworkStream和TcpClient,避免资源泄漏。 - 异常处理:添加全局异常捕获,避免单个回显失败导致整个流程中断,并通过UI提示用户。
内容的提问来源于stack exchange,提问作者Juju22 Nimza
相关产品推荐
相关产品推荐

