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

news2024/11/25 11:39:14

目录

  • 知识点
  • 常用链接
  • 一、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[] 需要指定长度;不支持Linq
            List<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]);

            // CRC
            command = CRC16(command);//command 为长度=8的字节{0x01 0x01 0x00 0x00 0x00 0x0A 0xBC 0x0D}
            // 以上报文组装完成

            // 发送-》SerialPort
            SerialPort 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 FC
            respList.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[] 需要指定长度;不支持Linq
            List<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]);

            // CRC
            command = CRC16(command);

            // 报文组装完成
            // 发送-》SerialPort
            SerialPort 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[] 需要指定长度;不支持Linq
            List<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);

            // CRC
            command = CRC16(command);

            // 报文组装完成
            // 发送-》SerialPort
            SerialPort 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 0011
            if (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 0011
                if (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)
            {
                //根据数据类型将报文切割,获取一个类型的字节内容,并将字节转换成对应 的str
                var 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 master
                byte 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")
                {
                    //AO
                    modbus_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 DO
                        modbus_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 + "失去连接");
            }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1265271.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

P24 C++ 字符串常量

前言 本期我们讨论字符串字面量。 这是一种基于字符串的东西&#xff0c;上一章我们讲过字符串&#xff0c;你一定要去先去看看那一期的内容。 P23 C字符串-CSDN博客 01 什么是字符串字常量呢&#xff1f; 字符串字面量就是在双引号之间的一串字符 在上面的代码中&#xf…

Linux CentOS_7解决无法上网的问题

参考视频&#xff1a;保姆式教学虚拟机联网liunx(centos)_哔哩哔哩_bilibili 配置网络&#xff1a;解决上网问题 第一步&#xff1a;选择网络模式 第二步&#xff1a;配置网卡命令&#xff1a;打开终端执行命令&#xff1a; 1、先切换到根目录下&#xff0c;防止在第执行cd …

计网Lesson4 - 计算机组网模型

文章目录 计算机的连接方式1. 两台计算机的互联2. 多台计算机的互联&#xff08;旧式&#xff09;3. 多台计算机的互联 --- 集线器&#xff08;Hub&#xff09;4. 网桥5. 多台计算机的互联 --- 交换器&#xff08;Switch&#xff09; 计算机的连接方式 1. 两台计算机的互联 网…

什么是路由抖动?该如何控制

路由器在实现不间断的网络通信和连接方面发挥着重要作用&#xff0c;具有所需功能的持续可用的路由器可确保其相关子网的良好性能&#xff0c;由于网络严重依赖路由器的性能&#xff0c;因此确保您的路由器不会遇到任何问题非常重要。路由器遇到的一个严重的网络问题是路由抖动…

如何使用APP UI自动化测试提高测试效率与质量?

pythonappium自动化测试系列就要告一段落了&#xff0c;本篇博客咱们做个小结。 首先想要说明一下&#xff0c;APP自动化测试可能很多公司不用&#xff0c;但也是大部分自动化测试工程师、高级测试工程师岗位招聘信息上要求的&#xff0c;所以为了更好的待遇&#xff0c;我们还…

第15关 K8s HPA:自动水平伸缩Pod,实现弹性扩展和资源优化

------> 课程视频同步分享在今日头条和B站 大家好&#xff0c;我是博哥爱运维&#xff0c;这节课带来k8s的HPA 自动水平伸缩pod&#xff08; 视频后面有彩蛋 : ) &#xff09;。 我们知道&#xff0c;初始Pod的数量是可以设置的&#xff0c;同时业务也分流量高峰和低峰&a…

Java 8 中 ReentrantLock 与 Synchronized 的区别

&#x1f680; 作者主页&#xff1a; 有来技术 &#x1f525; 开源项目&#xff1a; youlai-mall &#x1f343; vue3-element-admin &#x1f343; youlai-boot &#x1f33a; 仓库主页&#xff1a; Gitee &#x1f4ab; Github &#x1f4ab; GitCode &#x1f496; 欢迎点赞…

【安装指南】MySQL和Navicat下载、安装及使用详细教程

目录 ⛳️1.【MySQL】安装教程 1.1 获取下载包 1.2 MySQL安装 1.2.1 MySQL工具安装 1.2.2 MySQL环境变量 1.2.3 验证MySQL安装成功 ⛳️2.【Navicat-v15】的安装和无限使用 ⛳️3.【测试Navicat连接MySQL】 ⛳️1.【MySQL】安装教程 1.1 获取下载包 前往官网获取压缩包…

C#Backgroundworker与Thread的区别

前言 当谈到多线程编程时&#xff0c;C#中的BackgroundWorker和Thread是两个常见的选择。它们都可以用于实现并行处理和异步操作&#xff0c;但在某些方面有一些重要的区别。本文将详细解释BackgroundWorker和Thread之间的区别以及它们在不同场景中的使用。 目录 前言1. Backgr…

蓝桥杯每日一题2023.11.28

题目描述 三羊献瑞 - 蓝桥云课 (lanqiao.cn) 题目分析 本题首先进行观察可以确定 1.“三”为 1 &#xff08;十进制数字要进位进一位&#xff09; 2.“祥”一定不为 0 &#xff08;有前导0就不能算为 4 位数&#xff09; 使用搜索时将其特判 #include<bits/stdc.h> …

商用车的智慧眼车规级激光雷达

1、商用车自动驾驶技术&#xff1a;巨大的降本增效空间 2、感知是第一步&#xff1a;看懂环境路况才能安全的自动驾驶 3、多传感器融合&#xff0c;感知信息冗余&#xff0c;保障自动驾驶安全 4、商用车需要什么样的激光雷达 5、车规级激光雷达的软硬件成熟度及延展性 &#x…

Joint Bilateral Upsampling

Abstract 图像分析和增强任务&#xff08;例如色调映射、着色、立体深度和蒙太奇&#xff09;通常需要在像素网格上计算解决方案&#xff08;例如&#xff0c;曝光、色度、视差、标签&#xff09;。计算和内存成本通常要求在下采样图像上运行较小的解决方案。尽管通用上采样方…

leetcode:455. 分发饼干

一、题目 原题链接&#xff1a;455. 分发饼干 - 力扣&#xff08;LeetCode&#xff09; 函数原型&#xff1a;int findContentChildren(int* g, int gSize, int* s, int sSize) 二、思路 将胃口数组和饼干尺寸数组降序排序 遍历两个数组&#xff0c;从胃口数组中找到符合饼干尺…

【古月居《ros入门21讲》学习笔记】15_ROS中的坐标系管理系统

目录 说明&#xff1a; 1. 机器人中的坐标变换 tf功能包能干什么&#xff1f; tf坐标变换如何实现 2. 小海龟跟随实验 安装 ros-melodic-turtle-tf 实验命令 运行效果 说明&#xff1a; 1. 本系列学习笔记基于B站&#xff1a;古月居《ROS入门21讲》课程&#xff0c;且使…

Kafka事务机制:原理和实践

Kafka事务机制&#xff1a;原理和实践 Apache Kafka 是一个分布式流处理平台&#xff0c;广泛用于构建实时数据管道和流应用程序。它不仅以高吞吐量、可扩展性和容错能力著称&#xff0c;还提供了事务支持&#xff0c;以确保数据的完整性和一致性。在这篇博客中&#xff0c;我…

51单片机使用串口查看程序执行的数据

51单片机使用串口查看程序执行的数据 1.概述 这篇文章介绍利用串口输出程序执行的数据&#xff0c;辅助我们调试程序&#xff0c;提高代码定位问题的效率。 2.硬件电路原理 3.串口助手查看程序数据 输出串口数据的方式分为CPU查询方式和中断方式。他们各有优缺点&#xff0…

C语言 移位操作符

<< 左移操作符>> 右移操作符 注&#xff1a;移位操作符的操作数只能是整数。 移位操作符移动的是二进制位。 整数的二进制表示有3种&#xff1a; 原码反码补码 正的整数的原码、反码、补码相同。 负的整数的原码、反码、补码是要计算的。 由负整数原码计算出反…

计网Lesson3 - 计算机网络评价指标与封包解包

文章目录 计算机网络的性能指标1. 速率2. 带宽3. 吞吐量4. 时延5. 时延带宽积6. 往返时间7. 利用率8. 数据的解包和封包 计算机网络的术语实体![实体](https://img-blog.csdnimg.cn/direct/cbf4ca9ed5ab4df290b5a17b4642c6a1.png)协议服务 计算机网络的性能指标 1. 速率 数据…

linux CentOS MobaXterm 通过X11 Forwarding 在本地开启图形可视化窗口

第一步 操作系统安装图形界面 X11 Forwarding dnf install xorg-x11-xauth xorg-x11-fonts-* xorg-x11-font-utils xorg-x11-fonts-Type1 xclock第二步 修改参数&#xff0c;启用X11 Forwarding vim /etc/ssh/sshd_config修改参数X11Forwarding yes和X11UseLocalhost no #Al…

【专题】支持向量机(Support Vector Machines,SVM)

​​​​​ ​​ 支持向量机&#xff08;Support Vector Machines&#xff0c;SVM&#xff09;是一种强大的监督学习模型&#xff0c;常用于分类、回归和异常值检测任务。它的核心思想是通过构建一个最大间隔超平面来有效地分隔不同类别的数据点。 在SVM中&#xff0c;数据点…