资讯动态

C#与MCGS TCP通信实战:工控上位机数据采集与避坑指南

发布时间:2026/10/20 10:10:30 来源:尧图企业网站定制
简介这份资源面向使用C#进行上位机开发的工程师与工控学习者提供在Visual Studio 2013环境下与MCGS昆仑通态触摸屏进行TCP通信的完整范例代码重点解决如何从MCGS侧读取数据并完成交互这一常见需求。压缩包共36个文件约92KB以9个cs源码文件为核心配合sln解决方案、csproj工程文件、resx与resources资源文件、config配置及exe可执行程序另有txt说明、pdb调试符号与suo用户选项等辅助文件结构完整可直接在VS中打开编译运行。内容预览显示包含指示看板例程与看板人机工程文件便于对照理解通信数据与界面显示的对应关系。目前已有2506人学习下载适合希望快速掌握C#与昆仑通态MCGS通信流程、排查连接与数据读取问题的开发者参考借鉴。1. 工控上位机对接C# 与 MCGS 走 TCP 通信到底能解决什么车间里一台老设备还在用 MCGS昆仑通态触摸屏跑本地逻辑但产线要求把实时数据汇总到 MES 或者自研看板这时候最省事的做法不是换屏而是让 MCGS 把数据通过 TCP 吐出来上位机用 C# 接住。这套范例代码解决的就是这个场景MCGS 侧做 TCP 服务端或客户端C# 侧做对端双方约定一套简单的报文格式把温度、压力、产量这些寄存器值搬来搬去。适合谁做非标自动化上位机、产线数据采集、设备联网改造的 C# 开发者尤其是手头没有 OPC UA 授权、又不想上整套 SCADA 的情况。TCP 裸协议虽然土但胜在可控、无依赖、调试直观搞懂一次能复用到很多项目里。2. 先搞懂 MCGS 侧的 TCP 配置服务端还是客户端选错就白干2.1 MCGS 里 TCP 通信的两种角色与选型逻辑MCGS 的脚本系统里做 TCP 通信核心是调用!SetDevice或者用设备窗口里的“网络设备”构件。但更常见的做法是在 MCGS 的“运行策略”里写脚本调用系统函数!TcpConnect、!TcpSend、!TcpRecv这一套。这里第一个要定的事谁当服务端。如果 MCGS 是服务端C# 主动连它好处是 MCGS 侧不用关心上位机 IP 变不变适合上位机在局域网内 IP 不固定的场景。如果 MCGS 是客户端C# 开监听好处是 C# 侧可以同时接多台 MCGS 屏适合多工位汇总。我一般会选 MCGS 当客户端、C# 当服务端因为 C# 侧用TcpListener管理多连接比 MCGS 脚本里管理多连接舒服得多。选型定了之后MCGS 侧要配的参数其实就几个目标 IP、目标端口、本地端口如果当服务端、超时时间。这些在 MCGS 的“设备窗口”里添加“通用 TCP/IP 父设备”再挂子设备就能配也可以在运行策略里用脚本动态设。脚本方式更灵活但坑也更多后面避坑章节会细说。2.2 MCGS 脚本里收发数据的写法与寄存器映射MCGS 的 TCP 收发不是直接给你一个 socket 句柄而是通过“设备”抽象。你需要在设备窗口里建一个 TCP 设备然后把这个设备的“通道”和内部寄存器比如RW0、RW1关联起来。发的时候把寄存器值写进去调!TcpSend收的时候调!TcpRecv数据会落到关联的寄存器里。一个典型的 MCGS 运行策略脚本长这样 MCGS 运行策略脚本作为客户端连接 C# 服务端并发送数据 假设已建好 TCP 设备设备名 TCP设备0 目标 IP 和端口在设备属性里配好 !TcpConnect(TCP设备0, 1) 参数1设备名参数21 表示连接0 表示断开 把内部数据写入发送缓冲区对应的寄存器 RW0 100 温度值假设放大 10 倍 RW1 250 压力值 RW2 888 产量计数 !TcpSend(TCP设备0) 触发发送把关联寄存器的值按格式发出去 接收部分 !TcpRecv(TCP设备0) 触发接收数据落到接收寄存器区 假设接收寄存器从 RW10 开始 那么 RW10 就是 C# 发过来的第一个字这段脚本的逻辑说明!TcpConnect的第二个参数是布尔量1 连 0 断一般放在“启动策略”里连一次不用每次循环都连。!TcpSend和!TcpRecv是触发式的调用一次执行一次所以通常放在循环策略里配合!TimeStr或者循环时间来控制频率。寄存器映射是关键MCGS 的 TCP 设备属性里要配“发送寄存器起始地址”和“接收寄存器起始地址”发的时候从起始地址连续取 N 个字收的时候从起始地址连续写 N 个字。这个 N 就是你的报文长度单位是“字”16 位不是字节。参数怎么改如果你要发浮点数MCGS 侧得自己拆成两个 16 位寄存器或者用!FloatToStr转成字符串再发。我一般建议工控场景全用整数放大 10 倍或 100 倍传C# 侧再除回来省得纠结字节序。接收同理C# 发过来的整数直接落到 RW 寄存器MCGS 脚本里当普通数值用。提示MCGS 的 TCP 设备属性里有一个“通信超时”参数默认可能偏大建议改成 1000ms 以内否则断线后脚本会卡住很久才返回。3. C# 侧 TcpListener/TcpClient 实现把字节流拼成能用的数据3.1 服务端用 TcpListener 接多台 MCGS 屏C# 侧当服务端核心是TcpListener加异步接受。下面是一个能跑的最小骨架我把它拆成“监听—接受—收数据—回数据”四步using System; using System.Net; using System.Net.Sockets; using System.Text; using System.Threading.Tasks; class McgsTcpServer { private TcpListener _listener; private const int Port 6000; public async Task StartAsync() { _listener new TcpListener(IPAddress.Any, Port); _listener.Start(); Console.WriteLine($监听 {Port} 端口...); while (true) { // 接受一个 MCGS 屏的连接 TcpClient client await _listener.AcceptTcpClientAsync(); Console.WriteLine($MCGS 已连接{client.Client.RemoteEndPoint}); // 每个连接丢到独立任务里处理避免阻塞接受循环 _ HandleClientAsync(client); } } private async Task HandleClientAsync(TcpClient client) { NetworkStream stream client.GetStream(); byte[] buffer new byte[1024]; try { while (true) { int len await stream.ReadAsync(buffer, 0, buffer.Length); if (len 0) break; // 对端关闭 // 假设 MCGS 发来的是 3 个 16 位整数共 6 字节 if (len 6) { short temp BitConverter.ToInt16(buffer, 0); short pressure BitConverter.ToInt16(buffer, 2); short count BitConverter.ToInt16(buffer, 4); Console.WriteLine($温度{temp / 10.0} 压力{pressure / 10.0} 产量{count}); } // 回一个确认包比如把产量加一返回 byte[] resp BitConverter.GetBytes((short)1); await stream.WriteAsync(resp, 0, resp.Length); } } catch (Exception ex) { Console.WriteLine($连接异常{ex.Message}); } finally { client.Close(); } } }逻辑说明AcceptTcpClientAsync每来一个连接就返回一个TcpClient用_ HandleClientAsync(client)把它扔到后台任务这样多台 MCGS 屏可以同时连。stream.ReadAsync返回实际读到的字节数注意 TCP 是流式的一次 Read 不一定读到完整报文所以工业场景要么定长报文要么加长度头。上面例子假设 MCGS 每次固定发 6 字节简单但脆弱后面避坑章节会说怎么改。参数说明Port选 6000 只是举例实际要避开系统占用端口建议 5000 以上。IPAddress.Any表示监听所有网卡如果机器有多网卡且只想让内网连改成具体 IP。buffer大小 1024 够用但如果报文长要按最大报文长度设。3.2 客户端用 TcpClient 主动连 MCGS 服务端如果 MCGS 当服务端C# 侧就是TcpClient主动连。写法比服务端简单但断线重连要自己处理using System; using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; class McgsTcpClient { private TcpClient _client; private NetworkStream _stream; private const string McgsIp 192.168.1.100; private const int McgsPort 6000; public async Task ConnectLoopAsync() { while (true) { try { if (_client null || !_client.Connected) { _client new TcpClient(); await _client.ConnectAsync(McgsIp, McgsPort); _stream _client.GetStream(); Console.WriteLine(已连接 MCGS); } // 发一个查询指令比如读温度 byte[] cmd { 0x01, 0x03, 0x00, 0x00, 0x00, 0x01 }; await _stream.WriteAsync(cmd, 0, cmd.Length); byte[] buf new byte[256]; int len await _stream.ReadAsync(buf, 0, buf.Length); if (len 0) { Console.WriteLine($收到 {len} 字节); } } catch (Exception ex) { Console.WriteLine($通信失败{ex.Message}3 秒后重连); _client?.Close(); _client null; await Task.Delay(3000); } } } }逻辑说明外层while(true)保证断线后自动重连ConnectAsync连不上会抛异常被 catch 住后延时 3 秒再试。_client.Connected这个属性其实不可靠它只表示上次操作的状态所以更稳妥的做法是直接 try 发数据失败就重连。发送的cmd是随便举的 Modbus 风格报文实际要按你和 MCGS 约定的格式来。参数说明McgsIp和McgsPort要和 MCGS 侧配的目标地址一致。Task.Delay(3000)的重连间隔别设太短否则 MCGS 还没恢复你就疯狂重连日志会炸。如果现场网络抖动大可以加指数退避第一次 1 秒第二次 2 秒最多 10 秒。注意C# 的TcpClient默认有 Nagle 算法小包会攒着发工控场景建议设_client.NoDelay true否则实时性会莫名其妙变差。4. 避坑与排查MCGS 和 C# 联调时最容易翻车的五个点4.1 现象C# 能连上但收不到数据MCGS 侧脚本没报错原因MCGS 的!TcpSend调用时机不对。很多人把!TcpConnect和!TcpSend写在同一个循环策略里每次循环都重连一次连接刚建好就被下一次重连冲掉数据根本没发出去。另一个可能是发送寄存器没赋值MCGS 发出去的全是 0C# 侧以为没收到。解决!TcpConnect只在启动策略里调一次循环策略里只调!TcpSend和!TcpRecv。发送前确认关联的 RW 寄存器确实有值可以在 MCGS 运行界面上放个标签显示这些寄存器肉眼确认。4.2 现象C# 收到的数值是乱的比如温度 100 变成 25600原因字节序问题。MCGS 底层可能是大端而 C# 的BitConverter在 x86 上是小端。你按小端解析大端数据高低字节就反了。解决统一约定。要么 MCGS 侧发之前手动调换高低字节要么 C# 侧用BitConverter.ToInt16之后做(short)((val 8) | (val 8))交换。我一般让 MCGS 侧将就 C#因为 MCGS 脚本里做字节交换更直观把值拆成高 8 位和低 8 位分别存两个寄存器C# 侧按顺序拼。4.3 现象通信跑几小时后 C# 侧内存涨、连接数涨原因TcpClient和NetworkStream没释放。每次断线重连都 new 一个TcpClient旧的没 Close句柄泄漏。MCGS 侧如果频繁断连重连C# 侧就会积累一堆僵尸连接。解决在HandleClientAsync的 finally 里确保client.Close()并且NetworkStream也 Dispose。重连逻辑里new 之前先把旧的 Close 掉。可以用using包住TcpClient但异步场景下手动 Close 更可控。4.4 现象MCGS 脚本执行到!TcpRecv就卡住界面不刷新原因!TcpRecv是阻塞式的如果 C# 侧一直不发数据MCGS 脚本就停在那等循环策略被卡死界面自然不刷新。MCGS 的 TCP 函数没有超时参数可配至少常见版本没有这是最坑的地方。解决C# 侧要保证周期性发数据哪怕发个心跳包。如果 C# 侧可能长时间不发MCGS 侧就别用阻塞的!TcpRecv改用非阻塞方式——但 MCGS 脚本层面没有现成的非阻塞 recv所以实际做法是让 C# 定时发或者把!TcpRecv放在独立策略里别和界面刷新共用一个循环。4.5 现象换一台电脑或改 IP 后就连不上之前好好的原因MCGS 的 TCP 设备属性里目标 IP 是写死的C# 侧换了机器 IP 变了MCGS 还在往旧 IP 发。或者 Windows 防火墙拦了 C# 的监听端口MCGS 连不上。解决MCGS 侧如果目标 IP 可能变用脚本动态设 IP别在设备属性里写死。C# 侧第一次部署时先关防火墙测通再按需加规则。另外注意 MCGS 屏和 C# 机器要在同一网段跨网段走路由的话MCGS 的网关也要配对。5. 进阶把裸 TCP 包成可维护的报文协议与自测方法裸 TCP 最大的问题是“没有边界”收数据全靠猜。我后来固定用一套简单协议2 字节长度头 2 字节命令字 N 字节数据 1 字节校验和。长度头用大端C# 侧先读 2 字节拿到长度再读剩余部分这样不管 TCP 怎么拆包都能拼回来。MCGS 侧发的时候脚本里手动把长度和校验算好填进寄存器虽然麻烦但一次写好模板后面复制就行。自测方法我一般分三步。第一步用网络调试助手模拟 MCGS手动发几个包给 C#确认 C# 解析逻辑对。第二步用 C# 写个模拟 MCGS 的小工具按协议发包确认 MCGS 侧脚本能正确收。第三步真机联调在 MCGS 运行界面上放一排调试标签显示发送计数、接收计数、最后错误码出问题时一眼能看出是发不出去还是收不进来。// 带长度头的报文解析示例 async Task ReadExactAsync(NetworkStream stream, byte[] buffer, int offset, int count) { int read 0; while (read count) { int n await stream.ReadAsync(buffer, offset read, count - read); if (n 0) throw new Exception(连接关闭); read n; } } // 使用先读 2 字节长度再读剩余 byte[] header new byte[2]; await ReadExactAsync(stream, header, 0, 2); int bodyLen (header[0] 8) | header[1]; // 大端长度 byte[] body new byte[bodyLen]; await ReadExactAsync(stream, body, 0, bodyLen);这段ReadExactAsync是解决 TCP 粘包/拆包的标准做法循环读到指定字节数为止。参数offset和count让你可以复用同一个 buffer。校验和我一般用累加和取低 8 位MCGS 脚本里一个循环就能算C# 侧同样算一遍对比不匹配就丢弃并记日志。从那以后我每次做 MCGS 和 C# 的 TCP 对接都强制先跑一遍模拟工具确认协议层没问题再上真机省得在现场对着屏幕猜是脚本问题还是网络问题。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑