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

相关文章

Django开发时如何避免频繁发送短信验证码(python图文代码)

《Django开发时如何避免频繁发送短信验证码(python图文代码)》Django开发时,为防止频繁发送验证码,后端需用Redis限制请求频率,结合管道技术提升效率,通过生产者消费者模式解耦业务逻辑... 目录避免频繁发送 验证码1. www.chinasem.cn避免频繁发送 验证码逻辑分析2. 避免频繁

分布式锁在Spring Boot应用中的实现过程

《分布式锁在SpringBoot应用中的实现过程》文章介绍在SpringBoot中通过自定义Lock注解、LockAspect切面和RedisLockUtils工具类实现分布式锁,确保多实例并发操作... 目录Lock注解LockASPect切面RedisLockUtils工具类总结在现代微服务架构中,分布

Java使用Thumbnailator库实现图片处理与压缩功能

《Java使用Thumbnailator库实现图片处理与压缩功能》Thumbnailator是高性能Java图像处理库,支持缩放、旋转、水印添加、裁剪及格式转换,提供易用API和性能优化,适合Web应... 目录1. 图片处理库Thumbnailator介绍2. 基本和指定大小图片缩放功能2.1 图片缩放的

Python使用Tenacity一行代码实现自动重试详解

《Python使用Tenacity一行代码实现自动重试详解》tenacity是一个专为Python设计的通用重试库,它的核心理念就是用简单、清晰的方式,为任何可能失败的操作添加重试能力,下面我们就来看... 目录一切始于一个简单的 API 调用Tenacity 入门:一行代码实现优雅重试精细控制:让重试按我

深度解析Spring Security 中的 SecurityFilterChain核心功能

《深度解析SpringSecurity中的SecurityFilterChain核心功能》SecurityFilterChain通过组件化配置、类型安全路径匹配、多链协同三大特性,重构了Spri... 目录Spring Security 中的SecurityFilterChain深度解析一、Security

Redis客户端连接机制的实现方案

《Redis客户端连接机制的实现方案》本文主要介绍了Redis客户端连接机制的实现方案,包括事件驱动模型、非阻塞I/O处理、连接池应用及配置优化,具有一定的参考价值,感兴趣的可以了解一下... 目录1. Redis连接模型概述2. 连接建立过程详解2.1 连php接初始化流程2.2 关键配置参数3. 最大连

Python实现网格交易策略的过程

《Python实现网格交易策略的过程》本文讲解Python网格交易策略,利用ccxt获取加密货币数据及backtrader回测,通过设定网格节点,低买高卖获利,适合震荡行情,下面跟我一起看看我们的第一... 网格交易是一种经典的量化交易策略,其核心思想是在价格上下预设多个“网格”,当价格触发特定网格时执行买

全面解析Golang 中的 Gorilla CORS 中间件正确用法

《全面解析Golang中的GorillaCORS中间件正确用法》Golang中使用gorilla/mux路由器配合rs/cors中间件库可以优雅地解决这个问题,然而,很多人刚开始使用时会遇到配... 目录如何让 golang 中的 Gorilla CORS 中间件正确工作一、基础依赖二、错误用法(很多人一开

python设置环境变量路径实现过程

《python设置环境变量路径实现过程》本文介绍设置Python路径的多种方法:临时设置(Windows用`set`,Linux/macOS用`export`)、永久设置(系统属性或shell配置文件... 目录设置python路径的方法临时设置环境变量(适用于当前会话)永久设置环境变量(Windows系统

python运用requests模拟浏览器发送请求过程

《python运用requests模拟浏览器发送请求过程》模拟浏览器请求可选用requests处理静态内容,selenium应对动态页面,playwright支持高级自动化,设置代理和超时参数,根据需... 目录使用requests库模拟浏览器请求使用selenium自动化浏览器操作使用playwright