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

知识点

  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 + "失去连接");
            }