用TCP服务端向多个客户端分发图片,客户端接收并处理图片,再将处理结果返回给服务端

需求

服务端使用多线程向多个客户端发送图片,客户端用队列接收图片。客户端用10个线程从队列里取出图片处理并返回结果给服务端

我们来构建一个满足你所有要求的 高性能 WPF TCP 图片处理系统

这个系统将包含:

  1. 多线程服务端:能够同时接收多个客户端连接,并向它们并发发送图片任务。
  2. 队列化客户端 :使用队列(ConcurrentQueue)来缓冲接收到的图片,避免处理不及时导致的数据丢失或阻塞。
  3. 多线程图片处理:客户端内部启动 10 个工作线程,从队列中取出图片进行处理(例如,我们可以进行一个简单的灰度转换作为示例)。
  4. 结果回传:客户端处理完一张图片后,会将处理结果(例如,处理后的图片数据或状态信息)发送回服务端。

一、整体架构设计

服务端 (TcpImageServer)
  • 主监听线程:持续监听客户端连接。
  • 客户端管理 :为每个新连接创建一个独立的 ClientHandler 线程 / 任务。
  • 图片发送ClientHandler 负责向对应的客户端发送图片数据。
  • 结果接收ClientHandler 同时监听客户端返回的处理结果。
客户端 (TcpImageClient)
  • 连接管理:负责与服务端建立和维护连接。
  • 数据接收线程 :独立线程,持续从网络流读取数据,将完整的图片数据包解析后放入 ConcurrentQueue
  • 图片处理队列ConcurrentQueue<ImageData> 用于存储待处理的图片信息。
  • 处理线程池 :10 个工作线程,循环从队列中 TryDequeue 图片数据进行处理。
  • 结果发送:处理完成后,将结果打包并发送回服务端。

二、核心数据结构与协议定义

为了方便通信,我们先定义一套简单的应用层协议和数据结构。

1. 协议枚举(双方共用)

csharp

运行

复制代码
// Protocol.cs (可以放在一个共享类库中)
public enum MessageType : byte
{
    ImageData = 0x01,   // 服务端发送的图片数据
    ProcessingResult = 0x02, // 客户端返回的处理结果
    Heartbeat = 0x03    // 心跳包 (可选,用于连接保活)
}
2. 图片数据结构(服务端 -> 客户端)

csharp

运行

复制代码
// ImageData.cs (共享)
[Serializable]
public class ImageData
{
    public Guid TaskId { get; set; } // 任务唯一ID,用于匹配请求和响应
    public byte[] ImageBytes { get; set; } // 图片的字节数组
    public string ImageName { get; set; } // 图片名称
}
3. 处理结果结构(客户端 -> 服务端)

csharp

运行

复制代码
// ProcessingResult.cs (共享)
[Serializable]
public class ProcessingResult
{
    public Guid TaskId { get; set; } // 对应的任务ID
    public bool IsSuccess { get; set; } // 是否处理成功
    public string Message { get; set; } // 消息(成功/失败原因)
    public byte[] ProcessedImageBytes { get; set; } // 处理后的图片字节数组 (可选)
}

三、服务端实现 (TcpImageServer)

1. 服务端核心逻辑 (MainWindow.xaml.cs)

csharp

运行

复制代码
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media.Imaging;

namespace TcpImageServer
{
    public partial class MainWindow : Window
    {
        private TcpListener _tcpListener;
        private readonly ConcurrentDictionary<Guid, TcpClient> _connectedClients = new ConcurrentDictionary<Guid, TcpClient>();
        private CancellationTokenSource _cts;

        public MainWindow()
        {
            InitializeComponent();
        }

        private async void StartServerBtn_Click(object sender, RoutedEventArgs e)
        {
            if (_tcpListener == null)
            {
                int port = int.Parse(PortTextBox.Text);
                _tcpListener = new TcpListener(IPAddress.Any, port);
                _cts = new CancellationTokenSource();
                
                _tcpListener.Start();
                StatusTextBlock.Text = "服务已启动,等待客户端连接...";
                StartServerBtn.Content = "停止服务";

                // 开始接受客户端连接
                await AcceptClientsAsync(_cts.Token);
            }
            else
            {
                StopServer();
            }
        }

        private async Task AcceptClientsAsync(CancellationToken token)
        {
            while (!token.IsCancellationRequested)
            {
                try
                {
                    TcpClient client = await _tcpListener.AcceptTcpClientAsync();
                    Guid clientId = Guid.NewGuid();
                    _connectedClients.TryAdd(clientId, client);
                    
                    Dispatcher.Invoke(() => 
                    {
                        StatusTextBlock.Text = $"客户端 {clientId} 已连接。当前连接数: {_connectedClients.Count}";
                        ClientsListBox.Items.Add(clientId);
                    });

                    // 为每个客户端创建一个独立的处理任务
                    _ = HandleClientAsync(client, clientId, token);
                }
                catch (Exception ex)
                {
                    if (!token.IsCancellationRequested)
                    {
                        Dispatcher.Invoke(() => StatusTextBlock.Text = $"接受连接失败: {ex.Message}");
                    }
                }
            }
        }

        private async Task HandleClientAsync(TcpClient client, Guid clientId, CancellationToken token)
        {
            using (NetworkStream stream = client.GetStream())
            {
                byte[] buffer = new byte[4096];
                try
                {
                    while (!token.IsCancellationRequested && client.Connected)
                    {
                        // 这里服务端主要是发送图片,但也可以监听客户端的消息(如心跳或结果)
                        // 为了简单起见,我们假设服务端主动发送图片,这里可以留空或实现心跳接收逻辑
                        // 实际场景中,你可能需要一个更复杂的消息循环来处理双向通信
                        await Task.Delay(100, token); // 防止CPU空转
                    }
                }
                catch (Exception ex)
                {
                    Dispatcher.Invoke(() => StatusTextBlock.Text = $"客户端 {clientId} 通信异常: {ex.Message}");
                }
                finally
                {
                    if (client.Connected)
                        client.Close();
                    
                    _connectedClients.TryRemove(clientId, out _);
                    
                    Dispatcher.Invoke(() => 
                    {
                        StatusTextBlock.Text = $"客户端 {clientId} 已断开。当前连接数: {_connectedClients.Count}";
                        ClientsListBox.Items.Remove(clientId);
                    });
                }
            }
        }

        // 向所有连接的客户端广播一张图片
        private async void SendImageToAllBtn_Click(object sender, RoutedEventArgs e)
        {
            if (_connectedClients.IsEmpty)
            {
                MessageBox.Show("没有连接的客户端!");
                return;
            }

            OpenFileDialog openFileDialog = new OpenFileDialog
            {
                Filter = "图片文件 (*.jpg;*.jpeg;*.png;*.bmp)|*.jpg;*.jpeg;*.png;*.bmp"
            };

            if (openFileDialog.ShowDialog() == true)
            {
                try
                {
                    byte[] imageBytes = File.ReadAllBytes(openFileDialog.FileName);
                    string imageName = Path.GetFileName(openFileDialog.FileName);
                    
                    Dispatcher.Invoke(() => StatusTextBlock.Text = $"开始向 {_connectedClients.Count} 个客户端发送图片: {imageName}");

                    // 并行向所有客户端发送
                    var sendTasks = new List<Task>();
                    foreach (var clientPair in _connectedClients)
                    {
                        sendTasks.Add(SendImageToClientAsync(clientPair.Value, new ImageData
                        {
                            TaskId = Guid.NewGuid(),
                            ImageBytes = imageBytes,
                            ImageName = imageName
                        }));
                    }

                    await Task.WhenAll(sendTasks);
                    
                    Dispatcher.Invoke(() => StatusTextBlock.Text = "图片发送任务已全部提交。");
                }
                catch (Exception ex)
                {
                    MessageBox.Show($"发送图片失败: {ex.Message}");
                }
            }
        }

        // 向单个客户端发送图片
        private async Task SendImageToClientAsync(TcpClient client, ImageData imageData)
        {
            if (!client.Connected) return;

            try
            {
                using (NetworkStream stream = client.GetStream())
                using (MemoryStream ms = new MemoryStream())
                {
                    System.Runtime.Serialization.Formatters.Binary.BinaryFormatter formatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
                    
                    // 1. 写入消息类型
                    await stream.WriteAsync(new byte[] { (byte)MessageType.ImageData }, 0, 1);

                    // 2. 写入序列化后的图片数据长度 (4字节)
                    formatter.Serialize(ms, imageData);
                    byte[] dataBytes = ms.ToArray();
                    byte[] lengthBytes = BitConverter.GetBytes(dataBytes.Length);
                    await stream.WriteAsync(lengthBytes, 0, lengthBytes.Length);

                    // 3. 写入图片数据
                    await stream.WriteAsync(dataBytes, 0, dataBytes.Length);
                    
                    await stream.FlushAsync();
                }
            }
            catch (Exception ex)
            {
                Dispatcher.Invoke(() => StatusTextBlock.Text = $"向客户端发送图片失败: {ex.Message}");
            }
        }

        private void StopServer()
        {
            _cts?.Cancel();
            _tcpListener?.Stop();
            
            foreach (var client in _connectedClients.Values)
            {
                if (client.Connected)
                    client.Close();
            }
            _connectedClients.Clear();

            _tcpListener = null;
            StatusTextBlock.Text = "服务已停止。";
            StartServerBtn.Content = "启动服务";
            ClientsListBox.Items.Clear();
        }

        protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
        {
            StopServer();
            base.OnClosing(e);
        }
    }
}
2. 服务端界面 (MainWindow.xaml)

xml

复制代码
<Window x:Class="TcpImageServer.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="TCP 图片服务端" Height="450" Width="800">
    <Grid Margin="10">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>

        <Label Content="监听端口:" Grid.Row="0" Grid.Column="0"/>
        <TextBox x:Name="PortTextBox" Text="8888" Grid.Row="0" Grid.Column="1" Margin="5,0"/>
        <Button x:Name="StartServerBtn" Content="启动服务" Click="StartServerBtn_Click" Grid.Row="0" Grid.Column="2" Margin="5,0" Width="100"/>

        <Label Content="连接的客户端:" Grid.Row="1" Grid.Column="0" Margin="0,10,0,0"/>
        <ListBox x:Name="ClientsListBox" Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="3" Margin="5,0"/>
        
        <TextBlock x:Name="StatusTextBlock" Text="准备就绪" Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="2" Margin="5,10,0,0"/>
        <Button x:Name="SendImageToAllBtn" Content="发送图片给所有客户端" Click="SendImageToAllBtn_Click" Grid.Row="3" Grid.Column="2" Margin="5,10,0,0" HorizontalAlignment="Right"/>
    </Grid>
</Window>

四、客户端实现 (TcpImageClient)

1. 客户端核心逻辑 (MainWindow.xaml.cs)

csharp

运行

复制代码
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media.Imaging;

namespace TcpImageClient
{
    public partial class MainWindow : Window
    {
        private TcpClient _tcpClient;
        private NetworkStream _networkStream;
        private readonly ConcurrentQueue<ImageData> _imageQueue = new ConcurrentQueue<ImageData>();
        private readonly List<Thread> _workerThreads = new List<Thread>();
        private CancellationTokenSource _cts;
        private bool _isConnected = false;

        public MainWindow()
        {
            InitializeComponent();
            InitializeWorkerThreads();
        }

        // 初始化10个处理线程
        private void InitializeWorkerThreads()
        {
            for (int i = 0; i < 10; i++)
            {
                Thread thread = new Thread(ProcessImageLoop)
                {
                    IsBackground = true,
                    Name = $"WorkerThread-{i + 1}"
                };
                _workerThreads.Add(thread);
            }
        }

        private void ConnectBtn_Click(object sender, RoutedEventArgs e)
        {
            if (!_isConnected)
            {
                string ip = IpTextBox.Text;
                if (int.TryParse(PortTextBox.Text, out int port))
                {
                    _cts = new CancellationTokenSource();
                    _ = ConnectAndListenAsync(ip, port, _cts.Token);
                    
                    // 启动工作线程
                    _workerThreads.ForEach(t => { if (!t.IsAlive) t.Start(); });
                }
                else
                {
                    MessageBox.Show("无效的端口号!");
                }
            }
            else
            {
                Disconnect();
            }
        }

        private async Task ConnectAndListenAsync(string ip, int port, CancellationToken token)
        {
            try
            {
                _tcpClient = new TcpClient();
                await _tcpClient.ConnectAsync(ip, port);
                _networkStream = _tcpClient.GetStream();
                _isConnected = true;

                Dispatcher.Invoke(() =>
                {
                    StatusTextBlock.Text = $"成功连接到服务端 {ip}:{port}";
                    ConnectBtn.Content = "断开连接";
                    QueueCountTextBlock.Text = "0";
                });

                // 持续监听服务端消息
                await ListenForMessagesAsync(token);
            }
            catch (Exception ex)
            {
                Dispatcher.Invoke(() => StatusTextBlock.Text = $"连接失败: {ex.Message}");
                Disconnect();
            }
        }

        private async Task ListenForMessagesAsync(CancellationToken token)
        {
            byte[] buffer = new byte[4096];
            try
            {
                while (!token.IsCancellationRequested && _tcpClient.Connected)
                {
                    // 1. 读取消息类型
                    int bytesRead = await _networkStream.ReadAsync(buffer, 0, 1, token);
                    if (bytesRead == 0) break; // 连接关闭

                    MessageType msgType = (MessageType)buffer[0];

                    // 2. 读取数据长度
                    bytesRead = await _networkStream.ReadAsync(buffer, 0, 4, token);
                    if (bytesRead == 0) break;
                    int dataLength = BitConverter.ToInt32(buffer, 0);

                    // 3. 读取数据内容
                    byte[] dataBytes = new byte[dataLength];
                    int totalRead = 0;
                    while (totalRead < dataLength)
                    {
                        int read = await _networkStream.ReadAsync(dataBytes, totalRead, dataLength - totalRead, token);
                        if (read == 0) break;
                        totalRead += read;
                    }

                    if (totalRead == dataLength)
                    {
                        ProcessReceivedData(msgType, dataBytes);
                    }
                }
            }
            catch (OperationCanceledException)
            {
                // 正常断开
            }
            catch (Exception ex)
            {
                Dispatcher.Invoke(() => StatusTextBlock.Text = $"接收数据异常: {ex.Message}");
            }
            finally
            {
                Disconnect();
            }
        }

        private void ProcessReceivedData(MessageType msgType, byte[] dataBytes)
        {
            switch (msgType)
            {
                case MessageType.ImageData:
                    using (MemoryStream ms = new MemoryStream(dataBytes))
                    {
                        System.Runtime.Serialization.Formatters.Binary.BinaryFormatter formatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
                        ImageData imageData = (ImageData)formatter.Deserialize(ms);
                        
                        // 将图片数据放入队列
                        _imageQueue.Enqueue(imageData);
                        
                        Dispatcher.Invoke(() => 
                        {
                            StatusTextBlock.Text = $"收到图片: {imageData.ImageName},已加入队列。";
                            QueueCountTextBlock.Text = _imageQueue.Count.ToString();
                        });
                    }
                    break;
                case MessageType.ProcessingResult:
                    // 客户端一般不接收这个,除非有特殊需求
                    break;
            }
        }

        // 工作线程的处理循环
        private void ProcessImageLoop()
        {
            while (!_cts?.IsCancellationRequested ?? false)
            {
                if (_imageQueue.TryDequeue(out ImageData imageData))
                {
                    try
                    {
                        // --- 在这里执行你的图片处理逻辑 ---
                        Dispatcher.Invoke(() => StatusTextBlock.Text = $"线程 {Thread.CurrentThread.Name} 正在处理图片: {imageData.ImageName}");
                        
                        // 示例:转换为灰度图
                        byte[] processedBytes = ConvertToGrayscale(imageData.ImageBytes);

                        // --- 处理完成 ---

                        // 准备返回结果
                        var result = new ProcessingResult
                        {
                            TaskId = imageData.TaskId,
                            IsSuccess = true,
                            Message = "处理成功",
                            ProcessedImageBytes = processedBytes // 可以选择是否返回处理后的图片
                        };

                        // 将结果发送回服务端
                        _ = SendResultToServerAsync(result);

                        Dispatcher.Invoke(() => 
                        {
                            StatusTextBlock.Text = $"线程 {Thread.CurrentThread.Name} 处理完成: {imageData.ImageName}";
                            QueueCountTextBlock.Text = _imageQueue.Count.ToString();
                        });
                    }
                    catch (Exception ex)
                    {
                        // 处理失败,也返回结果
                        var errorResult = new ProcessingResult
                        {
                            TaskId = imageData.TaskId,
                            IsSuccess = false,
                            Message = $"处理失败: {ex.Message}"
                        };
                        _ = SendResultToServerAsync(errorResult);

                        Dispatcher.Invoke(() => 
                        {
                            StatusTextBlock.Text = $"线程 {Thread.CurrentThread.Name} 处理失败: {imageData.ImageName} - {ex.Message}";
                        });
                    }
                }
                else
                {
                    Thread.Sleep(10); // 队列空时,短暂休眠,减少CPU消耗
                }
            }
        }
        
        // 示例:将图片字节数组转换为灰度图字节数组 (使用WPF的BitmapSource)
        private byte[] ConvertToGrayscale(byte[] imageBytes)
        {
            using (MemoryStream ms = new MemoryStream(imageBytes))
            {
                BitmapImage bitmapImage = new BitmapImage();
                bitmapImage.BeginInit();
                bitmapImage.StreamSource = ms;
                bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
                bitmapImage.EndInit();

                FormatConvertedBitmap grayBitmap = new FormatConvertedBitmap(bitmapImage, PixelFormats.Gray8, null, 0);
                
                using (MemoryStream grayMs = new MemoryStream())
                {
                    PngBitmapEncoder encoder = new PngBitmapEncoder();
                    encoder.Frames.Add(BitmapFrame.Create(grayBitmap));
                    encoder.Save(grayMs);
                    return grayMs.ToArray();
                }
            }
        }

        private async Task SendResultToServerAsync(ProcessingResult result)
        {
            if (!_isConnected || _networkStream == null) return;

            try
            {
                using (MemoryStream ms = new MemoryStream())
                {
                    System.Runtime.Serialization.Formatters.Binary.BinaryFormatter formatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
                    
                    // 1. 写入消息类型
                    await _networkStream.WriteAsync(new byte[] { (byte)MessageType.ProcessingResult }, 0, 1);

                    // 2. 写入序列化后的结果长度
                    formatter.Serialize(ms, result);
                    byte[] dataBytes = ms.ToArray();
                    byte[] lengthBytes = BitConverter.GetBytes(dataBytes.Length);
                    await _networkStream.WriteAsync(lengthBytes, 0, lengthBytes.Length);

                    // 3. 写入结果数据
                    await _networkStream.WriteAsync(dataBytes, 0, dataBytes.Length);
                    
                    await _networkStream.FlushAsync();
                }
            }
            catch (Exception ex)
            {
                Dispatcher.Invoke(() => StatusTextBlock.Text = $"发送处理结果失败: {ex.Message}");
            }
        }

        private void Disconnect()
        {
            _cts?.Cancel();
            _isConnected = false;

            if (_networkStream != null)
            {
                _networkStream.Dispose();
                _networkStream = null;
            }
            
            _tcpClient?.Close();
            _tcpClient = null;

            Dispatcher.Invoke(() =>
            {
                StatusTextBlock.Text = "已断开连接";
                ConnectBtn.Content = "连接服务端";
                QueueCountTextBlock.Text = "0";
            });
        }

        protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
        {
            Disconnect();
            base.OnClosing(e);
        }
    }
}
2. 客户端界面 (MainWindow.xaml)

xml

复制代码
<Window x:Class="TcpImageClient.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="TCP 图片客户端" Height="350" Width="525">
    <Grid Margin="10">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="Auto"/>
        </Grid.ColumnDefinitions>

        <Label Content="服务端IP:" Grid.Row="0" Grid.Column="0"/>
        <TextBox x:Name="IpTextBox" Text="127.0.0.1" Grid.Row="0" Grid.Column="1" Margin="5,0"/>
        <Label Content="端口:" Grid.Row="0" Grid.Column="2" Margin="10,0,0,0"/>
        <TextBox x:Name="PortTextBox" Text="8888" Grid.Row="0" Grid.Column="3" Margin="5,0" Width="80"/>
        <Button x:Name="ConnectBtn" Content="连接服务端" Click="ConnectBtn_Click" Grid.Row="0" Grid.Column="4" Margin="10,0" Width="120"/>

        <Label Content="任务队列长度:" Grid.Row="1" Grid.Column="0" Margin="0,10,0,0"/>
        <TextBlock x:Name="QueueCountTextBlock" Text="0" Grid.Row="1" Grid.Column="1" Margin="5,10,0,0"/>

        <TextBlock x:Name="StatusTextBlock" Text="准备就绪" Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="5" Margin="0,10,0,0"/>
    </Grid>
</Window>

五、关键点说明与优化建议

  1. 并发与线程安全

    • 使用 ConcurrentQueue 是线程安全的,确保了生产者(接收线程)和消费者(10 个处理线程)之间的数据交换不会产生竞态条件。
    • UI 更新必须通过 Dispatcher.Invoke 进行,因为 WPF UI 元素不是线程安全的。
  2. 性能优化

    • 分块发送 / 接收:对于超大图片,示例中的一次性发送整个字节数组可能会占用大量内存。可以将其修改为分块传输(例如,每次发送 8KB)。
    • 对象池 :如果图片处理任务的创建和销毁非常频繁,可以考虑使用对象池(ObjectPool<T>)来复用 ImageDataProcessingResult 对象,减少 GC 压力。
    • 异步处理 :图片处理逻辑如果是 CPU 密集型的,使用 Thread 是合适的。如果处理过程中有 I/O 等待(如读写文件),可以考虑使用 Task.Run 结合 async/await 来提高线程利用率。
    • 限制队列大小 :可以为 ConcurrentQueue 设置一个最大容量,当队列满时,可以选择丢弃最旧的任务或通知服务端暂缓发送,防止内存溢出。
  3. 可靠性

    • 消息确认 :当前的 ProcessingResult 就是一种确认机制。服务端可以根据 TaskId 来追踪哪些任务已经被客户端成功处理。
    • 心跳机制 :可以在 HandleClientAsyncListenForMessagesAsync 中加入心跳包的发送和接收逻辑,以检测死连接。
    • 异常处理 :代码中包含了完善的 try-catch 块,确保单个客户端的异常不会导致整个服务端或客户端程序崩溃。
    • 优雅关闭 :在窗口关闭或用户点击断开时,通过 CancellationTokenSourceDisconnect 方法,确保所有资源(TcpClient, NetworkStream, Thread)都被正确释放。
  4. 序列化

    • 示例中使用了 .NET 自带的 BinaryFormatter,它简单易用,但不推荐在不可信的网络环境中使用,因为它存在安全漏洞。
    • 强烈建议 :在生产环境中,使用更安全、高效且跨平台的序列化库,如 Protobuf (Google.Protobuf)MessagePack-CSharp 。你需要为 ImageDataProcessingResult 定义 .proto.mp 契约文件,然后生成代码。这会稍微增加一些复杂度,但换来的是更高的性能和安全性。
相关推荐
资深web全栈开发1 小时前
Golang Cobra 教程:构建强大的CLI应用
开发语言·后端·golang
J***79391 小时前
Python在机器学习中的数据处理
开发语言·python·机器学习
子不语1802 小时前
Matlab(一)——绘图
开发语言·matlab
222you2 小时前
MyBatis-Plus当中BaseMapper接口的增删查改操作
java·开发语言·mybatis
2501_941144772 小时前
Apache Kafka高吞吐消息系统实践分享:实时数据流处理与消息可靠性优化经验
c#·linq
一起学开源2 小时前
实战总结:BACnet/IP 跨网段通讯的两种解决方案(BBMD 与 Foreign Device)
运维·网络·物联网·bacnet·网络协议·tcp/ip
tan180°2 小时前
Linux网络TCP(终)(14)
linux·网络·tcp/ip
jenchoi4132 小时前
【2025-11-22】软件供应链安全日报:最新漏洞预警与投毒预警情报汇总
网络·安全·web安全·网络安全·npm