IoTClient开发3 - ModBusTcp协议客户端实现
前言
进过前面两章的介绍,今天开始正式的实战。
进制转换
很多朋友对于进制转换可能是在刚学计算机的时候有接触,后来做高级语言开发可能就慢慢忘记了。我们做工控开发的时候需要经常进行进制转换,这里和大家一起复习下。
一个字节等8位(1byte = 8bit),可以存储2^8(0-255)共计256个数字。所以我们要对8、256等数字要敏感。
int16(short), int32(int), int64(long) 分别是占用2个字节、4个字节、8个字节,Single(float)也是占用4个字节。
bool System.Boolean (布尔型,其值为 true 或者 false)
byte System.Byte (字节型,占 1 字节,表示 8 位正整数,范围 0 ~ 255)
sbyte System.SByte (带符号字节型,占 1 字节,表示 8 位整数,范围 -128 ~ 127)
char System.Char (字符型,占有 2 个字节,表示 1 个 Unicode 字符)
short System.Int16 (短整型,占 2 字节,表示 16 位整数,范围 -32,768 ~ 32,767)
ushort System.UInt16 (无符号短整型,占 2 字节,表示 16 位正整数,范围 0 ~ 65,535)
uint System.UInt32 (无符号整型,占 4 字节,表示 32 位正整数,范围 0 ~ 4,294,967,295)
int System.Int32 (整型,占 4 字节,表示 32 位整数,范围 -2,147,483,648 到 2,147,483,647)
float System.Single (单精度浮点型,占 4 个字节)
ulong System.UInt64 (无符号长整型,占 8 字节,表示 64 位正整数)
long System.Int64 (长整型,占 8 字节,表示 64 位整数)
double System.Double (双精度浮点型,占8 个字节)
接着我们来看其他进制转十进制的计算
十进制转十进制
1263 = 1*10^3 + 2*10^2 + 6*10^1 + 3*10^0 = 1000 + 200 + 60 + 3 = 1263
二进制转十进制
1001 = 1*2^3 + 0*2^2 + 0*2^1 + 1*2^0 = 8 + 0 + 0 + 1 = 9
十六进制转十进制
3245 = 3*16^3 + 2*16^2 + 4*16^1 + 5*16^0 = 3*4096 + 2*256 + 4*16 + 5 = 12869
十进制转二进制
第八位 第七位 第六位 第五位 第四位 第三位 第二位 第一位
2^7 2^6 2^5 2^4 2^3 2^2 2^1 2^0
128 64 32 16 8 4 2 1
以上位二进制位能存储最大十进制数,所以我们反过来也可以对照把十进制转二进制。比如86,
86小于128多以第八位是0,86大于64所以第七位是1。86-64=22,22小于32所以第六位是0,22大于16所以第五位是1。。。
所以最好转成二进制是:0101 0110
二进制转十六进制
我们用二进制 0101 0110来演示,也就是上面十进制的86。
当然,你最好用计算器验证下
ModBusTcp协议介绍
我们在对进制转换进行复习过后,接下来讲ModBusTcp协议。
ModBus协议是现在工控里面用的比较多比较通用的一种协议,什么可靠啊、简单啊等等一些优点就不说了,直接入正题。
ModBus分为RTU、ASCII、TCP三种方式进行通信,今天我们只讲TCP。
在ModBus里面有站号、功能码、寄存器地址等概念。
- 站号:多设备的标识号
- 功能码:一些功能的标识号
功能码详解:
01:读线圈
02:读离散量
03:读保持寄存器(每个寄存器含有两个字节)
04:读输入寄存器
05:写单个线圈
06:写单个寄存器
15:用于写多个线圈
16:写多个寄存器
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 因为报文长度变了
- 其他的报文意义和请求报文一致
ModBusTcp对寄存器的读取
有了上面的三个报文做参考,我们就可以用Socket来实现ModBusTcp协议了。其实协议就是按照报文的规定来,也没有想的那么复杂,和我们前面实现的聊天通讯软件区别不大。
第一步,我们先实现数据读取报文的组装:
/// <summary>
/// 获取读取命令(此方法传入参数后就可以得到类似19 B2 00 00 00 06 02 03 00 04 00 01这样的请求报文)
/// </summary>
/// <param name="address">寄存器起始地址</param>
/// <param name="stationNumber">站号</param>
/// <param name="functionCode">功能码</param>
/// <param name="length">读取长度</param>
/// <returns></returns>
public static byte[] GetReadCommand(ushort address, byte stationNumber, byte functionCode, ushort length)
{
byte[] buffer = new byte[12];
buffer[0] = 0x19;
buffer[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(address)[1];
buffer[9] = BitConverter.GetBytes(address)[0];//寄存器地址
buffer[10] = BitConverter.GetBytes(length)[1];
buffer[11] = BitConverter.GetBytes(length)[0];//表示request 寄存器的长度(寄存器个数)
return buffer;
}
第二步,就是建立我们的Socket连接,并发送请求报文
//1 创建Socket
var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//2 建立连接
socket.Connect(new IPEndPoint(IPAddress.Parse(ip), 端口));
//3 获取命令[组装请求报文](寄存器起始地址:4、站号:2、功能码:3、读取寄存器长度:1)
byte[] command = GetReadCommand(4, 2, 3, 1);
//4 发送命令
socket.Send(command);
第三步,解析响应报文,得到数据值
//5 读取响应
byte[] buffer1 = new byte[8];//先读取前面八个字节(Map报文头)
socket.Receive(buffer1, 0, buffer1.Length, SocketFlags.None);
//5.1 获取将要读取的数据长度
int length = buffer1[4] * 256 + buffer1[5] - 2;//减2是因为这个长度数据包括了单元标识符和功能码,占两个字节
//5.2 读取数据
byte[] buffer2 = new byte[length];
var readLength2 = socket.Receive(buffer2, 0, buffer2.Length, SocketFlags.None);
byte[] buffer3 = new byte[readLength2 - 1];
//5.3 过滤第一个字节(第一个字节代表数据的字节个数)
Array.Copy(buffer2, 1, buffer3, 0, buffer3.Length);
var buffer3Reverse = buffer3.Reverse().ToArray();
var value = BitConverter.ToInt16(buffer3Reverse, 0);
//6 关闭连接
socket.Shutdown(SocketShutdown.Both);
socket.Close();
ModBusTcp对寄存器的写入
对于数据写入就更简单了。
第一步,组装请求报文
/// <summary>
/// 获取写入命令
/// </summary>
/// <param name="address">寄存器地址</param>
/// <param name="values"></param>
/// <param name="stationNumber">站号</param>
/// <param name="functionCode">功能码</param>
/// <returns></returns>
public static byte[] GetWriteCommand(ushort address, byte[] values, byte stationNumber, byte functionCode)
{
byte[] buffer = new byte[13 + values.Length];
buffer[0] = 0x19;
buffer[1] = 0xB2;//检验信息,用来验证response是否串数据了
buffer[4] = BitConverter.GetBytes(7 + values.Length)[1];
buffer[5] = BitConverter.GetBytes(7 + values.Length)[0];//表示的是header handle后面还有多长的字节
buffer[6] = stationNumber; //站号
buffer[7] = functionCode; //功能码
buffer[8] = BitConverter.GetBytes(address)[1];
buffer[9] = BitConverter.GetBytes(address)[0];//寄存器地址
buffer[10] = (byte)(values.Length / 2 / 256);
buffer[11] = (byte)(values.Length / 2 % 256);//写寄存器数量(除2是一个寄存器两个字节,寄存器16位。除以256是byte最大存储255。)
buffer[12] = (byte)(values.Length); //写字节的个数
values.CopyTo(buffer, 13); //把目标值附加到数组后面
return buffer;
}
第二步,建立Socket连接,并发送报文
//1 创建Socket
var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//2 建立连接
socket.Connect(new IPEndPoint(IPAddress.Parse(ip), 端口));
//值的转换
short value = 32;
var values = BitConverter.GetBytes(value).Reverse().ToArray();
//3 获取并发送命令(寄存器起始地址、站号、功能码)
var command = GetWriteCommand(4, values, 2, 16);
socket.Send(command);
//4 关闭连接
socket.Shutdown(SocketShutdown.Both);
socket.Close();
结束
- 同步至索引目录:《物联网基础组件IoTClient开发系列》
- 参考1:https://www.cnblogs.com/any91/p/3530540.html
- 参考2:https://www.cnblogs.com/DreamRecorder/p/9081134.html
- demo:https://github.com/zhaopeiym/BlogDemoCode/tree/master/IoTClient/ModbusTCP
- 完整实现:https://github.com/zhaopeiym/IoTClient
IoTClient开发3 - ModBusTcp协议客户端实现的更多相关文章
- IoTClient开发4 - ModBusTcp协议服务端模拟
前言 上篇我们实现了ModBusTcp协议的客户端读写,可是在很多时候编写业务代码之前是没有现场环境的.总不能在客户现场去写代码,或是蒙着眼睛写然后求神拜佛不出错,又或是在办公室部署一套硬件环境.怎么 ...
- IoTClient开发6 - S7-200SmarTcp协议客户端实现
环境和工具 服务端电脑IP:192.168.1.130 客户端电脑IP:192.168.1.120 1.在服务端电脑运行IoTClientTool 2.运行Wireshark 3.在客户端电脑运行Io ...
- IoTClient开发5 - ModBusRtu协议
前言 前面我们介绍了ModBusTcp协议.今天我们接着来介绍ModBusRtu协议.和ModBusTcp不同的是ModBusRtu基于串口通信,ModBusTcp是基于Tcp以太网通信. 所以我们在 ...
- 物联网基础组件IoTClient开发系列
系列目录 IoTClient开发1 - 你也可以写个聊天程序 IoTClient开发2 - 你也可以写个服务器 IoTClient开发3 - ModBusTcp协议客户端实现 IoTClient开发4 ...
- ModbusTCP协议
简介 Modbus由MODICON公司于1979年开发,是一种工业现场总线协议标准.1996年施耐德公司推出基于以太网TCP/IP的Modbus协议:ModbusTCP. Modbus协议是一项应用层 ...
- 开源的C#实现WebSocket协议客户端和服务器websocket-sharp组件解析
很久没有写博客了(至少自己感觉很长时间没有写了),没办法啊,楼主也是需要生活的人啊,这段一直都在找工作什么的.(整天催我代码的人,还望多多谅解啊,我会坚持写我们的项目的,还是需要相信我的,毕竟这是一个 ...
- Loadrunner 脚本开发-利用loadrunner开发Windows Sockets协议脚本
脚本开发-利用loadrunner开发Windows Sockets协议脚本 by:授客 QQ:1033553122 欢迎加入软件性能测试交流QQ群:7156436 实践举例 Socket服务端简单实 ...
- C#实现WebSocket协议客户端和服务器websocket sharp组件实例解析
看到这篇文章的题目,估计很多人都会问,这个组件是不是有些显的无聊了,说到web通信,很多人都会想到ASP.NET SignalR,或者Nodejs等等,实现web的网络实时通讯.有关于web实时通信的 ...
- 在Livemedia的基础上开发自己的流媒体客户端 V 0.01
在Livemedia的基础上开发自己的流媒体客户端 V 0.01 桂堂东 xiaoguizi@gmail.com 2004-10 2004-12 友情申明: 本文档适合已经从事流媒体传输工作或者对网络 ...
随机推荐
- LINUX系统学习以及初学者系统下载
Linux系统常用命令大全 来源:服务器之家 [博客中所有文章如有不对的地方希望看官们指出,有问题也可以提出来相互交流,相互学习,感谢大家!] 初学者建议安装:sentOS Ubuntu系统下载连接h ...
- Nginx+PHP7.3.9 Docker镜像制作
最近因项目需要制作了多个版本的php docker镜像,制作过程可谓是一波三折,因基于yum的方式安装php的方式在安装扩展插件时很不方便,不容易找到插件对应的yum源,所以PHP在docker镜像中 ...
- 使用JavaScript·求数组的最大值和最小值
前言 在数组中并没有提供arr.max()和arr.min()这样的方法.那么是不是可以通过别的方式实现类似这样的方法呢?那么今天我们就来整理取出数组中最大值和最小值的一些方法. 法一:其实利用 ...
- 理解 Redux 的中间件
将该思想抽象出来,其实和 Redux 就无关了.问题变成,怎样实现在截获函数的执行,以在其执行前后添加自己的逻辑. 为了演示,我们准备如下的示例代码来模拟 Redux dispatch action ...
- VirtualBox 启动时提示“获取 VirtualBox COM 对象失败”的解决
昨天给电脑打了一堆补丁和更新,今天启动 VirtualBox 的时候提示 “获取 VirtualBox COM 对象失败”,好在百度到了 CSDN 上的一篇文章解决了这个问题. 错误详情 “获取 Vi ...
- Windows和Mac系统下安装Docker
在windows和mac系统中使用Docker Desktop安装Docker对系统的要求是很高的. 对于 Windows 系统来说,安装 Docker for Windows 需要符合以下条件: 必 ...
- 为了给女朋友买件心怡内衣,我用Python爬虫了天猫内衣售卖数据
真爱,请置顶或星标 大家好,希望各位能怀着正直.严谨.专业的心态观看这篇文章.ヾ(๑╹◡╹)ノ" 接下来我们尝试用 Python 抓取天猫内衣销售数据,并分析得到中国女性普遍的罩杯数据.最受 ...
- 低效sql语句执行缓慢引起的大量占用服务器的CPU问题处理 (优化心得)
1> 2> 3> 4> 5>删除不良的执行计划后执行时间仍然有150s,这实在是太慢了,继续查看原sql代码,发现父表的关联条件放在了子查询里,这是应该避免的 调整原sq ...
- 从零开始的vue学习笔记(七)
前言 今天花一天时间阅读完vuex的官方文档,简单的做一下总结和记录 Vuex是什么 Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式,以前的符合"单向数据流"理念的 ...
- java之ReentrantLock详解
前言 如果一个代码块被synchronized修饰了,当一个线程获取了相应的锁,并执行该代码块时,其他线程便只能一直等待,等待获取锁的释放,现在有这么一种情况,这个获取锁的线程由于要等待IO或者其他原 ...