进过前面两章的介绍,今天开始正式的实战。html
不少朋友对于进制转换多是在刚学计算机的时候有接触,后来作高级语言开发可能就慢慢忘记了。咱们作工控开发的时候须要常常进行进制转换,这里和你们一块儿复习下。
一个字节等8位(1byte = 8bit),能够存储2^8(0-255)共计256个数字。因此咱们要对八、256等数字要敏感。
int16(short), int32(int), int64(long) 分别是占用2个字节、4个字节、8个字节,Single(float)也是占用4个字节。git
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 0110github
咱们用二进制 0101 0110来演示,也就是上面十进制的86。
数组
固然,你最好用计算器验证下
并发
咱们在对进制转换进行复习事后,接下来说ModBusTcp协议。
ModBus协议是如今工控里面用的比较多比较通用的一种协议,什么可靠啊、简单啊等等一些优势就不说了,直接入正题。
ModBus分为RTU、ASCII、TCP三种方式进行通讯,今天咱们只讲TCP。
在ModBus里面有站号、功能码、寄存器地址等概念。socket
01:读线圈 02:读离散量 03:读保持寄存器(每一个寄存器含有两个字节) 04:读输入寄存器 05:写单个线圈 06:写单个寄存器 15:用于写多个线圈 16:写多个寄存器
协议的理解和实现主要就是要对协议报文理解。(注意:如下报文数据都是十六进制)tcp
第一次获取前八个字节(Map报文头):19 B2 00 00 00 05 02 03 02 00 20ui
第二次获取的报文:02 00 20code
和请求报文的区别htm
有了上面的三个报文作参考,咱们就能够用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 获取命令[组装请求报文](寄存器起始地址:四、站号:二、功能码:三、读取寄存器长度: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();
对于数据写入就更简单了。
第一步,组装请求报文
/// <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();