当前位置: 首页 > news >正文

Socket-TCP 上位机下位机数据交互框架

Socket-TCP 上位机 ↔ 下位机数据交互框架(C# WinForm 版本)
框架特点:

  • 上位机既做“客户端”又可秒切“TCP服务器”,适应不同场景
  • 支持 01 线圈 / 03 保持寄存器 / 04 输入寄存器 的任意地址位配置
  • 实时日志、自动重连、断线检测、CRC16 校验
  • 线程安全:收发线程与 UI 线程解耦,不卡界面
  • 协议扩展:预留“私有协议”接口,可快速改为 Modbus-TCP、自定义帧格式

1. 工程准备

依赖NuGet 指令
日志Install-Package NLog
CRC16Install-Package Crc16

2. 核心代码目录

/FrmMain.cs        // UI
/TcpLink.cs        // 连接管理
/ProtocolCodec.cs  // 协议编解码
/DataStore.cs      // 地址-数据映射(01 03 04)

3. 代码

3.1 连接管理(TcpLink.cs)
public class TcpLink : IDisposable
{private TcpClient _tcp;private NetworkStream _ns;private readonly CancellationTokenSource _cts = new();public event Action<byte[]> OnReceived;public bool IsConnected => _tcp?.Connected == true;// 作为客户端连接public async Task ConnectAsync(string ip, int port){_tcp = new TcpClient();await _tcp.ConnectAsync(ip, port);_ns = _tcp.GetStream();_ = Task.Run(() => ReceiveLoop(_cts.Token));}// 作为服务器监听public async Task StartServerAsync(int port){var listener = new TcpListener(IPAddress.Any, port);listener.Start();var client = await listener.AcceptTcpClientAsync();_tcp = client; _ns = client.GetStream();_ = Task.Run(() => ReceiveLoop(_cts.Token));}public async Task SendAsync(byte[] data){if (!IsConnected) return;await _ns.WriteAsync(data, 0, data.Length);}private async Task ReceiveLoop(CancellationToken ct){var buf = new byte[1024];while (!ct.IsCancellationRequested){var len = await _ns.ReadAsync(buf, 0, buf.Length, ct);if (len == 0) break; // 断线OnReceived?.Invoke(buf.AsSpan(0, len).ToArray());}}public void Dispose(){_cts.Cancel();_ns?.Dispose();_tcp?.Close();}
}
3.2 地址-数据映射(DataStore.cs)
public class DataStore
{private readonly bool[] _coils = new bool[1000];          // 01private readonly ushort[] _holdingRegs = new ushort[1000]; // 03private readonly ushort[] _inputRegs = new ushort[1000];   // 04public bool[] Coils => _coils;public ushort[] HoldingRegs => _holdingRegs;public ushort[] InputRegs => _inputRegs;// 示例:设置单个线圈public void SetCoil(ushort addr, bool value) => _coils[addr] = value;public bool GetCoil(ushort addr) => _coils[addr];// 示例:设置保持寄存器public void SetHolding(ushort addr, ushort value) => _holdingRegs[addr] = value;public ushort GetHolding(ushort addr) => _holdingRegs[addr];
}
3.3 Modbus-TCP 协议编解码(ProtocolCodec.cs)
public static class ProtocolCodec
{public static byte[] BuildReadHolding(ushort addr, ushort qty, byte slaveId){var ms = new MemoryStream();var bw = new BinaryWriter(ms);bw.Write((ushort)0x0000);      // 事务标识bw.Write((ushort)0x0000);      // 协议标识bw.Write((ushort)0x0006);      // 长度bw.Write(slaveId);bw.Write((byte)0x03);          // 功能码 03bw.Write(addr);bw.Write(qty);return ms.ToArray();}public static bool TryDecode(byte[] raw, out byte fn, out ushort addr, out ushort[] values){if (raw.Length < 8) { fn = 0; addr = 0; values = null; return false; }fn = raw[7];addr = (ushort)(raw[8] << 8 | raw[9]);var qty = (ushort)(raw[10] << 8 | raw[11]);values = new ushort[qty];var bytes = raw.AsSpan(9 + 2);for (int i = 0; i < qty; i++)values[i] = (ushort)(bytes[i * 2] << 8 | bytes[i * 2 + 1]);return true;}
}
3.4 WinForm 调用(FrmMain.cs)
public partial class FrmMain : Form
{private readonly TcpLink _link = new();private readonly DataStore _store = new();public FrmMain(){InitializeComponent();_link.OnReceived += bytes =>{if (ProtocolCodec.TryDecode(bytes, out var fn, out var addr, out var vals)){// 更新 UIthis.Invoke(() => UpdateUI(fn, addr, vals));}};}private async void btnConnect_Click(object sender, EventArgs e){await _link.ConnectAsync(txtIP.Text, int.Parse(txtPort.Text));lblStatus.Text = "已连接";}private async void btnRead_Click(object sender, EventArgs e){ushort addr = ushort.Parse(txtAddr.Text);ushort qty = 1;var frame = ProtocolCodec.BuildReadHolding(addr, qty, 1);await _link.SendAsync(frame);}private void UpdateUI(byte fn, ushort addr, ushort[] vals){if (fn == 0x03) txtValue.Text = vals[0].ToString();}
}

4. 运行步骤

  1. 下位机(如STM32)作为 TCP服务器 监听 502 端口,实现 Modbus-TCP 协议。
  2. 上位机输入服务器 IP → 连接 → 点击“读保持寄存器”即可实时获取数据。
  3. 如需私有协议,只需替换 ProtocolCodec 即可;所有通信逻辑复用。

参考代码 soket、tcp上位机与下位机数据交互框架 www.youwenfan.com/contentcne/112324.html

5. 可扩展功能

  • 心跳包:定时发送,断线自动重连
  • CRC16 校验:在 ProtocolCodec 中加入 Crc16.ComputeChecksum()
  • 日志:使用 NLog 记录收发帧
  • 多线程:UI 线程与通信线程完全隔离,避免卡顿
http://www.dtcms.com/a/360844.html

相关文章:

  • 深入理解 HTTP 与 HTTPS:区别以及 HTTPS 加密原理
  • UART-TCP双向桥接服务
  • Flutter WebAssembly (Wasm) 支持 - 实用指南Flutter WebAssembly (Wasm) 支持 - 实用指南
  • 解决爬虫IP限制:Selenium隧道代理完整解决方案
  • 聚焦智慧教育新趋势:AI+虚拟仿真技术加速未来学习转型
  • 算法面试题(上)
  • 【Java后端】Spring Boot 全局域名替换
  • Azure AI Search构建RAG的优化点
  • 接口自动化测试之设置断言思路
  • 大模型应用开发面试实录:LLM原理、RAG工程与多Agent场景化落地解析
  • mysql实例是什么?
  • 产品月报|睿本云8月产品功能迭代
  • Topaz Video AI:AI驱动的视频增强与修复工具
  • 嵌入式实时操作系统(二十五)-实时性
  • 从 “能用” 到 “好用”:生成式 AI 落地三大核心痛点与破局路径
  • nt5inf.hash排序后前后两个共五个和nti5nf.cat文件用asn.1editor打开后导出后部分内容的对比--重要
  • Unity中多线程与高并发下的单例模式
  • 结构体成员大小及内存对齐练习
  • Electron使用WebAssembly实现CRC-16 CCITT校验
  • 9.1C++——类中特殊的成员函数
  • 安卓悬浮球-3566-测试报告
  • vue社区网格化管理系统(代码+数据库+LW)
  • Adobe Acrobat打开pdf文件时闪退如何解决?
  • OpenCV-CUDA 图像处理
  • 论文阅读_TradingAgents多智能体金融交易框架
  • .net 微服务jeager链路跟踪
  • C++11 ——— lambda表达式
  • LeetCode 19: 删除链表的倒数第 N 个结点
  • GIT(了解)
  • 计算机网络---https(超文本传输安全协议)