IoTClient开发4 - ModBusTcp协议服务端模拟
程序员文章站
2022-06-20 17:29:57
前言 上篇我们实现了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;
结束
下一篇: java-完全解耦