You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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>

关键注意事项

  1. 线程安全:使用DispatcherTimer而非System.Timers.Timer,确保定时器回调在UI线程执行;更新UI集合时通过Application.Current.Dispatcher.Invoke确保线程安全。
  2. JSON序列化:使用System.Text.Json进行序列化/反序列化,默认支持GUID类型;若需要更复杂的配置可添加JsonSerializerOptions。
  3. TCP资源释放:在finally块中关闭NetworkStream和TcpClient,避免资源泄漏。
  4. 异常处理:添加全局异常捕获,避免单个回显失败导致整个流程中断,并通过UI提示用户。

内容的提问来源于stack exchange,提问作者Juju22 Nimza

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 16:55:27