尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
上位机分层架构设计
C# 上位机开发 - 上位机分层架构设计HAL协议层业务层 一篇搞定 建议收藏作者萨达大 | 标签C#、上位机、分层架构、HAL、Modbus导读很多新手把SerialPort写在按钮事件里换一台 PLC 就要改几十个窗体。本文用四层架构把 UI、业务、协议、硬件拆开并给出可复制的接口与完整示例代码。一、为什么上位机必须分层现场设备千差万别串口 RS485、以太网 Modbus TCP、自定义 ASCII 帧……界面却要统一展示温度、压力、报警灯。若不分层改协议全项目搜0x03、改 N 个窗体。改硬件USB 转串口换品牌驱动层到处补丁。测试困难没有 Mock单元测试只能连真机。工业软件常见做法是UI 只谈业务语义不谈字节流。下面四层就是 WinForms/WPF 上位机最常用的一种切分。二、四层架构总览层次职责典型类/接口禁止做的事UI 层展示、交互、简单校验Form、BindingSource直接操作SerialPort、拼 Modbus 帧业务层设备语义、轮询策略、报警逻辑DeviceService解析 CRC、管理 Socket 生命周期细节协议层帧编解码、事务读/写寄存器IProtocolClient、ModbusProtocolClient关心 TextBox 刷新HAL 层字节收发、连接、超时IDeviceDriver、HalSerialPort理解「温度」含义2.1 依赖方向自上而下UI 层 WinForms/WPF业务层 DeviceService协议层 ModbusProtocolClientHAL HalSerialPort / TcpClient物理设备ASCII 等价示意[ Form 界面 ] -- [ DeviceService ] -- [ ModbusProtocolClient ] -- [ HalSerialPort ]铁律HAL 不知道 Modbus协议层不知道 Grid 有几列UI 不知道寄存器地址 0x1000 还是 40001。三、推荐项目结构UpperComputer.sln ├── UpperComputer.Core │ ├── Abstractions/IDeviceDriver.cs │ ├── Abstractions/IProtocolClient.cs │ └── Models/DeviceTag.cs ├── UpperComputer.HAL │ └── Serial/HalSerialPort.cs ├── UpperComputer.Protocol │ └── Modbus/ModbusProtocolClient.cs ├── UpperComputer.Business │ └── Services/DeviceService.cs └── UpperComputer.UI.WinForms └── MainForm.cs类库之间引用UI → Business → Protocol → HAL → Core禁止反向引用。四、核心接口设计4.1 IDeviceDriverHAL硬件抽象只表达「连接 收发字节 状态」与 Modbus 无关。namespaceUpperComputer.Core.Abstractions;publicinterfaceIDeviceDriver:IAsyncDisposable{boolIsConnected{get;}TaskConnectAsync(CancellationTokencancellationTokendefault);TaskDisconnectAsync(CancellationTokencancellationTokendefault);Taskbyte[]TransceiveAsync(byte[]request,inttimeoutMs,CancellationTokencancellationTokendefault);}设计要点TransceiveAsync请求-响应模型适合 Modbus RTU/TCP纯推送流可另加ReadAvailableAsync。超时在 HAL 实现协议层只关心「这次调用是否在时限内返回」。IAsyncDisposable窗体关闭、服务停止时统一释放端口。4.2 IProtocolClient协议层namespaceUpperComputer.Core.Abstractions;publicinterfaceIProtocolClient{Taskushort[]ReadHoldingRegistersAsync(byteslaveId,ushortstartAddress,ushortcount,CancellationTokenctdefault);TaskWriteSingleRegisterAsync(byteslaveId,ushortaddress,ushortvalue,CancellationTokenctdefault);}业务层调用的是「读保持寄存器」而不是「发 8 字节」。五、HAL 实现HalSerialPortusingSystem.IO.Ports;usingUpperComputer.Core.Abstractions;namespaceUpperComputer.HAL.Serial;publicsealedclassHalSerialPort:IDeviceDriver{privatereadonlySerialPort_port;publicboolIsConnected_port.IsOpen;publicHalSerialPort(stringportName,intbaudRate9600){_portnewSerialPort(portName,baudRate,Parity.Even,8,StopBits.One){ReadTimeout500,WriteTimeout500};}publicTaskConnectAsync(CancellationTokencancellationTokendefault){if(!_port.IsOpen)_port.Open();returnTask.CompletedTask;}publicTaskDisconnectAsync(CancellationTokencancellationTokendefault){if(_port.IsOpen)_port.Close();returnTask.CompletedTask;}publicasyncTaskbyte[]TransceiveAsync(byte[]request,inttimeoutMs,CancellationTokencancellationTokendefault){_port.Write(request,0,request.Length);varbuffernewbyte[256];vardeadlineDateTime.UtcNow.AddMilliseconds(timeoutMs);usingvarmsnewMemoryStream();while(DateTime.UtcNowdeadline){cancellationToken.ThrowIfCancellationRequested();varnawaitTask.Run(()_port.Read(buffer,0,buffer.Length),cancellationToken);if(n0)ms.Write(buffer,0,n);if(n0ms.Length5)break;// 简化Modbus RTU 最小响应awaitTask.Delay(10,cancellationToken);}returnms.ToArray();}publicasyncValueTaskDisposeAsync(){awaitDisconnectAsync();_port.Dispose();}}现场提示RTU 需处理「帧间隔」「3.5 字符时间」量产代码建议独立ModbusRtuTransceiverHAL 只保留Write/Read。六、协议层ModbusProtocolClient节选usingUpperComputer.Core.Abstractions;namespaceUpperComputer.Protocol.Modbus;publicsealedclassModbusProtocolClient:IProtocolClient{privatereadonlyIDeviceDriver_driver;publicModbusProtocolClient(IDeviceDriverdriver)_driverdriver;publicasyncTaskushort[]ReadHoldingRegistersAsync(byteslaveId,ushortstartAddress,ushortcount,CancellationTokenctdefault){varpduBuildReadHoldingRegistersPdu(slaveId,startAddress,count);varframeModbusRtuFrame.Wrap(pdu);// CRC16varresponseawait_driver.TransceiveAsync(frame,1000,ct);returnModbusRtuFrame.ParseRegisters(response,count);}publicTaskWriteSingleRegisterAsync(byteslaveId,ushortaddress,ushortvalue,CancellationTokenctdefault){varpduBuildWriteSingleRegisterPdu(slaveId,address,value);varframeModbusRtuFrame.Wrap(pdu);return_driver.TransceiveAsync(frame,1000,ct).ContinueWith(_Array.Emptybyte(),ct);}// BuildPdu / CRC 实现见 Modbus 通信专题此处省略细节privatestaticbyte[]BuildReadHoldingRegistersPdu(byteslaveId,ushortstart,ushortcount)Array.Emptybyte();}协议层集中处理功能码、异常码、字节序Big-Endian业务层拿到的已是ushort[]。七、业务层DeviceServiceusingUpperComputer.Core.Abstractions;namespaceUpperComputer.Business.Services;publicsealedclassDeviceService{privatereadonlyIProtocolClient_protocol;publiceventActiondouble?TemperatureChanged;publicDeviceService(IProtocolClientprotocol)_protocolprotocol;publicasyncTaskPollTemperatureAsync(CancellationTokenctdefault){varregsawait_protocol.ReadHoldingRegistersAsync(1,0,1,ct);varrawregs[0];vartempraw/10.0;// 工程量换算放在业务层TemperatureChanged?.Invoke(temp);}}业务层负责从寄存器到物理量、报警阈值、轮询间隔策略。同一协议客户端可被多个 Service 组合温湿度、电机状态。八、WinForms 集成示例publicpartialclassMainForm:Form{privatereadonlyDeviceService_service;privatereadonlyCancellationTokenSource_ctsnew();publicMainForm(DeviceServiceservice){_serviceservice;_service.TemperatureChangedtBeginInvoke(()lblTemp.Text${t:F1}°C);}privateasyncvoidMainForm_Load(objectsender,EventArgse){await_service.ConnectIfNeededAsync();// 可在 Service 内封装 Driver.Connect_Task.Run(async(){while(!_cts.IsCancellationRequested){await_service.PollTemperatureAsync(_cts.Token);awaitTask.Delay(500,_cts.Token);}},_cts.Token);}protectedoverridevoidOnFormClosed(FormClosedEventArgse){_cts.Cancel();base.OnFormClosed(e);}}窗体只做订阅事件、BeginInvoke 刷新控件。若换 WPF业务层DeviceService可原样复用。九、分层与测试测试对象方式协议层MockIDeviceDriver注入固定响应字节业务层MockIProtocolClient验证换算与报警UI人工 / UI 自动化不连真机// 示例假驱动返回固定 Modbus 响应publicsealedclassFakeDriver:IDeviceDriver{publicboolIsConnectedtrue;publicTaskConnectAsync(CancellationTokencancellationTokendefault)Task.CompletedTask;publicTaskDisconnectAsync(CancellationTokencancellationTokendefault)Task.CompletedTask;publicTaskbyte[]TransceiveAsync(byte[]request,inttimeoutMs,CancellationTokencancellationTokendefault)Task.FromResult(Convert.FromHexString(010302006400B9));// 寄存器值 100publicValueTaskDisposeAsync()ValueTask.CompletedTask;}十、常见问题 FAQQ1四层会不会太「重」小项目也要吗小 demo 可以两层UI协议但一旦要上现场、要维护两年以上四层边际成本很低收益是换设备不改 UI。Q2TCP 和串口怎么统一HAL 各写一个HalSerialPort、HalTcpStream都实现IDeviceDriver。协议层 Modbus RTU/TCP 可共用一个IProtocolClient或拆两个实现。Q3异步会不会把 WinForms 搞复杂UI 线程只BeginInvoke轮询放Task.RunCancellationToken。比Timer里同步Read卡界面更安全。Q4寄存器地址 40001 和 0 怎么统一在业务层或配置里用「协议地址 0」文档与 HMI 显示「40001」仅展示层转换。Q5和 MVVM 什么关系WPF 的 ViewModel 相当于本文的 UI 逻辑 部分业务DeviceService 仍应独立于 ViewModel便于单元测试。MVVM 专题见博客单独模块。Q6多设备多线程怎么分层每个设备一条「Driver Protocol Service」实例UI 用字典或列表绑定。线程安全在 Service 或 DataHub 用锁/Channel见本模块 03 篇。十一、小结UI展示与交互订阅业务事件。业务工程量、轮询、报警。协议Modbus/自定义帧。HAL串口/TCP 字节流。把接口钉死后换 PLC 改配置 换 Driver窗体一行不动——这才是上位机架构的终极目标。上一篇02-communication / 08 TCP Socket 通信下一篇02 工厂模式与设备驱动
RELATED

相关推荐

为什么你越努力优化,供应链反而越脆弱?

为什么你越努力优化,供应链反而越脆弱?

开篇:一次让我崩溃的多部门会议 去年做年度预算规划,我同时面对三个部门的“合理诉求”: 销售总监说:“客户要的交付率必须做到98%以上,哪怕多备库存。丢一个客户,再找回来比登天还难。” 财务总监说&#…

📅 2026/10/6 20:45:38
Docker 部署 milvus

Docker 部署 milvus

1 创建工作目录 D:\home\milvus2 创建文件 docker-compose.yml内容:services:etcd:container_name: milvus-etcdimage: quay.io/coreos/etcd:v3.5.5environment:- ETCD_AUTO_COMPACTION_MODErevision- ETCD_AUTO_COMPACTION_RETENTION1000- ETCD_QUOTA_BACKEND_…

📅 2026/10/6 20:46:22
Kali Linux下Weevely WebShell免杀实战:从生成部署到绕过安全检测

Kali Linux下Weevely WebShell免杀实战:从生成部署到绕过安全检测

1. 项目概述:WebShell攻防实战的“瑞士军刀”在渗透测试和红队评估的实战中,获取一个稳定、隐蔽且功能强大的WebShell是后续横向移动和数据获取的关键一步。然而,随着安全防护能力的提升,尤其是各类WAF(Web应用防火墙&…

📅 2026/9/4 17:40:13
MORE NEWS

更多资讯

📰

老游戏超分怎么升到最新:用 DLSS Swapper 免费替换 DLSS/FSR/XeSS DLL 完整指南(一键回滚)

老游戏超分怎么升到最新:用 DLSS Swapper 免费替换 DLSS/FSR/XeSS DLL 完整指南(一键回滚) 【免费下载链接】dlss-swapper 项目地址: https://gitcode.com/GitHub_Trending/dl/dlss-swapper DLSS Swapper 是一款免费开源的 Windows 工…

📰

LinkSwift:九大盘盘一键取直链的免费下载助手,3 分钟装好脚本

LinkSwift:九大盘盘一键取直链的免费下载助手,3 分钟装好脚本 【免费下载链接】Online-disk-direct-link-download-assistant 一个基于 JavaScript 的网盘文件下载地址获取工具。基于【网盘直链下载助手】修改 ,支持 百度网盘 / 阿里云盘 / 中…

📰

@tanstack/vue-virtual 版本演进解析:从 3.13.3 到 3.13.39 的核心修复与底层原理

前端UI组件 【免费下载链接】virtual 🤖 Headless UI for Virtualizing Large Element Lists in JS/TS, React, Solid, Vue and Svelte 项目地址: https://gitcode.com/gh_mirrors/vi/virtual 点击查看 免费下载 tanstack/vue-virtual 是 TanStack Virt…

📰

用 Gobot 控制 Holystone HS200 无人机:从 Wi-Fi 连接、UDP 控制协议到起飞降落实战

物联网机器人嵌入式 【免费下载链接】gobot Golang framework for robotics, drones, and the Internet of Things (IoT) 项目地址: https://gitcode.com/gh_mirrors/go/gobot 点击查看 免费下载 本篇技术指南聚焦于 Gobot(Go 语言机器人/IoT 框架&…

📰

DataTables 1.10 jQuery 表格插件安装与快速上手实战指南

前端UI组件 【免费下载链接】DataTables DataTables - legacy repo 项目地址: https://gitcode.com/gh_mirrors/da/DataTables 点击查看 免费下载 导读 本文以本仓库的 Readme.md 为骨架,系统讲解 DataTables——一个为 jQuery 设计的 HTML 表格增强插…

📰

[LangGraph编译原理-02]面向通道定义Agent的状态

LangGraph编程基本围绕StateGraph进行,所以我们有必要对这个类型具有一个深刻的认识。这是一个泛型类型,四个泛型参数StateT、ContextT、InputT和OutputT分别表示状态、静态上下文、输入和输出类型,而且它们的类型都是一个StateLike类型。Sta…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬