進過前面兩章的介紹,今天開始正式的實戰。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();