基于modbusTcp连接Modbus Slave本地模拟服务通讯(C#编写ModbusTcp类库)(一)
C#编写ModbusTcp类库,模拟plc进行本地通信测试
Modbus是一个应用层协议,常用于工业自动化设备之间的通信,主要有两种传输方式:RTU和TCP。
常见的功能码包括读取线圈(01)、读取离散输入(02)、读保持寄存器(03)、读输入寄存器(04)、写单个线圈(05)、写单个寄存器(06)、写多个线圈(15)、写多个寄存器(16)等。类库需要支持这些基本操作。
一、协议基础:
-
Modbus TCP 使用 TCP/IP 协议,默认端口 502。
-
数据帧格式:事务标识符(2字节) + 协议标识符(2字节) + 长度(2字节) + 单元标识符(1字节) + Modbus PDU(功能码 + 数据)。

二、常用功能码:
- 03 功能码:读取保持寄存器(Read Holding Registers)。
- 06 功能码:写单个寄存器(Write Single Register)。
三、工具准备:
- Modbus Slave 模拟器:如 Modbus Slave ,用于模拟从站设备。下载地址: 模拟器下载地址
- 配置从站的 IP(如 127.0.0.1)、端口(502)、寄存器地址和初始值。

四、C# 实现步骤:
- 使用 TcpClient 建立 TCP 连接。
- 构造 Modbus 请求报文并发送。
- 接收响应报文并解析数据。
- 处理异常和超时。

五、代码实现:
- 建立数据连接:
/// <summary>
/// 连接
/// </summary>
/// <returns></returns>
protected override Result Connect()
{var result = new Result();socket?.SafeClose();socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);try{//超时时间设置socket.ReceiveTimeout = timeout;socket.SendTimeout = timeout;//连接IAsyncResult connectResult = socket.BeginConnect(ipEndPoint, null, null);//阻塞当前线程 if (!connectResult.AsyncWaitHandle.WaitOne(timeout))throw new TimeoutException("连接超时");socket.EndConnect(connectResult);}catch (Exception ex){socket?.SafeClose();result.IsSucceed = false;result.Err = ex.Message;result.ErrCode = 408;result.Exception = ex;}return result.EndTime();
}
2.断开连接
public static void SafeClose(this Socket socket)
{try{if (socket?.Connected ?? false) socket?.Shutdown(SocketShutdown.Both);//正常关闭连接}catch { }try{socket?.Close();}catch { }
}
3.读取数据
/// <summary>
/// 读取数据
/// </summary>
/// <param name="address">寄存器起始地址</param>
/// <param name="stationNumber">站号</param>
/// <param name="functionCode">功能码</param>
/// <param name="readLength">读取长度</param>
/// <param name="byteFormatting">大小端转换</param>
/// <returns></returns>
public Result<byte[]> Read(string address, byte stationNumber = 1, byte functionCode = 3, ushort readLength = 1, bool byteFormatting = true)
{var result = new Result<byte[]>();if (!socket?.Connected ?? true){var conentResult = Connect();if (!conentResult.IsSucceed){conentResult.Err = $"读取 地址:{address} 站号:{stationNumber} 功能码:{functionCode} 失败。{ conentResult.Err}";return result.SetErrInfo(conentResult);}}try{var chenkHead = GetCheckHead(functionCode);//1 获取命令(组装报文)byte[] command = GetReadCommand(address, stationNumber, functionCode, readLength, chenkHead);result.Requst = string.Join(" ", command.Select(t => t.ToString("X2")));//获取响应报文var sendResult = SendPackageReliable(command);if (!sendResult.IsSucceed){sendResult.Err = $"读取 地址:{address} 站号:{stationNumber} 功能码:{functionCode} 失败。{ sendResult.Err}";return result.SetErrInfo(sendResult).EndTime();}var dataPackage = sendResult.Value;byte[] resultBuffer = new byte[dataPackage.Length - 9];Array.Copy(dataPackage, 9, resultBuffer, 0, resultBuffer.Length);result.Response = string.Join(" ", dataPackage.Select(t => t.ToString("X2")));//4 获取响应报文数据(字节数组形式) if (byteFormatting)result.Value = resultBuffer.Reverse().ToArray().ByteFormatting(format);elseresult.Value = resultBuffer.Reverse().ToArray();if (chenkHead[0] != dataPackage[0] || chenkHead[1] != dataPackage[1]){result.IsSucceed = false;result.Err = $"读取 地址:{address} 站号:{stationNumber} 功能码:{functionCode} 失败。响应结果校验失败";socket?.SafeClose();}else if (ModbusHelper.VerifyFunctionCode(functionCode, dataPackage[7])){result.IsSucceed = false;result.Err = ModbusHelper.ErrMsg(dataPackage[8]);}}catch (SocketException ex){result.IsSucceed = false;if (ex.SocketErrorCode == SocketError.TimedOut){result.Err = $"读取 地址:{address} 站号:{stationNumber} 功能码:{functionCode} 失败。连接超时";socket?.SafeClose();}else{result.Err = $"读取 地址:{address} 站号:{stationNumber} 功能码:{functionCode} 失败。{ ex.Message}";}}finally{if (isAutoOpen) Dispose();}return result.EndTime();
}/// <summary>
/// 获取随机校验头
/// </summary>
/// <returns></returns>
private byte[] GetCheckHead(int seed)
{var random = new Random(DateTime.Now.Millisecond + seed);return new byte[] { (byte)random.Next(255), (byte)random.Next(255) };
}/// <summary>
/// 获取读取命令
/// </summary>
/// <param name="address">寄存器起始地址</param>
/// <param name="stationNumber">站号</param>
/// <param name="functionCode">功能码</param>
/// <param name="length">读取长度</param>
/// <returns></returns>
public byte[] GetReadCommand(string address, byte stationNumber, byte functionCode, ushort length, byte[] check = null)
{var readAddress = ushort.Parse(address?.Trim());if (plcAddresses) readAddress = (ushort)(Convert.ToUInt16(address?.Trim().Substring(1)) - 1);byte[] buffer = new byte[12];buffer[0] = check?[0] ?? 0x19;buffer[1] = check?[1] ?? 0xB2;//Client发出的检验信息buffer[2] = 0x00;buffer[3] = 0x00;//表示tcp/ip 的协议的Modbus的协议buffer[4] = 0x00;buffer[5] = 0x06;//表示的是该字节以后的字节长度buffer[6] = stationNumber; //站号buffer[7] = functionCode; //功能码buffer[8] = BitConverter.GetBytes(readAddress)[1];buffer[9] = BitConverter.GetBytes(readAddress)[0];//寄存器地址buffer[10] = BitConverter.GetBytes(length)[1];buffer[11] = BitConverter.GetBytes(length)[0];//表示request 寄存器的长度(寄存器个数)return buffer;
}/// <summary>
/// 发送报文,并获取响应报文(如果网络异常,会自动进行一次重试)
/// TODO 重试机制应改成用户主动设置
/// </summary>
/// <param name="command"></param>
/// <returns></returns>
public Result<byte[]> SendPackageReliable(byte[] command)
{try{var result = SendPackageSingle(command);if (!result.IsSucceed){WarningLog?.Invoke(result.Err, result.Exception);//如果出现异常,则进行一次重试 var conentResult = Connect();if (!conentResult.IsSucceed)return new Result<byte[]>(conentResult);return SendPackageSingle(command);}elsereturn result;}catch (Exception ex){try{WarningLog?.Invoke(ex.Message, ex);//如果出现异常,则进行一次重试 var conentResult = Connect();if (!conentResult.IsSucceed)return new Result<byte[]>(conentResult);return SendPackageSingle(command);}catch (Exception ex2){Result<byte[]> result = new Result<byte[]>();result.IsSucceed = false;result.Err = ex2.Message;result.AddErr2List();return result.EndTime();}}
}
4.其他类型数据读取
/// <summary>/// 读取Int16类型数据/// </summary>/// <param name="address">寄存器起始地址</param>/// <param name="stationNumber">站号</param>/// <param name="functionCode">功能码</param>/// <returns></returns>public Result<short> ReadInt16(string address, byte stationNumber = 1, byte functionCode = 3){var readResut = Read(address, stationNumber, functionCode);var result = new Result<short>(readResut);if (result.IsSucceed)result.Value = BitConverter.ToInt16(readResut.Value, 0);return result.EndTime();}/// <summary>/// 按位的方式相关文章:
基于modbusTcp连接Modbus Slave本地模拟服务通讯(C#编写ModbusTcp类库)(一)
C#编写ModbusTcp类库,模拟plc进行本地通信测试 Modbus是一个应用层协议,常用于工业自动化设备之间的通信,主要有两种传输方式:RTU和TCP。 常见的功能码包括读取线圈(01)、读取离散输入(02)、读保持寄存器(03)、读输入寄存器(04)、写单个线圈(05)、写单个寄存器(…...
IS-IS:单区域集成配置与多区域集成配置
一、IS-IS概述 IS-IS(Intermediate System to Intermediate System) 是一种链路状态内部网关协议(IGP),设计用于自治系统(AS)内部的路由选择。最初由ISO为OSI模型的无连接网络服务(…...
Open GL ES ->GLSurfaceView正交投影与透视投影方法中近远平面取值参考
坐标系 OpenGL ES使用右手坐标系,相机默认朝向负z方向 相机位置|vz轴<----- 0 -----> -near -----> -far -----不可见 可见区域 不可见裁剪规则 far>near>0,只有z值在[-far, -near]范围内的物体可见, 当z > -near…...
API 安全之认证鉴权
作者:半天 前言 API 作为企业的重要数字资源,在给企业带来巨大便利的同时也带来了新的安全问题,一旦被攻击可能导致数据泄漏重大安全问题,从而给企业的业务发展带来极大的安全风险。正是在这样的背景下,OpenAPI 规范…...
Gateway实战入门(四)、断言-请求头以及请求权重分流等
spring cloud-Gateway:断言-请求头以及请求权重分流等 一、断言Header信息要求项目前置环境要求案例一、断言-请求头信息-匹配X-Request-Id1、配置文件及代码2、测试案例二、断言-请求头信息-匹配API版本场景主要配置信息案例三、断言-请求头信息:匹配请求来源场景主要配置信…...
【人工智能】从 Llama 到 DeepSeek:开源大模型的演进与技术对比
《Python OpenCV从菜鸟到高手》带你进入图像处理与计算机视觉的大门! 解锁Python编程的无限可能:《奇妙的Python》带你漫游代码世界 随着人工智能的迅猛发展,开源大语言模型(LLM)在自然语言处理领域扮演着越来越重要的角色。本文从 Meta 的 Llama 系列开始,追溯开源大模…...
[测试] Google Test | 主流的 C 测试框架
目录 GoogleTest 2. 准备工作 3. 测试 4.怎么用 Attention is All You Need 写项目代码的时候 边写边测试 非常重要,这样可以帮助我们减少很多的问题。 这篇文章后面 主要以 GoogleTest 为例,进行介绍最近找了些 gtest 相关的资料,学习了下.后面主要…...
OpenCV 图形API(3)高层次设计概览
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 描述 G-API 是一个异构框架,提供了统一的 API 来使用多个支持的后端编程图像处理流水线。 关键的设计理念是在指定使用哪些内核和设备时保持流…...
51单片机的五类指令(五)——位操作类指令
目录 一、位传送指令 1、MOV C, bit 2、MOV bit, C 3、位传送指令的应用场景 二、位变量修改指令 1、CLR(清 0 指令) 2、SETB(置 1 指令) 3、位变量修改指令的应用场景 三、位变量逻辑操作指令 1、位变量逻辑与指令 ANL…...
用python编写poc的流程
目录 一、POC 编写核心流程 二、常用 Python 库与工具 三、POC 框架推荐 四、高级优化技巧 五、安全规范与注意事项 六、实战案例:命令注入漏洞验证 一、POC 编写核心流程 漏洞分析 确定漏洞类型:根据目标特征判断漏洞类型(如 SQL 注入、…...
碰一碰发视频网页版本开发的源码搭建指南
引言 在数字化信息快速传播的时代,近场通信(NFC)技术为信息交互带来了新的便捷方式。通过网页版本实现碰一碰发视频功能,能够让用户在浏览器环境中轻松实现视频分享,拓展了视频传播的途径。本文将详细介绍碰一碰发视频…...
【含文档+PPT+源码】基于Python爬虫二手房价格预测与可视化系统的设计与实现
项目介绍 本课程演示的是一款基于Python爬虫二手房价格预测与可视化系统,主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的 Java 学习者。 包含:项目源码、项目文档、数据库脚本、软件工具等所有资料 带你从零开始部署运行本套系统 该项…...
多台服务器上docker部署 Redis 集群
规划集群节点 确保你的服务器有固定 IP,比如: 172.16.17.100 172.16.17.101 172.16.17.102 每台服务器运行 2 个 Redis 节点,总共 6 个节点,满足 Redis Cluster 最小节点数要求。 2. 在每台服务器上运行 Redis 在每台服务器上执行…...
Redis-16.在Java中操作Redis-Spring Data Redis使用方式-操作有序集合类型的数据
一. 操作有序集合类型的数据 package com.sky.test;import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.redis.core.*;imp…...
针对单台浪潮服务器运行Windows Server 2019和SQL Server的MES系统场景、高效能监控策略(兼顾软硬件健康)
--- ### **一、监控架构设计原则** - **轻量化**:优先使用Windows原生工具和免费方案,避免额外资源消耗 - **关键性聚焦**:仅监控直接影响MES运行的核心指标 - **自动化告警**:异常发生时主动触发通知,无需人工巡检 -…...
Vue Transition组件类名+TailwindCSS
#本文教学结合TailwindCSS实现一个Transition动画的例子# 举例代码: <transition enter-active-class"transition-all duration-300 ease-out"enter-from-class"opacity-0 translate-y-[-10px]"enter-to-class"opacity-100 translate-…...
Anaconda和Pycharm的区别,以及如何选择两者
目录 主要区别详细说明如何选择?Anaconda的使用步骤 主要区别 Anaconda 和 PyCharm 是 Python 开发中常用的两个工具,但它们的定位和功能完全不同。以下是它们的主要区别: 对比项AnacondaPyCharm类型Python 发行版 包管理工具Python 集成开…...
STM32智能手表——任务线程部分
RTOS和LVGL我没学过,但是应该能硬啃这个项目例程 ├─Application/User/Tasks # 用于存放任务线程的函数 │ ├─user_TaskInit.c # 初始化任务 │ ├─user_HardwareInitTask.c # 硬件初始化任务 │ ├─user_RunModeTasks.c…...
SQL命令
一、表的创建 SQL MS Access、MySQL 和 SQL Server 数据类型 | 菜鸟教程 SQL Server 和 MySQL 中的 Date 函数 | 菜鸟教程 1.1、创建表 CREATE TABLE Citys (CityID int PRIMARY KEY,CityName varchar(255) );CREATE TABLE Per (PersonID int PRIMARY KEY, …...
DRM_CLIENT_CAP_UNIVERSAL_PLANES和DRM_CLIENT_CAP_ATOMIC
drmSetClientCap(fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1); drmSetClientCap(fd, DRM_CLIENT_CAP_ATOMIC, 1); 这两行代码用于启用 Linux DRM(Direct Rendering Manager)客户端的两个关键特性,具体作用如下: 1. drmSetClientCap…...
anaconda安装 创建虚拟环境+pycharm中conda环境配置
miniconda下载安装参考以下链接: https://blog.csdn.net/2301_76831056/article/details/143165738?fromshareblogdetail&sharetypeblogdetail&sharerId143165738&sharereferPC&sharesourceweixin_63339973&sharefromfrom_link (注…...
EasyExcel导出导入excel工具类
接上一篇EasyExcel导出导入excel的文章,附上一份完整的工具类代码。对于字体颜色名称,请参考这篇文章。 POI字体颜色 小技巧 类转换用属性拷贝不同类如果有相同属性,则使用反射验证,减少代码量 private List<Person> vali…...
终端SSH连接工具SecureCRT安装和连接Linux
SecureCRT 9.5是一款集终端仿真与加密功能于一身的专业软件,其坚如磐石的安全性、高效的信息传输能力以及高度可定制的会话管理,使得它成为众多用户的首选。该软件不仅支持SSH2、SSH1、Telnet等多种协议,还提供了Relogin、Serial、TAPI、RAW等…...
赛逸展2025“创新引擎”启动:限量席位,点亮科技绿色新征程
当今时代,科技革新与绿色发展已然成为推动社会进步的双引擎。2025第七届亚洲消费电子技术贸易展(赛逸展)敏锐捕捉这一趋势,重磅打造“科技创新专区”,并面向科技、绿色企业吹响限量招募号角。 这个独具特色的专区紧扣…...
Spring的 init-method, @PostConstruct, InitializingBean 对比
Spring的 init-method, PostConstruct, InitializingBean 对比 在Spring框架中,init-method、PostConstruct和InitializingBean都是用于定义Bean初始化后执行逻辑的机制,但它们在实现方式、耦合度、执行顺序及适用场景上有所不同。以下是它们的对比总结…...
Gogs 精简备份与恢复方案(仅SQLite数据库和配置)
一、备份方案设计 1. 备份内容 SQLite数据库文件:/home/git/gogs/data/gogs.db 配置和附件:/home/git/gogs/custom 整个目录 2. 备份策略 每周日凌晨2点执行完整备份 保留最近4周的备份文件 备份存储在独立分区 /backup(使用永久化挂载…...
FPGA实现数码管显示分秒时间
目录 一. verilog实现 二. 烧录验证 三. 结果验证 使用开发板:DE2-115开发板 一. verilog实现 要实现分和秒,需要知道定时器的频率,通过查手册可知,我使用的开发板时钟为50hz,也就是时钟一个周期是2微秒。 5000000…...
读书记录九之《在峡江的转弯处-陈行甲人生笔记》
距离上本读完的书,写读后感有很长一段时间了,中间读了几本书,但都没写点文字,没错,是懒病又犯了。陈行甲这本书,一开始从网络上推荐看到,看之前介绍是一本人物自传的回忆录。我个人对这类贴近的…...
可视化开发:用Qt实现Excel级动态柱状图
Qt柱状图 QtChart 首先我们介绍一下 图表建立的基础:Qt Charts QtChart 是Qt框架的一个模块,专注与提供交互式数据可视化功能 俗话就是 用于用户轻松创建各种类型的图表和图形界面 它包含的图表类型有很多:折线图,饼图&#x…...
从零实现Json-Rpc框架】- 项目实现 - 基于Dispatcher模块的RPC框架
📢博客主页:https://blog.csdn.net/2301_779549673 📢博客仓库:https://gitee.com/JohnKingW/linux_test/tree/master/lesson 📢欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正! &…...
