IoTClient开发4 - ModBusTcp协议服务端模拟
前言
上篇我们实现了ModBusTcp协议的客户端读写,可是在很多时候编写业务代码之前是没有现场环境的。总不能在客户现场去写代码,或是蒙着眼睛写然后求神拜佛不出错,又或是在办公室部署一套硬件环境。怎么说都感觉不太合适,如果我们能用软件仿真模拟硬件那不就完美了,以后有各种不同的硬件协议接口都模拟出来,而不是每个硬件都买一套回来部署了做测试。
真要用软件仿真模拟也是可以的,客户端是对协议的请求报文发送和响应报文的解析,服务端其实就是请求报文的接收和响应报文的发送,正好和客户端的动作相反。
前面我们在写你也可以写个聊天程序 - C# Socket学习1的时候就有写Socket服务端实现,其实这个也差不了多少。
ModBusTcp报文分析(上篇拷贝过来的,方便下面代码实现的时候做对比)
协议的理解和实现主要就是要对协议报文理解。(注意:以下报文数据都是十六进制)
数据【读取-请求报文】:19 B2 00 00 00 06 02 03 00 04 00 01
- 19 B2 是客户端发的检验信息,随意定义。
- 00 00 代表是基于tcp/ip协议的modbus
- 00 06 标识后面还有多长的字节
- 02 表示站号地址
- 03 为功能码(读保持寄存器)
- 00 04 为寄存器地址
- 00 01 为寄存器的长度(寄存器个数)
数据【读取-响应报文】(分两次获取)
第一次获取前八个字节(Map报文头):19 B2 00 00 00 05 02 03 02 00 20
- 19 B2 检验验证信息(复制的客户端发的,配件检验)
- 00 00 代表是基于tcp/ip协议的modbus(复制的客户端发的)
- 00 05 为当前位置到最后的长度
- 02 表示站号地址(复制的客户端发的)
- 03 为功能码(复制的客户端发的)
第二次获取的报文:02 00 20
- 02 字节个数
- 00 20 响应的数据
数据【写入-请求报文】:19 B2 00 00 00 09 02 10 00 04 00 01 02 00 20
- 19 B2 是客户端发的检验信息,随意定义。
- 00 00 代表是基于tcp/ip协议的modbus
- 00 09 从本字节下一个到最后
- 02 站号
- 10 功能码(转十进制就是16)
- 00 04 寄存器地址
- 00 01 寄存器的长度(寄存器个数)
- 02 写字节的个数
- 00 20 要写入的值(转十进制为32)
数据【写入-响应报文】:19 B2 00 00 00 06 02 10 00 04 00 01
和请求报文的区别
- 没有了请求报文的数据值
- 00 09 变成了00 06 因为报文长度变了
- 其他的报文意义和请求报文一致
实现
//启动服务
public void Start()
{
//1 创建Socket对象
var socketServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//2 绑定ip和端口
IPEndPoint ipEndPoint = new IPEndPoint(IPAddress.Any, 502);
socketServer.Bind(ipEndPoint);
//3、开启侦听(等待客户机发出的连接),并设置最大客户端连接数为10
socketServer.Listen(10);
Task.Run(() => { Accept(socketServer); });
}
//客户端连接到服务端
void Accept(Socket socket)
{
while (true)
{
//阻塞等待客户端连接
Socket newSocket = socket.Accept();
Task.Run(() => { Receive(newSocket); });
}
}
以上都和我们前面的一样,这了不一样的地方就是对请求报文的解析和响应报文的组装发送
//接收客户端发送的消息
void Receive(Socket newSocket)
{
while (newSocket.Connected)
{
byte[] requetData1 = new byte[8];
//读取客户端发送报文 报文头
int readLeng = newSocket.Receive(requetData1, 0, requetData1.Length, SocketFlags.None);
byte[] requetData2 = new byte[requetData1[5] - 2];
//读取客户端发送报文 报文数据
readLeng = newSocket.Receive(requetData2, 0, requetData2.Length, SocketFlags.None);
var requetData = requetData1.Concat(requetData2).ToArray();
byte[] responseData1 = new byte[8];
//复制请求报文中的报文头
Buffer.BlockCopy(requetData, 0, responseData1, 0, responseData1.Length);
//这里可以自己实现一个对象,用来存储客户端写入的数据(也可以用redis等实现数据的持久化)
DataPersist data = new DataPersist("");
//根据协议,报文的第八个字节是功能码(前面我们有说过 03:读保持寄存器 16:写多个寄存器)
switch (requetData[7])
{
//读保持寄存器
case 3:
{
var value = data.Read(requetData[9]);
short.TryParse(value, out short resultValue);
var bytes = BitConverter.GetBytes(resultValue);
//当前位置到最后的长度
responseData1[5] = (byte)(3 + bytes.Length);
byte[] responseData2 = new byte[3] { (byte)bytes.Length, bytes[1], bytes[0] };
var responseData = responseData1.Concat(responseData2).ToArray();
newSocket.Send(responseData);
}
break;
//写多个寄存器
case 16:
{
data.Write(requetData[9], requetData[requetData.Length - 1].ToString());
var responseData = new byte[12];
Buffer.BlockCopy(requetData, 0, responseData, 0, responseData.Length);
responseData[5] = 6;
newSocket.Send(responseData);
}
break;
}
}
}
这段要点就是根据请求报文获得功能码,然后对报文数据进行读取或写入动作。当然你完全可以对写入的数据进行持久化存储(如用redis),这样在断电或重启后数据依然可以正常读取。
当然,以上只是个类伪代码,为了减少代码量和方便理解,很多情况和实际可能性没有做对应的处理。
IoTClient中ModBusTcp协议的使用
安装
Nuget安装 Install-Package IoTClient
或图形化安装

使用
//1、实例化客户端 - 输入正确的IP和端口
ModBusTcpClient client = new ModBusTcpClient("127.0.0.1", 502);
//2、写操作 - 参数依次是:地址 、值 、站号 、功能码
client.Write("4", (short)33, 2, 16);
client.Write("4", (short)3344, 2, 16);
//3、读操作 - 参数依次是:地址 、站号 、功能码
var value = client.ReadInt16("4", 2, 3).Value;
var value2 = client.ReadInt32("4", 2, 3).Value;
//4、如果没有主动Open,则会每次读写操作的时候自动打开自动和关闭连接,这样会使读写效率大大减低。所以建议手动Open和Close。
client.Open();
//5、读写操作都会返回操作结果对象Result
var result = client.ReadInt16("4", 2, 3);
//5.1 读取是否成功(true或false)
var isSucceed = result.IsSucceed;
//5.2 读取失败的异常信息
var errMsg = result.Err;
//5.3 读取操作实际发送的请求报文
var requst = result.Requst;
//5.4 读取操作服务端响应的报文
var response = result.Response;
//5.5 读取到的值
var value3 = result.Value;
结束
- 同步至索引目录:《物联网基础组件IoTClient开发系列》
- demo:https://github.com/zhaopeiym/BlogDemoCode
- 完整实现:https://github.com/zhaopeiym/IoTClient
IoTClient开发4 - ModBusTcp协议服务端模拟的更多相关文章
- IoTClient开发3 - ModBusTcp协议客户端实现
前言 进过前面两章的介绍,今天开始正式的实战. 进制转换 很多朋友对于进制转换可能是在刚学计算机的时候有接触,后来做高级语言开发可能就慢慢忘记了.我们做工控开发的时候需要经常进行进制转换,这里和大家一 ...
- 使用GSoap开发WebService客户端与服务端
Gsoap 编译工具提供了一个SOAP/XML 关于C/C++ 语言的实现, 从而让C/C++语言开发web服务或客户端程序的工作变得轻松了很多. 用gsoap开发web service的大致思路 我 ...
- 基于fiddler的APP抓包及服务端模拟
在HTTP接口的测试过程中,一般我们会按照如下的步骤进行: 1)测试环境的准备 2)HTTP消息体的构造 3)HTTP消息的发送及断言 如果我们可以拿到项目组的接口文档,并且HTTP后台服务是可以工作 ...
- [转]基于fiddler的APP抓包及服务端模拟
在HTTP接口的测试过程中,一般我们会按照如下的步骤进行: 1)测试环境的准备 2)HTTP消息体的构造 3)HTTP消息的发送及断言 如果我们可以拿到项目组的接口文档,并且HTTP后台服务是可以工作 ...
- C#开发BIMFACE系列6 服务端API之获取文件信息
在<C#开发BIMFACE系列4 服务端API之源上传文件>.<C#开发BIMFACE系列5 服务端API之文件直传>两篇文章中详细介绍了如何将本地文件上传到BIMFACE服务 ...
- C#开发BIMFACE系列4 服务端API之源上传文件
在注册成为BIMFACE的应用开发者后,要能在浏览器里浏览你的模型或者获取你模型内的BIM数据, 首先需要把你的模型文件上传到BIMFACE.根据不同场景,BIMFACE提供了丰富的文件相关的接口. ...
- C#开发BIMFACE系列3 服务端API之获取应用访问凭证AccessToken
系列目录 [已更新最新开发文章,点击查看详细] BIMFACE 平台为开发者提供了大量的服务器端 API 与 JavaScript API,用于二次开发 BIM 的相关应用. BIMFACE ...
- C#开发BIMFACE系列7 服务端API之获取文件信息列表
系列目录 [已更新最新开发文章,点击查看详细] 本文详细介绍如何获取BIMFACE平台中所有上传过的文件信息列表. 请求地址:GET https://file.bimface.com/file ...
- C#开发BIMFACE系列8 服务端API之获取文件上传状态信息
系列目录 [已更新最新开发文章,点击查看详细] 在BIMFACE控制台上传文件,上传过程及结束后它会自动告诉你文件的上传状态,目前有三种状态:uploading,success,failure ...
随机推荐
- Java工程师学习指南(中级篇)
Java工程师学习指南 中级篇 最近有很多小伙伴来问我,Java小白如何入门,如何安排学习路线,每一步应该怎么走比较好.原本我以为之前的几篇文章已经可以解决大家的问题了,其实不然,因为我写的文章都是站 ...
- 从零学习基于Python的RobotFramework自动化
从零学习基于Python的RobotFramework自动化 一. Python基础 1) 版本差异 版本 编码 语法 其他 2.X ASCII try: raise Type ...
- 面试必备:高频算法题终章「图文解析 + 范例代码」之 矩阵 二进制 + 位运算 + LRU 合集
Attention 秋招接近尾声,我总结了 牛客.WanAndroid 上,有关笔试面经的帖子中出现的算法题,结合往年考题写了这一系列文章,所有文章均与 LeetCode 进行核对.测试.欢迎食用 本 ...
- 比较两个文件的异同Python3 标准库difflib 实现
比较两个文件的异同Python3 标准库difflib 实现 对于要比较两个文件特别是配置文件的差异,这种需求很常见,如果用眼睛看,真是眼睛疼. 可以使用linux命令行工具diff a_file b ...
- CSP2019 考前复习
动态规划 [NOIP2016]愤怒的小鸟(状压+思维) 多组数据题 共有i只猪,给出每只猪的坐标,鸟的飞行轨迹为经过原点的抛物线,求最少要多少只鸟能消灭所有的猪 \[ 猪数量n<=18 \] 看 ...
- Python开发【第四篇】语句与函数
语句 statement 语句是由一些表达式组成,通常一条语句可以独立的执行来完成一部分事情,并且形成结果. 多条语句写在一行内要用分号分开 例子: print('hello world') #这是一 ...
- spring boot 中的路径映射
在spring boot中集成thymeleaf后,我们知道thymeleaf的默认的html的路径为classpath:/templates也就是resources/templates,那如何访问这 ...
- MyBatis 概念
简介 什么是 MyBatis? MyBatis 是一款优秀的持久层框架,它支持定制化 SQL.存储过程以及高级映射.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyB ...
- 百万年薪python之路 -- 迭代器
3.1 可迭代对象 3.1.1 可迭代对象定义 **在python中,但凡内部含有 _ _ iter_ _方法的对象,都是可迭代对象**. 3.1.2 查看对象内部方法 该对象内部含有什么方法除了看源 ...
- 在.net core3.0中使用SignalR实现实时通信
最近用.net core3.0重构网站,老大想做个站内信功能,就是有些耗时的后台任务的结果需要推送给用户.一开始我想简单点,客户端每隔1分钟调用一下我的接口,看看是不是有新消息,有的话就告诉用户有新推 ...