ModbusRTU\TCP消息帧解析(C#实现报文发送与解析)

2023-11-29 06:20

本文主要是介绍ModbusRTU\TCP消息帧解析(C#实现报文发送与解析),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

  • 知识点
  • 常用链接
  • 一、Modbus
    • 1.ModbusRTU消息帧解析
    • 2.主站poll、从站slave通讯仿真-modbusRTU
      • 1.功能码=01读线圈状态
      • 2.功能码=03读保持寄存器
        • 报文解析(寄存器存整型)
        • 报文解析(寄存器存float)
    • 3.C#模拟主站Poll(ModbusRTU协议-组报文)
    • 4.NModbus4模拟主站poll(ModbusRTU协议)
    • 5.C#模拟主站Poll(ModbusTCP协议-组报文)
    • 6.NModbus4模拟从站slave(ModbusTCP协议)
    • 7.NModbus4模拟从站slave(ModbusRTU协议)
    • 8.modbusRTU、modbusTCP报文不同之处
  • 二、明文TCP

知识点

  1. PLC寄存器中存储(整型和无符号整型:2字节。长整型:4字节。单精度浮点数:4字节。双精度浮点数:8字节),我们只要知道数据类型,是2个字节一截取,还是4个字节 ,对接收到的报文进行字节截取然后编码成str就行
  2. 向PLC中写入Float,float占4个字节=2个寄存器,所以要使用功能码“写多寄存器0x10”, 功能码0x06只能写一个寄存器”
  3. serialPort.write(bytes,0,bytes.Length); thread.sleep(300); serialPort.Read() 发完指令后,要等待从站响应300ms,然后再去读数据
  4. 主站请求从站有两种方式:主动(手动点击查询线圈状态的按钮)被动(通过委托方式,一件事情的发生触发另外事件。场景:称菜,菜一放上去,触发去查询的功能代码块)
  5. 一个F要用4个二进制表示,两个F用8个二进制表示,所以 0xFA :表示1个字节
  6. modbusTCP响应 Tx:00 00 00 00 00 03 01 83 02 【83=1000 0011 (功能码03 的高位为1,就是异常)02是错误码代号要查表】
    在这里插入图片描述
  7. send()/recv()和write()/read():发送数据和接收数据 参考链接
  8. socket原理
  9. 不同协议图,
    在这里插入图片描述
  10. 比如omoronsocekt, modbustcp,他们都是用socket进行数据交互,只是他们在应用层采用不同的协议约定,对报文进行不同方式的解析;明文协议就是直接编码不组包,其他协议都是组包发出去(如明文协议,将字符串编码后直接send
    modbustcp协议,要组装发送报文为(从站地址+功能码+等等+字符串数据))

常用链接

虚拟串口调试工具 V6.9 汉化版免费版
在这里插入图片描述
串口、Modbus通信协议

一、Modbus

课程
文章介绍
一篇博客

1.ModbusRTU消息帧解析

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.主站poll、从站slave通讯仿真-modbusRTU

从站slave用于模拟PLC中的功能区,一个tab页表示一个功能模块(下图建了两个功能块)
主站poll发送请求,获取PLC中数据。
poll、slave都要设置connection、setup两个区域,只有参数配对了才能正常收发数据
在这里插入图片描述

1.功能码=01读线圈状态

在这里插入图片描述
在这里插入图片描述

报文都是16进制表示
16进制 0X01=0000 0001,一位16进制需要4位二进制表达(F =1111),两个16进制数字表示1个字节
线圈中数据要么是0,要么是1
读取长度:00 0A表示读取10个寄存器
响应字节数(单位是字节):02 表示两个字节,从02往后数两个字节都是数据未位
输出状态:A2 02 这是两字节,解析:先颠倒高低位 02 A2= 0000 0010 1010 0010 再反向读取数据0100 0101 0100 0000
在这里插入图片描述

2.功能码=03读保持寄存器

寄存器中数据可以是整数,浮点型 (整型和无符号整型:2字节。长整型:4字节。单精度浮点数:4字节。双精度浮点数:8字节)
在这里插入图片描述

报文解析(寄存器存整型)

读取长度:00 0A表示读取10个寄存器,1个寄存器是16位=2个字节,所以返回20个字节,一个整型=2字节,所以返回的是10个数据
响应字节数(单位是字节):14 表示20个字节
输出状态:007B 00 00 00 00 00 00 00 00 00 00 这是20个字节,解析: 第一个数为123
在这里插入图片描述

报文解析(寄存器存float)

读取长度:00 0A表示读取10个寄存器,1个寄存器是16位=2个字节,所以返回20个字节,一个float 占4字节,所以返回的是5个数据
响应字节数(单位是字节):14 表示20个字节
输出状态:解析: 42 0A 00 00 通过IEEE转换标准->第一个数为34.5
在这里插入图片描述

3.C#模拟主站Poll(ModbusRTU协议-组报文)

说明
1.下面代码模拟的是主站,需要开启小程序mbslave作为从站PLC
2.主站发起的功能码请求有:读线圈,读保持寄存器,写多个寄存器
3.主站发送报文,然后对响应报文按消息帧进行解析

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;using System;
using System.Collections.Generic;
using System.IO.Ports;
using System.Linq;
using System.Threading;namespace 通讯收发功能解析
{class Program{static void Main(string[] args){Console.WriteLine("Hello World!");//Test_0x01();Test_0x03();//Test_0x10();Console.ReadKey();}static void Test_0x01()/// 01功能码-读线圈状态测试,PLC中线圈全为0{ushort startAddr = 0;ushort readLen = 10;// 请求// byte[] 需要指定长度;不支持LinqList<byte> command = new List<byte>();command.Add(0x01);// 1号从站command.Add(0x01);// 功能码:读线圈状态// 起始地址command.Add(BitConverter.GetBytes(startAddr)[1]);command.Add(BitConverter.GetBytes(startAddr)[0]);// 读取数量command.Add(BitConverter.GetBytes(readLen)[1]);command.Add(BitConverter.GetBytes(readLen)[0]);// CRCcommand = CRC16(command);//command 为长度=8的字节{0x01 0x01 0x00 0x00 0x00 0x0A 0xBC 0x0D}// 以上报文组装完成// 发送-》SerialPortSerialPort serialPort = new SerialPort("COM1", 9600, Parity.None, 8, StopBits.One);// 打开串口serialPort.Open();//将command的第0位->command.count所有数据都发出去serialPort.Write(command.ToArray(), 0, command.Count);//发送报文为01 01 00 00 00 0A BC 0D   请求10个线圈的状态,响应时1个字节8位接收不够,所以两字节Thread.Sleep(5);//加上延时等待PLC反应时间// 进行响应报文的接收和解析 byte[] respBytes = new byte[serialPort.BytesToRead];//缓冲区信息serialPort.Read(respBytes, 0, respBytes.Length);// respBytes -> 01 01 02 00 00 B9 FC// 检查一个校验位//对报文进行解析数据List<byte> respList = new List<byte>(respBytes);respList.RemoveRange(0, 3);//00 00 B9 FCrespList.RemoveRange(respList.Count - 2, 2);//00 00 数据报文// 1。高低位切换// 2。从后往前读respList.Reverse();var respStrList = respList.Select(r => Convert.ToString(r, 2)).ToList();var values = string.Join("", respStrList).ToList();values.Reverse();values.ForEach(c => Console.WriteLine(Convert.ToBoolean(int.Parse(c.ToString()))));//Convert.ToBoolean('1');}static void Test_0x03()//读保持寄存器  PLC中第一个寄存器为123,其他=0{ushort startAddr = 0;ushort readLen = 10;// 请求// byte[] 需要指定长度;不支持LinqList<byte> command = new List<byte>();command.Add(0x01);// 1号从站command.Add(0x03);// 功能码:读保持型寄存器// 起始地址command.Add(BitConverter.GetBytes(startAddr)[1]);command.Add(BitConverter.GetBytes(startAddr)[0]);// 读取数量command.Add(BitConverter.GetBytes(readLen)[1]);command.Add(BitConverter.GetBytes(readLen)[0]);// CRCcommand = CRC16(command);// 报文组装完成// 发送-》SerialPortSerialPort serialPort = new SerialPort("COM1", 9600, Parity.None, 8, StopBits.One);// 打开串口serialPort.Open();serialPort.Write(command.ToArray(), 0, command.Count);// 进行响应报文的接收和解析byte[] respBytes = new byte[serialPort.BytesToRead];serialPort.Read(respBytes, 0, respBytes.Length);// respBytes -> 01 01 02 00 00 B9 FC// 检查一个校验位List<byte> respList = new List<byte>(respBytes);respList.RemoveRange(0, 3);respList.RemoveRange(respList.Count - 2, 2);// 拿到实际的数据部分,进行数据解析// 明确一点:读的是无符号单精度,占两个字节//byte[] data = new byte[2]; //for (int i = 0; i < readLen; i++)//{//    // 字节序问题    小端   大端//    data[0] = respList[i * 2 + 1];//    data[1] = respList[i * 2];//    // 根据此两个字节转换成想要的实际数字//    var value = BitConverter.ToUInt16(data, 0);//    Console.WriteLine(value);//}// 明确一点:读的是Float  占4个字节byte[] data = new byte[4];for (int i = 0; i < readLen / 2; i++){// 字节序问题    小端   大端data[0] = respList[i * 4 + 3];data[1] = respList[i * 4 + 2];data[2] = respList[i * 4 + 1];data[3] = respList[i * 4];// 根据此两个字节转换成想要的实际数字var value = BitConverter.ToSingle(data, 0);Console.WriteLine(value);}}//向PLC中写入Float,float占4个字节=2个寄存器,所以要使用功能码“写多寄存器0x10”, 功能码0x06只能写一个寄存器”, static void Test_0x10()//写多个寄存器功能码0x10{ushort startAddr = 2;ushort writeLen = 4;float[] values = new float[] { 123.45f, 14.3f };// 请求// byte[] 需要指定长度;不支持LinqList<byte> command = new List<byte>();command.Add(0x01);// 1号从站command.Add(0x10);// 功能码:写多个保持型寄存器// 写入地址command.Add(BitConverter.GetBytes(startAddr)[1]);command.Add(BitConverter.GetBytes(startAddr)[0]);// 写入数量command.Add(BitConverter.GetBytes(writeLen)[1]);command.Add(BitConverter.GetBytes(writeLen)[0]);// 获取数值的byte[]List<byte> valueBytes = new List<byte>();for (int i = 0; i < values.Length; i++){List<byte> temp = new List<byte>(BitConverter.GetBytes(values[i]));temp.Reverse();// 调整字节序valueBytes.AddRange(temp);}// 字节数command.Add((byte)valueBytes.Count);command.AddRange(valueBytes);// CRCcommand = CRC16(command);// 报文组装完成// 发送-》SerialPortSerialPort serialPort = new SerialPort("COM1", 9600, Parity.None, 8, StopBits.One);// 打开串口serialPort.Open();serialPort.Write(command.ToArray(), 0, command.Count);}static List<byte> CRC16(List<byte> value, ushort poly = 0xA001, ushort crcInit = 0xFFFF){if (value == null || !value.Any())throw new ArgumentException("");//运算ushort crc = crcInit;for (int i = 0; i < value.Count; i++){crc = (ushort)(crc ^ (value[i]));for (int j = 0; j < 8; j++){crc = (crc & 1) != 0 ? (ushort)((crc >> 1) ^ poly) : (ushort)(crc >> 1);}}byte hi = (byte)((crc & 0xFF00) >> 8);  //高位置byte lo = (byte)(crc & 0x00FF);         //低位置List<byte> buffer = new List<byte>();buffer.AddRange(value);buffer.Add(lo);buffer.Add(hi);return buffer;}}
}

4.NModbus4模拟主站poll(ModbusRTU协议)

在这里插入图片描述
ReadHoldingRegisters(1, 0, 1)# 参数:从站地址,起始地址,读取数量
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

5.C#模拟主站Poll(ModbusTCP协议-组报文)

课程视频P17-P14

03读保持寄存器报文
在这里插入图片描述
在这里插入图片描述

说明
1.下面代码模拟的是modbusTCP主站,需要开启小程序mbslave作为从站PLC,要设置slave的connection、setup两个区域的TCP相关参数
2.主站发起的功能码请求有:ReadHoldingRegister,ReadInputRegister
3.主站发送报文,然后对响应报文按消息帧进行解析
在这里插入图片描述

using System;
using System.Collections.Generic;
using System.IO.Ports;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;namespace 通讯收发功能解析
{public class ModbusTcp: ModbusBase{Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream,ProtocolType.Tcp);public Result connect(string host, int port){Result result = new Result();try{socket.Connect(host, port);result.State = true;}catch (Exception ex){result.State = false;result.Exception = ex.Message;}return result;}// 读保持型寄存器03// ModbusRTU:0x01(从站地址) 03 (功能码)  0x00 0x0A(起始地址)  0x00 0x05(读取长度)   0xXX 0xXX(CRC16校验码)// ModbusTCP:请求报文:0x00 0x00(TransationID 最大65535)  0x00 0x00 (Modbus协议标识)   0x00 0x06(后续字节数)   =>  0x01 (单元标识) 0x03  0x0 0x0A 0x00 0x05// 响应报文:                   0x00 0x00(TransationID 最大65535)   0x00 0x00 (Modbus协议标识)   0x00 0x0D(后续字节数)   =>  0x01 (单元标识) 0x03(功能码)=>//                                       0x0A(返回10个字节数据) 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00/// <summary>////// </summary>/// <param name="unit_id">从站地址</param>/// <param name="start_addr">寄存器起始地址</param>/// <param name="count">读寄存器数量</param>public Result  ReadHoldingRegister(byte unit_id , ushort start_addr,ushort count){Result result = new Result();try {ushort tid = 0;//报文组装byte[] req_bytes = new byte[]{(byte)(tid/256),(byte)(tid%256),0x00, 0x00,0x00, 0x06,unit_id,0x03,(byte)(start_addr/256),(byte)(start_addr%256),//10进制转成16(byte)(count/256),(byte)(count%256),};tid++;tid %= 65535;//var req_bytes = this.ReadCommandBytes( unit_id,  0x03,start_addr,  count);//发送请求socket.Send(req_bytes);//接收响应byte[] resp_bytes = new byte[6];//由于plc返回的响应字数长度是不一样的,先取前6个字节socket.Receive(resp_bytes, 0 ,6 ,SocketFlags.None);var len_bytes = resp_bytes.ToList().GetRange(4, 2);ushort len = (ushort)(len_bytes[4] * 256 + len_bytes[5]);//解析报文中返回的有多少个字节数resp_bytes = new byte[len];//这个resp_bytes len 表明数据中有多少个字节数据是有用的数据报文socket.Receive(resp_bytes, 0, len, SocketFlags.None);//上面从缓存区拿走了6个字节,现在把剩余的都拿走//检查响应报文是否正常,功能码的高位为1,就是异常//0x83 1000 0011if (resp_bytes[1] > 0x80){//说明响应的是异常报文// 返回异常信息 根据resp_bytes[2]字节进行异常的关联throw new Exception("错误了");}//提取PLC中寄存器中的数据部分报文var data_bytes = resp_bytes.ToList().GetRange(3, resp_bytes[2]).ToArray();result.State = true;result.Datas = data_bytes;}catch(Exception ex){result.State = false;result.Exception = ex.Message;}return result;}public Result ReadInputRegister(byte unit_id, ushort start_addr, ushort count){Result result = new Result();try{ushort tid = 0;//报文组装byte[] req_bytes = new byte[]{(byte)(tid/256),(byte)(tid%256),0x00, 0x00,0x00, 0x06,unit_id,0x04,(byte)(start_addr/256),(byte)(start_addr%256),//10进制转成16(byte)(count/256),(byte)(count%256),};tid++;tid %= 65535;//var req_bytes = this.ReadCommandBytes(unit_id, 0x04, start_addr, count);//发送请求socket.Send(req_bytes);//接收响应byte[] resp_bytes = new byte[6];//由于plc返回的响应字数长度是不一样的,先取前6个字节socket.Receive(resp_bytes, 0, 6, SocketFlags.None);var len_bytes = resp_bytes.ToList().GetRange(4, 2);ushort len = (ushort)(len_bytes[4] * 256 + len_bytes[5]);//解析报文中返回的有多少个字节数resp_bytes = new byte[len];//这个resp_bytes len 表明数据中有多少个字节数据是有用的数据报文socket.Receive(resp_bytes, 0, len, SocketFlags.None);//上面从缓存区拿走了6个字节,现在把剩余的都拿走//检查响应报文是否正常,功能码的高位为1,就是异常//0x83 1000 0011if (resp_bytes[1] > 0x80){//说明响应的是异常报文// 返回异常信息 根据resp_bytes[2]字节进行异常的关联throw new Exception("错误了");}//解析PLC中寄存器中的数据var data_bytes = resp_bytes.ToList().GetRange(3, resp_bytes[2]).ToArray();result.State = true;result.Datas = data_bytes;}catch (Exception ex){result.State = false;result.Exception = ex.Message;}return result;}public void write(){}public T[] Getvalues<T>(byte[] data_bytes)//解析报文{var type_len = Marshal.SizeOf(typeof(T));//检查类型的长度 , int32是4字节,float是4字节for (var i =0; i<data_bytes.Length;i+=type_len){//根据数据类型将报文切割,获取一个类型的字节内容,并将字节转换成对应 的strvar temp_bytes = data_bytes.ToList().GetRange(i, type_len);temp_bytes.Reverse();//字序short v = BitConverter.ToInt16(temp_bytes.ToArray(), 0);ushort vv = BitConverter.ToUInt16(temp_bytes.ToArray(), 0);float vvv = BitConverter.ToSingle(temp_bytes.ToArray(),0);}return null;}}
}

6.NModbus4模拟从站slave(ModbusTCP协议)

用小工具poll连接自己建立的从站,可读取从站的值
文章

using System.Threading;
using System.Net.Sockets;
using System.Net;
using Modbus.Data;
using Modbus.Device;public class slave{/// <summary>/// 服务器提供的数据区/// </summary>public  DataStore Data=DataStoreFactory.CreateDefaultDataStore(); //初始化服务数据区;/// <summary>/// Modbus服务器/// </summary>public  ModbusSlave modbus_tcp_server;public  void modbustcpslave(){modbus_tcp_server = ModbusTcpSlave.CreateTcp(1, new TcpListener(IPAddress.Parse("127.0.0.1"), 502)); //创建ModbusTcp服务器modbus_tcp_server.DataStore = Data;//数据区赋值Thread th_0 = new Thread(() =>{modbus_tcp_server.Listen();//异步 非阻塞 启动服务}){IsBackground = true,};th_0.SetApartmentState(ApartmentState.STA);th_0.Start();Thread th_1 = new Thread(() =>{SetData(); //数据区数据赋值}){IsBackground = true,};th_1.SetApartmentState(ApartmentState.STA);th_1.Start();}/// <summary>/// 设置数据/// </summary>public  void SetData() //static修饰的函数或变量都是在类初始化的时候加载的,而非静态的变量都是在对象初始化的时候加载。{while (true){Data.InputRegisters[1] = (ushort)DateTime.Now.Year;         //年Data.InputRegisters[2] = (ushort)DateTime.Now.Month;        //月Data.InputRegisters[3] = (ushort)DateTime.Now.Day;          //日Data.InputRegisters[4] = (ushort)DateTime.Now.Hour;         //时Data.InputRegisters[5] = (ushort)DateTime.Now.Minute;       //分Data.InputRegisters[6] = (ushort)DateTime.Now.Second;       //秒Data.InputRegisters[7] = (ushort)DateTime.Now.Millisecond;  //毫秒Random ran = new Random();Data.InputRegisters[8] = (ushort)ran.Next(0, 32767);        //产生的随机数}}
}

7.NModbus4模拟从站slave(ModbusRTU协议)

文章

public class slave_RTU{public ModbusSlave modbus_rtu_server;public void create(){SerialPort slavePort = new SerialPort();slavePort.PortName = "COM1";slavePort.BaudRate = 9600;slavePort.DataBits = 8;slavePort.Parity = Parity.Even;slavePort.StopBits = StopBits.One;slavePort.Open();byte slaveID =1;modbus_rtu_server = ModbusSerialSlave.CreateRtu(slaveID, slavePort);modbus_rtu_server.ModbusSlaveRequestReceived += new EventHandler<ModbusSlaveRequestEventArgs>(Modbus_Request_Event);modbus_rtu_server.DataStore = Modbus.Data.DataStoreFactory.CreateDefaultDataStore();modbus_rtu_server.DataStore.DataStoreWrittenTo += new EventHandler<DataStoreEventArgs>(Modbus_DataStoreWriteTo);modbus_rtu_server.DataStore.InputRegisters[1] = (ushort)DateTime.Now.Year;modbus_rtu_server.DataStore.InputRegisters[2] = (ushort)DateTime.Now.Year;modbus_rtu_server.DataStore.InputRegisters[3] = (ushort)DateTime.Now.Year;modbus_rtu_server.DataStore.CoilDiscretes[1] = true;modbus_rtu_server.DataStore.CoilDiscretes[2] = false;modbus_rtu_server.DataStore.CoilDiscretes[3] = false;modbus_rtu_server.Listen();}private void Modbus_Request_Event(object sender, Modbus.Device.ModbusSlaveRequestEventArgs e){try{//request from masterbyte fc = e.Message.FunctionCode;byte[] data = e.Message.MessageFrame;byte[] byteStartAddress = new byte[] { data[3], data[2] };byte[] byteNum = new byte[] { data[5], data[4] };Int16 StartAddress = BitConverter.ToInt16(byteStartAddress, 0);Int16 NumOfPoint = BitConverter.ToInt16(byteNum, 0);bool BOOL = true;string FCNUM = fc.ToString();if (fc.ToString() == "6"){//AOmodbus_rtu_server.DataStore.HoldingRegisters[StartAddress] = 16;modbus_rtu_server.DataStore.HoldingRegisters[StartAddress + 1] = 17;}Console.WriteLine(fc.ToString() + "," + StartAddress.ToString() + "," + NumOfPoint.ToString());}catch (Exception exc){}}private void Modbus_DataStoreWriteTo(object sender, Modbus.Data.DataStoreEventArgs e){//this.Text = "DataType=" + e.ModbusDataType.ToString() + "  StartAdress=" + e.StartAddress;int iAddress = e.StartAddress;//e.StartAddress;switch (e.ModbusDataType){case ModbusDataType.HoldingRegister:for (int i = 0; i < e.Data.B.Count; i++){//Set AO                 modbus_rtu_server.DataStore.HoldingRegisters[e.StartAddress + i + 1] = e.Data.B[i];//e.Data.B[i] already write to slave.DataStore.HoldingRegisters[e.StartAddress + i + 1]//e.StartAddress starts from 0//You can set AO value to hardware here//DoAOUpdate(iAddress, e.Data.B[i].ToString());iAddress++;}break;case ModbusDataType.Coil:for (int i = 0; i < e.Data.A.Count; i++){//Set DOmodbus_rtu_server.DataStore.CoilDiscretes[e.StartAddress + i + 1] = e.Data.A[i];//e.Data.A[i] already write to slave.DataStore.CoilDiscretes[e.StartAddress + i + 1]//e.StartAddress starts from 0//You can set DO value to hardware here//DoDOUpdate(iAddress, e.Data.A[i]);iAddress++;if (e.Data.A.Count == 1){break;}}break;}}}

8.modbusRTU、modbusTCP报文不同之处

在这里插入图片描述

二、明文TCP

博客
视频
在这里插入图片描述
在这里插入图片描述

using System.Net;//创建Socket套接字Socket server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);IPEndPoint point = new IPEndPoint(IPAddress.Parse(textBox1.Text), int.Parse(textBox2.Text));try { server.Bind(point); }catch (Exception ex){MessageBox.Show("无法启动服务器");}server.Listen(3);//Socket Client = server.Accept();//Accept 抓取的连接请求是客户端发来的string client = Client.RemoteEndPoint.ToString();MessageBox.Show(client+"连接了服务器");byte[] b = new byte[1024 * 1024 * 2];//缓冲器int length = 0;try{length = Client.Receive(b);}catch{MessageBox.Show(client + "失去连接");}if (length > 0){string msg = Encoding.Default.GetString(b, 0, length);Client.Send(Encoding.Default.GetBytes(textBox3.Text));}else{MessageBox.Show(client + "失去连接");}

这篇关于ModbusRTU\TCP消息帧解析(C#实现报文发送与解析)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/431778

相关文章

C++中unordered_set哈希集合的实现

《C++中unordered_set哈希集合的实现》std::unordered_set是C++标准库中的无序关联容器,基于哈希表实现,具有元素唯一性和无序性特点,本文就来详细的介绍一下unorder... 目录一、概述二、头文件与命名空间三、常用方法与示例1. 构造与析构2. 迭代器与遍历3. 容量相关4

Java中Redisson 的原理深度解析

《Java中Redisson的原理深度解析》Redisson是一个高性能的Redis客户端,它通过将Redis数据结构映射为Java对象和分布式对象,实现了在Java应用中方便地使用Redis,本文... 目录前言一、核心设计理念二、核心架构与通信层1. 基于 Netty 的异步非阻塞通信2. 编解码器三、

C++中悬垂引用(Dangling Reference) 的实现

《C++中悬垂引用(DanglingReference)的实现》C++中的悬垂引用指引用绑定的对象被销毁后引用仍存在的情况,会导致访问无效内存,下面就来详细的介绍一下产生的原因以及如何避免,感兴趣... 目录悬垂引用的产生原因1. 引用绑定到局部变量,变量超出作用域后销毁2. 引用绑定到动态分配的对象,对象

SpringBoot基于注解实现数据库字段回填的完整方案

《SpringBoot基于注解实现数据库字段回填的完整方案》这篇文章主要为大家详细介绍了SpringBoot如何基于注解实现数据库字段回填的相关方法,文中的示例代码讲解详细,感兴趣的小伙伴可以了解... 目录数据库表pom.XMLRelationFieldRelationFieldMapping基础的一些代

Java HashMap的底层实现原理深度解析

《JavaHashMap的底层实现原理深度解析》HashMap基于数组+链表+红黑树结构,通过哈希算法和扩容机制优化性能,负载因子与树化阈值平衡效率,是Java开发必备的高效数据结构,本文给大家介绍... 目录一、概述:HashMap的宏观结构二、核心数据结构解析1. 数组(桶数组)2. 链表节点(Node

Java AOP面向切面编程的概念和实现方式

《JavaAOP面向切面编程的概念和实现方式》AOP是面向切面编程,通过动态代理将横切关注点(如日志、事务)与核心业务逻辑分离,提升代码复用性和可维护性,本文给大家介绍JavaAOP面向切面编程的概... 目录一、AOP 是什么?二、AOP 的核心概念与实现方式核心概念实现方式三、Spring AOP 的关

Java 虚拟线程的创建与使用深度解析

《Java虚拟线程的创建与使用深度解析》虚拟线程是Java19中以预览特性形式引入,Java21起正式发布的轻量级线程,本文给大家介绍Java虚拟线程的创建与使用,感兴趣的朋友一起看看吧... 目录一、虚拟线程简介1.1 什么是虚拟线程?1.2 为什么需要虚拟线程?二、虚拟线程与平台线程对比代码对比示例:三

一文解析C#中的StringSplitOptions枚举

《一文解析C#中的StringSplitOptions枚举》StringSplitOptions是C#中的一个枚举类型,用于控制string.Split()方法分割字符串时的行为,核心作用是处理分割后... 目录C#的StringSplitOptions枚举1.StringSplitOptions枚举的常用

Python函数作用域与闭包举例深度解析

《Python函数作用域与闭包举例深度解析》Python函数的作用域规则和闭包是编程中的关键概念,它们决定了变量的访问和生命周期,:本文主要介绍Python函数作用域与闭包的相关资料,文中通过代码... 目录1. 基础作用域访问示例1:访问全局变量示例2:访问外层函数变量2. 闭包基础示例3:简单闭包示例4

Python实现字典转字符串的五种方法

《Python实现字典转字符串的五种方法》本文介绍了在Python中如何将字典数据结构转换为字符串格式的多种方法,首先可以通过内置的str()函数进行简单转换;其次利用ison.dumps()函数能够... 目录1、使用json模块的dumps方法:2、使用str方法:3、使用循环和字符串拼接:4、使用字符