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

相关文章

SpringBoot集成redisson实现延时队列教程

《SpringBoot集成redisson实现延时队列教程》文章介绍了使用Redisson实现延迟队列的完整步骤,包括依赖导入、Redis配置、工具类封装、业务枚举定义、执行器实现、Bean创建、消费... 目录1、先给项目导入Redisson依赖2、配置redis3、创建 RedissonConfig 配

线上Java OOM问题定位与解决方案超详细解析

《线上JavaOOM问题定位与解决方案超详细解析》OOM是JVM抛出的错误,表示内存分配失败,:本文主要介绍线上JavaOOM问题定位与解决方案的相关资料,文中通过代码介绍的非常详细,需要的朋... 目录一、OOM问题核心认知1.1 OOM定义与技术定位1.2 OOM常见类型及技术特征二、OOM问题定位工具

Python的Darts库实现时间序列预测

《Python的Darts库实现时间序列预测》Darts一个集统计、机器学习与深度学习模型于一体的Python时间序列预测库,本文主要介绍了Python的Darts库实现时间序列预测,感兴趣的可以了解... 目录目录一、什么是 Darts?二、安装与基本配置安装 Darts导入基础模块三、时间序列数据结构与

Python使用FastAPI实现大文件分片上传与断点续传功能

《Python使用FastAPI实现大文件分片上传与断点续传功能》大文件直传常遇到超时、网络抖动失败、失败后只能重传的问题,分片上传+断点续传可以把大文件拆成若干小块逐个上传,并在中断后从已完成分片继... 目录一、接口设计二、服务端实现(FastAPI)2.1 运行环境2.2 目录结构建议2.3 serv

C#实现千万数据秒级导入的代码

《C#实现千万数据秒级导入的代码》在实际开发中excel导入很常见,现代社会中很容易遇到大数据处理业务,所以本文我就给大家分享一下千万数据秒级导入怎么实现,文中有详细的代码示例供大家参考,需要的朋友可... 目录前言一、数据存储二、处理逻辑优化前代码处理逻辑优化后的代码总结前言在实际开发中excel导入很

SpringBoot+RustFS 实现文件切片极速上传的实例代码

《SpringBoot+RustFS实现文件切片极速上传的实例代码》本文介绍利用SpringBoot和RustFS构建高性能文件切片上传系统,实现大文件秒传、断点续传和分片上传等功能,具有一定的参考... 目录一、为什么选择 RustFS + SpringBoot?二、环境准备与部署2.1 安装 RustF

Nginx部署HTTP/3的实现步骤

《Nginx部署HTTP/3的实现步骤》本文介绍了在Nginx中部署HTTP/3的详细步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学... 目录前提条件第一步:安装必要的依赖库第二步:获取并构建 BoringSSL第三步:获取 Nginx

MyBatis Plus实现时间字段自动填充的完整方案

《MyBatisPlus实现时间字段自动填充的完整方案》在日常开发中,我们经常需要记录数据的创建时间和更新时间,传统的做法是在每次插入或更新操作时手动设置这些时间字段,这种方式不仅繁琐,还容易遗漏,... 目录前言解决目标技术栈实现步骤1. 实体类注解配置2. 创建元数据处理器3. 服务层代码优化填充机制详

Python实现Excel批量样式修改器(附完整代码)

《Python实现Excel批量样式修改器(附完整代码)》这篇文章主要为大家详细介绍了如何使用Python实现一个Excel批量样式修改器,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一... 目录前言功能特性核心功能界面特性系统要求安装说明使用指南基本操作流程高级功能技术实现核心技术栈关键函

Java实现字节字符转bcd编码

《Java实现字节字符转bcd编码》BCD是一种将十进制数字编码为二进制的表示方式,常用于数字显示和存储,本文将介绍如何在Java中实现字节字符转BCD码的过程,需要的小伙伴可以了解下... 目录前言BCD码是什么Java实现字节转bcd编码方法补充总结前言BCD码(Binary-Coded Decima