news 2026/7/17 4:10:47

(十二)通讯(03):C#上位机TCP/IP通讯与海康相机软触发拍照

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
(十二)通讯(03):C#上位机TCP/IP通讯与海康相机软触发拍照

一、环境配置

1、创建wpf工程

文件->新建->解决方案

C#->WPF应用程序

修改配置管理器更改为release/X64

运行->工程配置成功

2、TCP/IP通讯依赖项

using System.Net; using System.Net.Sockets;

二、TCP服务器后台代码实现

1、客户端信息结构体,用于管理连接的客户端

public class ClientInfo { public Socket ClientSocket { get; set; } public string ClientId { get; set; } public DateTime ConnectTime { get; set; } public string RemoteEndPoint { get; set; } public bool IsConnected { get; set; } public Thread ClientThread { get; set; } public DateTime LastActivityTime { get; set; } }

2、变量

private Socket _serverSocket; private Thread _listenerThread; private Thread _heartbeatThread; private readonly List<ClientInfo> _connectedClients = new List<ClientInfo>(); private readonly object _clientListLock = new object(); private bool _isRunning = false; private bool _disposed = false; private readonly int _receiveBufferSize = 1024; private readonly int _heartbeatInterval = 30; // 秒 public IPAddress ip = IPAddress.Parse("127.0.0.1");

3、事件

public event Action<ClientInfo> OnClientConnected; /// <summary> /// 客户端断开事件 /// </summary> public event Action<ClientInfo> OnClientDisconnected; /// <summary> /// 接收到数据事件 /// </summary> public event Action<ClientInfo, string> OnDataReceived; /// <summary> /// 服务器启动事件 /// </summary> public event Action OnServerStarted; /// <summary> /// 服务器停止事件 /// </summary> public event Action OnServerStopped; /// <summary> /// 日志事件 /// </summary> public event Action<DateTime, string, string> OnLogMessage; #endregion

4、构造函数

public TcpServer() { } /// <summary> /// 构造函数 /// </summary> /// <param name="port">监听端口</param> public TcpServer(int port) { Port = port; }

5、开启监听

public bool Start(int port = 0) { if (_isRunning) { LogWarning("服务器已经在运行中"); return false; } try { if (port > 0) { Port = port; } if (Port <= 0) { LogError("端口号无效"); return false; } // 创建服务器Socket _serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); // 设置Socket选项:允许地址重用 _serverSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); // 绑定IP和端口 IPEndPoint localEndPoint = new IPEndPoint(ip, Port); _serverSocket.Bind(localEndPoint); // 开始监听,设置最大等待连接数 _serverSocket.Listen(100); _isRunning = true; // 启动监听线程 _listenerThread = new Thread(ListenerThreadProc) { IsBackground = true, Name = "TCP_Server_Listener" }; _listenerThread.Start(); // 启动心跳检测线程 _heartbeatThread = new Thread(HeartbeatThreadProc) { IsBackground = true, Name = "TCP_Server_Heartbeat" }; _heartbeatThread.Start(); LogInfo($"TCP服务器启动成功,监听端口: {Port}"); OnServerStarted?.Invoke(); return true; } catch (Exception ex) { LogError($"启动服务器失败: {ex.Message}", ex); _isRunning = false; return false; } }

6、结束监听

public void Stop() { if (!_isRunning) return; LogInfo("正在停止服务器..."); _isRunning = false; try { // 断开所有客户端连接 DisconnectAllClients(); // 关闭服务器Socket if (_serverSocket != null) { _serverSocket.Close(); _serverSocket = null; } // 等待监听线程结束 if (_listenerThread != null && _listenerThread.IsAlive) { _listenerThread.Join(2000); } // 等待心跳线程结束 if (_heartbeatThread != null && _heartbeatThread.IsAlive) { _heartbeatThread.Join(1000); } LogInfo("服务器已停止"); OnServerStopped?.Invoke(); } catch (Exception ex) { LogError($"停止服务器时出错: {ex.Message}", ex); } }

7、发送和接收

public bool SendToClient(ClientInfo client, string message) { if (client == null || !client.IsConnected || client.ClientSocket == null) { return false; } try { byte[] data = Encoding.UTF8.GetBytes(message); int bytesSent = client.ClientSocket.Send(data); LogDebug($"向客户端 {client.ClientId} 发送了 {bytesSent} 字节"); return bytesSent > 0; } catch (Exception ex) { LogError($"向客户端 {client.ClientId} 发送消息失败: {ex.Message}", ex); HandleClientDisconnect(client); return false; } }

接收数据在主界面注册事件,方便在UI上显示

server.OnDataReceived += server_OnDataReceived;
private void server_OnDataReceived(ClientInfo client, string data) { // 确保在 UI 线程操作 ReceiveBox Application.Current.Dispatcher.Invoke(() => { string time = DateTime.Now.ToString("HH:mm:ss"); // 格式化客户端信息 string clientInfo = $"{"Client-"+client.ClientId}"; // 按指定格式追加到文本框,每条数据换行 ReceiveBox.AppendText($" {clientInfo} { time}:{data}\n"); // 可选:自动滚动到最新内容 ReceiveBox.ScrollToEnd(); }); }

三、TCP客户端后台代码实现

1、变量

#region 私有字段 private Socket _clientSocket; private Thread _receiveThread; private bool _isRunning = false; private bool _disposed = false; private int _receiveBufferSize = 1024; private readonly object _sendLock = new object(); private string _remoteEndPoint;

2、事件

#region 事件 /// <summary> /// 连接成功事件 /// </summary> public event Action OnConnected; /// <summary> /// 断开连接事件(参数为断开原因) /// </summary> public event Action<string> OnDisconnected; /// <summary> /// 收到数据事件(参数为原始字符串) /// </summary> public event Action<string> OnDataReceived; /// <summary> /// 发生错误事件(参数为异常和消息) /// </summary> public event Action<Exception, string> OnError; /// <summary> /// 日志事件 /// </summary> public event Action<DateTime, string, string> OnLogMessage; #endregion

3、连接

/// <summary> /// 连接到服务器 /// </summary> /// <param name="host">服务器 IP 或主机名</param> /// <param name="port">端口号</param> /// <returns>是否连接成功</returns> public bool Connect(string host, int port) { if (_isRunning) { LogWarning("客户端已处于连接状态"); return false; } try { // 解析主机地址 IPAddress ipAddress; if (!IPAddress.TryParse(host, out ipAddress)) { // 尝试解析主机名 var hostEntry = Dns.GetHostEntry(host); ipAddress = hostEntry.AddressList[0]; } IPEndPoint remoteEndPoint = new IPEndPoint(ipAddress, port); _clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); // 连接服务器 _clientSocket.Connect(remoteEndPoint); _remoteEndPoint = _clientSocket.RemoteEndPoint?.ToString(); // 设置接收超时(30秒,防止线程阻塞) _clientSocket.ReceiveTimeout = 30000; _isRunning = true; // 启动接收线程 _receiveThread = new Thread(ReceiveThreadProc) { IsBackground = true, Name = "TCP_Client_Receiver" }; _receiveThread.Start(); LogInfo($"已连接到服务器 {_remoteEndPoint}"); OnConnected?.Invoke(); return true; } catch (Exception ex) { LogError($"连接服务器失败: {ex.Message}", ex); OnError?.Invoke(ex, "连接失败"); return false; } }

4、断开

/// <summary> /// 断开连接 /// </summary> public void Disconnect() { if (!_isRunning) return; LogInfo("正在断开连接..."); _isRunning = false; try { // 关闭 Socket if (_clientSocket != null) { if (_clientSocket.Connected) { _clientSocket.Shutdown(SocketShutdown.Both); } _clientSocket.Close(); _clientSocket = null; } // 等待接收线程结束 if (_receiveThread != null && _receiveThread.IsAlive) { _receiveThread.Join(2000); } LogInfo("已断开连接"); OnDisconnected?.Invoke("主动断开"); } catch (Exception ex) { LogError($"断开连接时出错: {ex.Message}", ex); } }

5、发送和接收

/// <summary> /// 发送字符串数据(UTF-8 编码) /// </summary> /// <param name="message">要发送的字符串</param> /// <returns>是否发送成功</returns> public bool Send(string message) { if (string.IsNullOrEmpty(message)) { LogWarning("发送内容为空"); return false; } if (!IsConnected) { LogWarning("未连接到服务器,无法发送"); return false; } try { byte[] data = Encoding.UTF8.GetBytes(message); lock (_sendLock) { int bytesSent = _clientSocket.Send(data); LogDebug($"发送了 {bytesSent} 字节"); return bytesSent > 0; } } catch (Exception ex) { LogError($"发送失败: {ex.Message}", ex); OnError?.Invoke(ex, "发送失败"); HandleDisconnect("发送异常"); return false; } }
#region 接收线程 private void ReceiveThreadProc() { LogInfo("接收线程已启动"); byte[] buffer = new byte[_receiveBufferSize]; try { while (_isRunning && _clientSocket != null && _clientSocket.Connected) { try { int bytesReceived = _clientSocket.Receive(buffer); if (bytesReceived == 0) { // 服务器主动关闭连接 LogInfo("服务器已关闭连接"); break; } // 获取数据字符串 string receivedData = Encoding.UTF8.GetString(buffer, 0, bytesReceived); LogDebug($"收到 {bytesReceived} 字节: {receivedData}"); // 处理心跳(服务器发送 "PING" 时回复 "PONG") if (receivedData == "PING") { LogDebug("收到心跳请求,回复 PONG"); Send("PONG"); continue; // 不触发数据事件 } // 触发数据接收事件 OnDataReceived?.Invoke(receivedData); } catch (SocketException ex) { if (ex.SocketErrorCode == SocketError.TimedOut) { // 接收超时,继续循环 continue; } else { LogError($"Socket 异常: {ex.Message}", ex); break; } } catch (Exception ex) { LogError($"接收数据异常: {ex.Message}", ex); break; } } } catch (Exception ex) { LogError($"接收线程异常: {ex.Message}", ex); } finally { // 线程结束时,如果仍在运行状态则触发断开 if (_isRunning) { HandleDisconnect("接收线程退出"); } LogInfo("接收线程已退出"); } }

接收数据在主界面注册事件,方便在UI上显示

client.OnDataReceived += client_OnDataReceived;
private void client_OnDataReceived(string data) { // 确保在 UI 线程操作 ReceiveBox Application.Current.Dispatcher.Invoke(() => { string time = DateTime.Now.ToString("HH:mm:ss"); // 格式化客户端信息 // 按指定格式追加到文本框,每条数据换行 ReceiveBox.AppendText($" {time}:{data}\n"); // 可选:自动滚动到最新内容 ReceiveBox.ScrollToEnd(); }); }

三、用WPF设计一个TCP调试助手

1、界面设计

(1)资源

<Window.Resources> <!-- 通用按钮样式 --> <Style TargetType="Button"> <Setter Property="FontFamily" Value="Segoe UI"/> <Setter Property="FontSize" Value="14"/> <Setter Property="Foreground" Value="White"/> <Setter Property="Padding" Value="16,8"/> <Setter Property="BorderThickness" Value="0"/> <Setter Property="Cursor" Value="Hand"/> <Setter Property="Effect"> <Setter.Value> <DropShadowEffect ShadowDepth="2" BlurRadius="6" Opacity="0.15"/> </Setter.Value> </Setter> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="Button"> <Border Background="{TemplateBinding Background}" CornerRadius="6" Padding="{TemplateBinding Padding}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"> <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/> </Border> <ControlTemplate.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter Property="Opacity" Value="0.85"/> </Trigger> <Trigger Property="IsPressed" Value="True"> <Setter Property="Opacity" Value="0.7"/> </Trigger> <Trigger Property="IsEnabled" Value="False"> <Setter Property="Opacity" Value="0.5"/> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style> <!-- 文本框样式 --> <Style TargetType="TextBox"> <Setter Property="FontFamily" Value="Consolas, Segoe UI"/> <Setter Property="FontSize" Value="13"/> <Setter Property="Background" Value="White"/> <Setter Property="BorderBrush" Value="#D0D7DE"/> <Setter Property="BorderThickness" Value="1"/> <Setter Property="Padding" Value="8,6"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="TextBox"> <Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="4"> <ScrollViewer x:Name="PART_ContentHost" Margin="{TemplateBinding Padding}"/> </Border> <ControlTemplate.Triggers> <Trigger Property="IsFocused" Value="True"> <Setter Property="BorderBrush" Value="#007ACC"/> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style> <!-- 标签样式 --> <Style TargetType="Label"> <Setter Property="FontFamily" Value="Segoe UI"/> <Setter Property="FontSize" Value="14"/> <Setter Property="Foreground" Value="#2C3E50"/> <Setter Property="VerticalContentAlignment" Value="Center"/> <Setter Property="Margin" Value="0"/> </Style> <!-- 复选框样式 --> <Style TargetType="CheckBox"> <Setter Property="FontFamily" Value="Segoe UI"/> <Setter Property="FontSize" Value="13"/> <Setter Property="Foreground" Value="#2C3E50"/> <Setter Property="VerticalContentAlignment" Value="Center"/> </Style> <!-- 分组边框样式 --> <Style x:Key="GroupBorder" TargetType="Border"> <Setter Property="Background" Value="White"/> <Setter Property="BorderBrush" Value="#E2E8F0"/> <Setter Property="BorderThickness" Value="1"/> <Setter Property="CornerRadius" Value="8"/> <Setter Property="Effect"> <Setter.Value> <DropShadowEffect ShadowDepth="2" BlurRadius="8" Opacity="0.06"/> </Setter.Value> </Setter> </Style> <!-- 标题文本样式 --> <Style x:Key="SectionTitle" TargetType="TextBlock"> <Setter Property="FontFamily" Value="Segoe UI"/> <Setter Property="FontSize" Value="15"/> <Setter Property="FontWeight" Value="SemiBold"/> <Setter Property="Foreground" Value="#2C3E50"/> <Setter Property="Margin" Value="0,0,0,8"/> </Style> </Window.Resources>

(2) 布局

<Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions>

(3) 标题栏和连接区

<!-- ========== 标题栏 ========== --> <TextBlock Grid.Row="0" Text="🔌 TCP 调试助手" FontSize="22" FontWeight="Bold" Foreground="#2C3E50" Margin="0,0,0,18" FontFamily="Segoe UI"/> <!-- ========== 连接设置区 ========== --> <Border Grid.Row="1" Style="{StaticResource GroupBorder}" Padding="16,14" Margin="0,0,0,18"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="100"/> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="140"/> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="100"/> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="Auto"/> </Grid.ColumnDefinitions> <Label Content="模式" Grid.Column="0" Margin="0,0,8,0"/> <ComboBox x:Name="ModeCombo" Grid.Column="1" Height="32" FontSize="13" SelectedIndex="0" Background="White" BorderBrush="#D0D7DE" Foreground="Black" VerticalContentAlignment="Center"> <ComboBoxItem Content="客户端" Background="White" IsSelected="True"/> <ComboBoxItem Content="服务端" Background="White" /> </ComboBox> <Label Content="本地 IP" Grid.Column="2" Margin="0,0,8,0"/> <TextBox x:Name="LocalIPBox" Grid.Column="3" Text="127.0.0.1" /> <Label Content="端口" Grid.Column="4" Margin="16,0,8,0"/> <TextBox x:Name="PortBox" Grid.Column="5" Text="8000" /> <Button x:Name="btnConnect" Grid.Column="6" Content="连接" Background="#27AE60" Height="36" Width="80" Margin="16,0,6,0" Click="btnConnect_Click"/> <Button x:Name="btnDisconnect" Grid.Column="7" Content="断开" Background="#E74C3C" Height="36" Width="80" Margin="0,0,16,0" Click="btnDisconnect_Click"/> <StackPanel Grid.Column="8" Orientation="Horizontal" VerticalAlignment="Center" Margin="0,0,16,0"> <Ellipse x:Name="Ellipse1" Width="12" Height="12" Fill="#E74C3C" Margin="0,0,6,0" /> <TextBlock x:Name="tbConnected" Text="未连接" Foreground="#E74C3C" FontSize="13" FontWeight="SemiBold" /> </StackPanel> </Grid> </Border>

(4)发送区和接收区

<!-- ========== 接收区 ========== --> <Grid Grid.Row="2" Margin="0,0,0,12"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <!-- 接收工具栏 --> <Grid Grid.Row="0" Margin="0,0,0,8"> <Grid.ColumnDefinitions> <ColumnDefinition Width="100"/> <ColumnDefinition Width="450"/> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="100"/> <ColumnDefinition Width="100"/> </Grid.ColumnDefinitions> <TextBlock Grid.Column="0" Text="📥 接收数据" Style="{StaticResource SectionTitle}"/> <CheckBox Grid.Column="2" Content="十六进制显示" Margin="0,0,16,0" VerticalAlignment="Center"/> <CheckBox Grid.Column="3" Content="暂停显示" Margin="0,0,16,0" VerticalAlignment="Center"/> <Button Grid.Column="4" Name="btnClear" Content="清空" Background="#95A5A6" Width="70" FontSize="13" Padding="0" Click="Button_Click" Margin="15,0,15,0"/> </Grid> <!-- 接收文本框 --> <Border Grid.Row="1" Style="{StaticResource GroupBorder}" Padding="6"> <ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto"> <TextBox x:Name="ReceiveBox" TextWrapping="Wrap" IsReadOnly="True" Background="#FAFCFE" FontSize="13" BorderThickness="0" VerticalScrollBarVisibility="Disabled" HorizontalScrollBarVisibility="Disabled" AcceptsReturn="True" MinHeight="200"> <TextBox.Effect> <DropShadowEffect ShadowDepth="0" BlurRadius="0" Opacity="0"/> </TextBox.Effect> </TextBox> </ScrollViewer> </Border> </Grid>
<!-- ========== 发送区 ========== --> <Grid Grid.Row="3"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <!-- 发送工具栏 --> <Grid Grid.Row="0" Margin="0,0,0,8"> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="*"/> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="Auto"/> </Grid.ColumnDefinitions> <TextBlock Grid.Column="0" Text="📤 发送数据" Style="{StaticResource SectionTitle}"/> <CheckBox Grid.Column="2" Content="十六进制发送" Margin="0,0,16,0" VerticalAlignment="Center"/> <CheckBox Grid.Column="3" Content="自动发送" Margin="0,0,10,0" VerticalAlignment="Center"/> <TextBox Grid.Column="4" Text="1000" Width="60" Height="28" FontSize="12" TextAlignment="Center" VerticalContentAlignment="Center"/> <TextBlock Grid.Column="5" Text="ms" FontSize="13" Foreground="#7F8C8D" VerticalAlignment="Center" Margin="6,0,0,0"/> </Grid> <!-- 发送输入框 --> <Border Grid.Row="1" Style="{StaticResource GroupBorder}" Padding="6" Margin="0,0,0,10"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="*"/> <ColumnDefinition Width="Auto"/> </Grid.ColumnDefinitions> <TextBox x:Name="SendBox" Grid.Column="0" TextWrapping="Wrap" AcceptsReturn="True" Background="#FAFCFE" FontSize="13" BorderThickness="0" MinHeight="70" VerticalScrollBarVisibility="Auto"> <TextBox.Effect> <DropShadowEffect ShadowDepth="0" BlurRadius="0" Opacity="0"/> </TextBox.Effect> </TextBox> <Button x:Name="SendBtn" Grid.Column="1" Content="发送" Background="#3498DB" Height="40" Width="90" FontSize="16" Margin="12,0,0,0" VerticalAlignment="Center"/> </Grid> </Border> <!-- 底部状态栏(可选) --> <TextBlock Grid.Row="2" Text="按 Ctrl+Enter 快速发送" FontSize="12" Foreground="#95A5A6" HorizontalAlignment="Right"/> </Grid>

(5)效果

运行

2、后台实现

(1)选择客户端和服务端时切换按钮

private void ModeCombo_SelectionChanged(object sender, SelectionChangedEventArgs e) { if (btnConnect == null || btnDisconnect == null) return; ComboBox combo = sender as ComboBox; if (combo.SelectedItem is ComboBoxItem item) { string content = item.Content.ToString(); // 执行您的切换逻辑 // 例如:更新界面、切换主题等 if (ModeCombo.SelectedIndex == 1) { btnConnect.Content = "监听"; btnDisconnect.Content = "结束"; } else { btnConnect.Content = "连接"; btnDisconnect.Content = "断开"; } } }

(2) 连接按钮,创建服务器或客户端

private void btnConnect_Click(object sender, RoutedEventArgs e) { string ipText = LocalIPBox.Text.Trim(); //校验IP是都合法 if (string.IsNullOrWhiteSpace(ipText)) { MessageBox.Show("IP 地址不能为空", "输入错误", MessageBoxButton.OK, MessageBoxImage.Warning); return; } if (!IPAddress.TryParse(ipText, out IPAddress ipFinal)) { MessageBox.Show("请输入合法的 IP 地址", "格式错误", MessageBoxButton.OK, MessageBoxImage.Warning); return; } if (ModeCombo.SelectedIndex == 1) { string portText = PortBox.Text.Trim(); if (IsValidPort(portText)) { int port = int.Parse(portText); server = new TcpServer(port); server.ip = ipFinal; server.OnDataReceived += server_OnDataReceived; } else { MessageBox.Show("请输入合法的端口号", "格式错误", MessageBoxButton.OK, MessageBoxImage.Warning); } //开启监听 bool isStartSuccess = server.Start(); if (!isStartSuccess) { MessageBox.Show("开启失败", "异常错误", MessageBoxButton.OK, MessageBoxImage.Warning); } else { btnDisconnect.IsEnabled = true; btnConnect.IsEnabled = false; tbConnected.Text = "已连接"; tbConnected.Foreground = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#27AE60")); Ellipse1.Fill = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#27AE60")); } }else { string portText = PortBox.Text.Trim(); if (IsValidPort(portText)) { int port = int.Parse(portText); client = new TcpClient(); client.OnDataReceived += client_OnDataReceived; bool isConnected=client.Connect(ipText, port); if (!isConnected) { MessageBox.Show("开启失败", "异常错误", MessageBoxButton.OK, MessageBoxImage.Warning); } else { btnDisconnect.IsEnabled = true; btnConnect.IsEnabled = false; tbConnected.Text = "已连接"; tbConnected.Foreground = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#27AE60")); Ellipse1.Fill = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#27AE60")); } } else { MessageBox.Show("请输入合法的端口号", "格式错误", MessageBoxButton.OK, MessageBoxImage.Warning); } } }

(3)断开连接按钮

private void btnDisconnect_Click(object sender, RoutedEventArgs e) { if (ModeCombo.SelectedIndex == 1) { if (server != null) { server.Stop(); server.Dispose(); } btnDisconnect.IsEnabled = false; btnConnect.IsEnabled = true; tbConnected.Text = "未连接"; tbConnected.Foreground = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#E74C3C")); Ellipse1.Fill = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#E74C3C")); } else { if (client != null) { client.Disconnect(); client.Dispose(); } btnDisconnect.IsEnabled = false; btnConnect.IsEnabled = true; tbConnected.Text = "未连接"; tbConnected.Foreground = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#E74C3C")); Ellipse1.Fill = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#E74C3C")); } }

(4)接收到数据的事件处理程序

private void server_OnDataReceived(ClientInfo client, string data) { // 确保在 UI 线程操作 ReceiveBox Application.Current.Dispatcher.Invoke(() => { string time = DateTime.Now.ToString("HH:mm:ss"); // 格式化客户端信息 string clientInfo = $"{"Client-"+client.ClientId}"; // 按指定格式追加到文本框,每条数据换行 ReceiveBox.AppendText($" {clientInfo} { time}:{data}\n"); // 可选:自动滚动到最新内容 ReceiveBox.ScrollToEnd(); }); }
private void client_OnDataReceived(string data) { // 确保在 UI 线程操作 ReceiveBox Application.Current.Dispatcher.Invoke(() => { string time = DateTime.Now.ToString("HH:mm:ss"); // 格式化客户端信息 // 按指定格式追加到文本框,每条数据换行 ReceiveBox.AppendText($" {time}:{data}\n"); // 可选:自动滚动到最新内容 ReceiveBox.ScrollToEnd(); }); }

(5)发送按钮实现

private void btnSend_Click(object sender, RoutedEventArgs e) { if (ModeCombo.SelectedIndex == 1) { if(server!=null) { List<ClientInfo> ClientList = server.GetAllConnectedClients(); server.SendToClient(ClientList[0], tbSend.Text); } } else { if (client != null) { client.Send(tbSend.Text); } } }

3、效果演示

(1)做客户端时

利用TCP助手创建TCP服务器

连接

接收助手信息

发送消息到助手

(2)做服务端时

连接

多台客户端同时向服务器发送数据

向指定客户端发送数据

(3)两个自己写的助手通讯

四、利用TCP通讯实现海康相机软触发拍照及向PLC发送NG信号

1、通讯触发拍照

采用TCP服务器作为上位机触发接收端,可以同时接受多个客户端的触发信号。

(1) 相机初始化

camera = new interfaceMVS_SDK(); camera.RefreshDeviceList(); camera.SelectCamera(0); camera.OpenDevice();

(2)注册事件

server = new TcpServer(8000); server.OnDataReceived += server_OnDataReceived;

(3)事件处理函数

private void server_OnDataReceived(ClientInfo client, string data) { MessageBox.Show("接收到数据"); // 必须通过 Dispatcher 更新 UI Dispatcher.Invoke(() => { // 将接收到的数据追加到 TextBox if (data=="1234") { camera.StartGrabbing(); int flag = camera.GetOneFrame(); //MessageBox.Show(flag.ToString()); if (flag == MvError.MV_OK) { IFrameOut temp = camera.ReturnOneFrame(); var bitmap = temp?.Image?.ToBitmap(); Bitmap colorBitmap = GrayscaleTo24bppRgb(bitmap); var show = ConvertToBitmapImage(colorBitmap); ImageOri.Dispatcher.BeginInvoke(new Action(() => { ImageOri.Source = show; })); } camera.StopGrabbing(); } }); }

2、发送相机识别结果

private void server_OnDataReceived(ClientInfo client, string data) { MessageBox.Show("接收到数据"); // 必须通过 Dispatcher 更新 UI Dispatcher.Invoke(() => { // 将接收到的数据追加到 TextBox if (data=="1234") { camera.StartGrabbing(); int flag = camera.GetOneFrame(); //MessageBox.Show(flag.ToString()); if (flag == MvError.MV_OK) { IFrameOut temp = camera.ReturnOneFrame(); var bitmap = temp?.Image?.ToBitmap(); Bitmap colorBitmap = GrayscaleTo24bppRgb(bitmap); var show = ConvertToBitmapImage(colorBitmap); ImageOri.Dispatcher.BeginInvoke(new Action(() => { ImageOri.Source = show; })); } camera.StopGrabbing(); if (server != null) { List<ClientInfo> ClientList = server.GetAllConnectedClients(); server.SendToClient(ClientList[0], "拍照完成"); } } }); }

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/17 4:10:20

自动滚屏阅读工具:提升PDF和长文档阅读效率的实用指南

1. 为什么需要自动滚屏阅读工具如果你经常阅读长PDF文档、电子书或在线文章&#xff0c;手动翻页或拖动滚动条会打断阅读节奏。自动滚屏工具的核心价值就是解放双手&#xff0c;让文档内容以恒定速度自动向上滚动&#xff0c;你可以像看电影一样专注阅读&#xff0c;不需要频繁…

作者头像 李华
网站建设 2026/7/17 4:07:45

算法总结:字符串算法(一)

背景 复健次日&#xff0c;本来想接着做连通图&#xff0c;结果调一个题调了3h才发现自己把i–写成i了&#xff0c;AI也没发现&#xff0c;气得本人想当场退役&#xff0c;只好看看字符串来压压惊。 一、字符串哈希 模板&#xff1a;P3370 判断两个字符串是否相同&#xff0…

作者头像 李华
网站建设 2026/7/17 4:06:44

一块砖,四只手——合金日记灶台篇(下)

Bold 钩子&#xff1a; 收与放——不是设计&#xff0c;是被辨认出来的。025 老罗手心朝里收旗&#xff0c;030 老石手心朝外嵌名。两篇隔了五十三年&#xff0c;两个动作&#xff0c;同一种「顿」。三句三分钟&#xff1a;1.五个字到一千三百字——「修城墙的人」怎么从四个字…

作者头像 李华
网站建设 2026/7/17 4:05:05

HarmonyOS鸿蒙PC平台nodejs三方库移植实战(bcrypt和 sqlite3移植笔记)

本文记录下移植nodejs三方库bcrypt和 sqlite3到鸿蒙PC上的踩坑过程和经验分享。鸿蒙PC平台nodejs三方库移植也很简单。鸿蒙 PC 安装 bcrypt 和sqlite3三方库实录&#xff1a;预编译与源码编译双路实战&#xff0c;关于 HarmonyOS Node.js 原生模块的兼容性测试验证总结笔记。 …

作者头像 李华
网站建设 2026/7/17 4:04:22

【Python基础】Python开发环境搭建实战操作详解

目录 一、前言 二、Python 基本介绍 2.1 Python 核心特点 2.2 Python 应用领域 2.3 Python 优缺点 2.4 现代 Python 生态趋势 2.5 Python 使用建议 三、Python 环境搭建 3.1 获取安装包 3.2 安装过程 3.3 效果验证 四、Python 编程初体验 4.1 Hello World 输出 4.…

作者头像 李华
网站建设 2026/7/17 4:03:47

VScode:使用TeamoRouter的API服务

TeamoRouter的优势 TeamoRouter是一个包含多种大语言模型的中转平台&#xff0c;可以间接调用 Claude、GPT、Gemini 、Deepseek等各种 AI 模型&#xff0c;同时提供API服务。 1.价格便宜 如图所示&#xff0c;无需多言。 2.操作省心 无需翻墙&#xff0c;担心网络环境是否…

作者头像 李华