前言

进过前面两章的介绍,今天开始正式的实战。

进制转换

很多朋友对于进制转换可能是在刚学计算机的时候有接触,后来做高级语言开发可能就慢慢忘记了。我们做工控开发的时候需要经常进行进制转换,这里和大家一起复习下。

一个字节等8位(1byte = 8bit),可以存储2^8(0-255)共计256个数字。所以我们要对8、256等数字要敏感。

int16(short), int32(int), int64(long) 分别是占用2个字节、4个字节、8个字节,Single(float)也是占用4个字节。

  1. bool System.Boolean (布尔型,其值为 true 或者 false)
  2. byte System.Byte (字节型,占 1 字节,表示 8 位正整数,范围 0 ~ 255)
  3. sbyte System.SByte (带符号字节型,占 1 字节,表示 8 位整数,范围 -128 ~ 127)
  4. char System.Char (字符型,占有 2 个字节,表示 1 Unicode 字符)
  5. short System.Int16 (短整型,占 2 字节,表示 16 位整数,范围 -32,768 ~ 32,767)
  6. ushort System.UInt16 (无符号短整型,占 2 字节,表示 16 位正整数,范围 0 ~ 65,535)
  7. uint System.UInt32 (无符号整型,占 4 字节,表示 32 位正整数,范围 0 ~ 4,294,967,295)
  8. int System.Int32 (整型,占 4 字节,表示 32 位整数,范围 -2,147,483,648 2,147,483,647)
  9. float System.Single (单精度浮点型,占 4 个字节)
  10. ulong System.UInt64 (无符号长整型,占 8 字节,表示 64 位正整数)
  11. long System.Int64 (长整型,占 8 字节,表示 64 位整数)
  12. double System.Double (双精度浮点型,占8 个字节)

接着我们来看其他进制转十进制的计算

  1. 十进制转十进制
  2. 1263 = 1*10^3 + 2*10^2 + 6*10^1 + 3*10^0 = 1000 + 200 + 60 + 3 = 1263
  3. 二进制转十进制
  4. 1001 = 1*2^3 + 0*2^2 + 0*2^1 + 1*2^0 = 8 + 0 + 0 + 1 = 9
  5. 十六进制转十进制
  6. 3245 = 3*16^3 + 2*16^2 + 4*16^1 + 5*16^0 = 3*4096 + 2*256 + 4*16 + 5 = 12869

十进制转二进制

  1. 第八位 第七位 第六位 第五位 第四位 第三位 第二位 第一位
  2. 2^7 2^6 2^5 2^4 2^3 2^2 2^1 2^0
  3. 128 64 32 16 8 4 2 1
  4. 以上位二进制位能存储最大十进制数,所以我们反过来也可以对照把十进制转二进制。比如86
  5. 86小于128多以第八位是086大于64所以第七位是186-64=2222小于32所以第六位是022大于16所以第五位是1。。。



所以最好转成二进制是:0101 0110

二进制转十六进制

我们用二进制 0101 0110来演示,也就是上面十进制的86。

当然,你最好用计算器验证下

ModBusTcp协议介绍

我们在对进制转换进行复习过后,接下来讲ModBusTcp协议。

ModBus协议是现在工控里面用的比较多比较通用的一种协议,什么可靠啊、简单啊等等一些优点就不说了,直接入正题。

ModBus分为RTU、ASCII、TCP三种方式进行通信,今天我们只讲TCP。

在ModBus里面有站号、功能码、寄存器地址等概念。

  • 站号:多设备的标识号
  • 功能码:一些功能的标识号

    功能码详解:
  1. 01:读线圈
  2. 02:读离散量
  3. 03:读保持寄存器(每个寄存器含有两个字节)
  4. 04:读输入寄存器
  5. 05:写单个线圈
  6. 06:写单个寄存器
  7. 15:用于写多个线圈
  8. 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协议了。其实协议就是按照报文的规定来,也没有想的那么复杂,和我们前面实现的聊天通讯软件区别不大。

第一步,我们先实现数据读取报文的组装:

  1. /// <summary>
  2. /// 获取读取命令(此方法传入参数后就可以得到类似19 B2 00 00 00 06 02 03 00 04 00 01这样的请求报文)
  3. /// </summary>
  4. /// <param name="address">寄存器起始地址</param>
  5. /// <param name="stationNumber">站号</param>
  6. /// <param name="functionCode">功能码</param>
  7. /// <param name="length">读取长度</param>
  8. /// <returns></returns>
  9. public static byte[] GetReadCommand(ushort address, byte stationNumber, byte functionCode, ushort length)
  10. {
  11. byte[] buffer = new byte[12];
  12. buffer[0] = 0x19;
  13. buffer[1] = 0xB2;//Client发出的检验信息
  14. buffer[2] = 0x00;
  15. buffer[3] = 0x00;//表示tcp/ip 的协议的modbus的协议
  16. buffer[4] = 0x00;
  17. buffer[5] = 0x06;//表示的是该字节以后的字节长度
  18. buffer[6] = stationNumber; //站号
  19. buffer[7] = functionCode; //功能码
  20. buffer[8] = BitConverter.GetBytes(address)[1];
  21. buffer[9] = BitConverter.GetBytes(address)[0];//寄存器地址
  22. buffer[10] = BitConverter.GetBytes(length)[1];
  23. buffer[11] = BitConverter.GetBytes(length)[0];//表示request 寄存器的长度(寄存器个数)
  24. return buffer;
  25. }

第二步,就是建立我们的Socket连接,并发送请求报文

  1. //1 创建Socket
  2. var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
  3. //2 建立连接
  4. socket.Connect(new IPEndPoint(IPAddress.Parse(ip), 端口));
  5. //3 获取命令[组装请求报文](寄存器起始地址:4、站号:2、功能码:3、读取寄存器长度:1)
  6. byte[] command = GetReadCommand(4, 2, 3, 1);
  7. //4 发送命令
  8. socket.Send(command);

第三步,解析响应报文,得到数据值

  1. //5 读取响应
  2. byte[] buffer1 = new byte[8];//先读取前面八个字节(Map报文头)
  3. socket.Receive(buffer1, 0, buffer1.Length, SocketFlags.None);
  4. //5.1 获取将要读取的数据长度
  5. int length = buffer1[4] * 256 + buffer1[5] - 2;//减2是因为这个长度数据包括了单元标识符和功能码,占两个字节
  6. //5.2 读取数据
  7. byte[] buffer2 = new byte[length];
  8. var readLength2 = socket.Receive(buffer2, 0, buffer2.Length, SocketFlags.None);
  9. byte[] buffer3 = new byte[readLength2 - 1];
  10. //5.3 过滤第一个字节(第一个字节代表数据的字节个数)
  11. Array.Copy(buffer2, 1, buffer3, 0, buffer3.Length);
  12. var buffer3Reverse = buffer3.Reverse().ToArray();
  13. var value = BitConverter.ToInt16(buffer3Reverse, 0);
  14. //6 关闭连接
  15. socket.Shutdown(SocketShutdown.Both);
  16. socket.Close();

ModBusTcp对寄存器的写入

对于数据写入就更简单了。

第一步,组装请求报文

  1. /// <summary>
  2. /// 获取写入命令
  3. /// </summary>
  4. /// <param name="address">寄存器地址</param>
  5. /// <param name="values"></param>
  6. /// <param name="stationNumber">站号</param>
  7. /// <param name="functionCode">功能码</param>
  8. /// <returns></returns>
  9. public static byte[] GetWriteCommand(ushort address, byte[] values, byte stationNumber, byte functionCode)
  10. {
  11. byte[] buffer = new byte[13 + values.Length];
  12. buffer[0] = 0x19;
  13. buffer[1] = 0xB2;//检验信息,用来验证response是否串数据了
  14. buffer[4] = BitConverter.GetBytes(7 + values.Length)[1];
  15. buffer[5] = BitConverter.GetBytes(7 + values.Length)[0];//表示的是header handle后面还有多长的字节
  16. buffer[6] = stationNumber; //站号
  17. buffer[7] = functionCode; //功能码
  18. buffer[8] = BitConverter.GetBytes(address)[1];
  19. buffer[9] = BitConverter.GetBytes(address)[0];//寄存器地址
  20. buffer[10] = (byte)(values.Length / 2 / 256);
  21. buffer[11] = (byte)(values.Length / 2 % 256);//写寄存器数量(除2是一个寄存器两个字节,寄存器16位。除以256是byte最大存储255。)
  22. buffer[12] = (byte)(values.Length); //写字节的个数
  23. values.CopyTo(buffer, 13); //把目标值附加到数组后面
  24. return buffer;
  25. }

第二步,建立Socket连接,并发送报文

  1. //1 创建Socket
  2. var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
  3. //2 建立连接
  4. socket.Connect(new IPEndPoint(IPAddress.Parse(ip), 端口));
  5. //值的转换
  6. short value = 32;
  7. var values = BitConverter.GetBytes(value).Reverse().ToArray();
  8. //3 获取并发送命令(寄存器起始地址、站号、功能码)
  9. var command = GetWriteCommand(4, values, 2, 16);
  10. socket.Send(command);
  11. //4 关闭连接
  12. socket.Shutdown(SocketShutdown.Both);
  13. socket.Close();

结束

IoTClient开发3 - ModBusTcp协议客户端实现的更多相关文章

  1. IoTClient开发4 - ModBusTcp协议服务端模拟

    前言 上篇我们实现了ModBusTcp协议的客户端读写,可是在很多时候编写业务代码之前是没有现场环境的.总不能在客户现场去写代码,或是蒙着眼睛写然后求神拜佛不出错,又或是在办公室部署一套硬件环境.怎么 ...

  2. IoTClient开发6 - S7-200SmarTcp协议客户端实现

    环境和工具 服务端电脑IP:192.168.1.130 客户端电脑IP:192.168.1.120 1.在服务端电脑运行IoTClientTool 2.运行Wireshark 3.在客户端电脑运行Io ...

  3. IoTClient开发5 - ModBusRtu协议

    前言 前面我们介绍了ModBusTcp协议.今天我们接着来介绍ModBusRtu协议.和ModBusTcp不同的是ModBusRtu基于串口通信,ModBusTcp是基于Tcp以太网通信. 所以我们在 ...

  4. 物联网基础组件IoTClient开发系列

    系列目录 IoTClient开发1 - 你也可以写个聊天程序 IoTClient开发2 - 你也可以写个服务器 IoTClient开发3 - ModBusTcp协议客户端实现 IoTClient开发4 ...

  5. ModbusTCP协议

    简介 Modbus由MODICON公司于1979年开发,是一种工业现场总线协议标准.1996年施耐德公司推出基于以太网TCP/IP的Modbus协议:ModbusTCP. Modbus协议是一项应用层 ...

  6. 开源的C#实现WebSocket协议客户端和服务器websocket-sharp组件解析

    很久没有写博客了(至少自己感觉很长时间没有写了),没办法啊,楼主也是需要生活的人啊,这段一直都在找工作什么的.(整天催我代码的人,还望多多谅解啊,我会坚持写我们的项目的,还是需要相信我的,毕竟这是一个 ...

  7. Loadrunner 脚本开发-利用loadrunner开发Windows Sockets协议脚本

    脚本开发-利用loadrunner开发Windows Sockets协议脚本 by:授客 QQ:1033553122 欢迎加入软件性能测试交流QQ群:7156436 实践举例 Socket服务端简单实 ...

  8. C#实现WebSocket协议客户端和服务器websocket sharp组件实例解析

    看到这篇文章的题目,估计很多人都会问,这个组件是不是有些显的无聊了,说到web通信,很多人都会想到ASP.NET SignalR,或者Nodejs等等,实现web的网络实时通讯.有关于web实时通信的 ...

  9. 在Livemedia的基础上开发自己的流媒体客户端 V 0.01

    在Livemedia的基础上开发自己的流媒体客户端 V 0.01 桂堂东 xiaoguizi@gmail.com 2004-10 2004-12 友情申明: 本文档适合已经从事流媒体传输工作或者对网络 ...

随机推荐

  1. LINUX系统学习以及初学者系统下载

    Linux系统常用命令大全 来源:服务器之家 [博客中所有文章如有不对的地方希望看官们指出,有问题也可以提出来相互交流,相互学习,感谢大家!] 初学者建议安装:sentOS Ubuntu系统下载连接h ...

  2. Nginx+PHP7.3.9 Docker镜像制作

    最近因项目需要制作了多个版本的php docker镜像,制作过程可谓是一波三折,因基于yum的方式安装php的方式在安装扩展插件时很不方便,不容易找到插件对应的yum源,所以PHP在docker镜像中 ...

  3. 使用JavaScript·求数组的最大值和最小值

    前言  在数组中并没有提供arr.max()和arr.min()这样的方法.那么是不是可以通过别的方式实现类似这样的方法呢?那么今天我们就来整理取出数组中最大值和最小值的一些方法.   法一:其实利用 ...

  4. 理解 Redux 的中间件

    将该思想抽象出来,其实和 Redux 就无关了.问题变成,怎样实现在截获函数的执行,以在其执行前后添加自己的逻辑. 为了演示,我们准备如下的示例代码来模拟 Redux dispatch action ...

  5. VirtualBox 启动时提示“获取 VirtualBox COM 对象失败”的解决

    昨天给电脑打了一堆补丁和更新,今天启动 VirtualBox 的时候提示 “获取 VirtualBox COM 对象失败”,好在百度到了 CSDN 上的一篇文章解决了这个问题. 错误详情 “获取 Vi ...

  6. Windows和Mac系统下安装Docker

    在windows和mac系统中使用Docker Desktop安装Docker对系统的要求是很高的. 对于 Windows 系统来说,安装 Docker for Windows 需要符合以下条件: 必 ...

  7. 为了给女朋友买件心怡内衣,我用Python爬虫了天猫内衣售卖数据

    真爱,请置顶或星标 大家好,希望各位能怀着正直.严谨.专业的心态观看这篇文章.ヾ(๑╹◡╹)ノ" 接下来我们尝试用 Python 抓取天猫内衣销售数据,并分析得到中国女性普遍的罩杯数据.最受 ...

  8. 低效sql语句执行缓慢引起的大量占用服务器的CPU问题处理 (优化心得)

    1> 2> 3> 4> 5>删除不良的执行计划后执行时间仍然有150s,这实在是太慢了,继续查看原sql代码,发现父表的关联条件放在了子查询里,这是应该避免的 调整原sq ...

  9. 从零开始的vue学习笔记(七)

    前言 今天花一天时间阅读完vuex的官方文档,简单的做一下总结和记录 Vuex是什么 Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式,以前的符合"单向数据流"理念的 ...

  10. java之ReentrantLock详解

    前言 如果一个代码块被synchronized修饰了,当一个线程获取了相应的锁,并执行该代码块时,其他线程便只能一直等待,等待获取锁的释放,现在有这么一种情况,这个获取锁的线程由于要等待IO或者其他原 ...