• 基于WebSocket的modbus通信(二)- 客户端


    上一篇已经实现了ModbusTcp服务器和8个主要的功能码,只是还没有实现错误处理功能。
    但是在测试客户端时却发现了上一篇的一个错误,那就是写数据成功,服务器不需要响应。
    接下来要做的就是实现ModbusTcp客户端。有了清晰的协议,代码循规蹈矩的写就行了。

    效果

    • 原始数据
      其中只读寄存器和线圈都有可分辨的值
      image

    • 交互
      改变线圈和寄存器的值

      • 向线圈写入4个1
      • 向寄存器写入4个11
      • 将每个栈的值查询出来

    image

    • 结果
      可以看到数据已变成我们设定的值
      image

    客户端解析

    • 工作流程
      • 命令行输入指令
      • 解析指令
      • 根据功能码跳转到相应分支
      • 构造、发送请求
      • 解析响应
    • 根据协议,每次发请求,事务标识符都会自增。
    • 客户端需要实现8种功能码,因此每个功能码都需要一个方法去实现。
        //WebModbus.cs
      
        // 读 读写线圈
        public async Task<bool[]> Request_01(ushort startIndex, ushort length){}
        // 读 只读线圈
        public async Task<bool[]> Request_02(ushort startIndex, ushort length){}
        // 读 读写寄存器
        public async Task<ushort[]> Request_03(ushort startIndex, ushort length){}
        // 读 只读寄存器
        public async Task<ushort[]> Request_04(ushort startIndex, ushort length){}
        // 写 读写一个线圈
        public async Task Request_05(ushort startIndex, bool coil){}
        // 写 读写一个寄存器
        public async Task Request_06(ushort startIndex, ushort register){}
        // 写 读写多个线圈
        public async Task Request_0f(ushort startIndex, bool[] coils){}
        // 写 读写多个寄存器
        public async Task Request_10(ushort startIndex, ushort[] registers){}
      
    • 为了便于观察消息,我在请求发出后和接到响应后都打印了出来。
        PrintBytes(ADUMessage.Serialze(request), "请求");
        if (Client != null)
        {
        	await Client.Client.SendAsync(new Memory<byte>(ADUMessage.Serialze(request)));
        	byte[] bytes = new byte[1024];
        	int msgLength = await Client.Client.ReceiveAsync(new ArraySegment<byte>(bytes));
        	PrintBytes(bytes.Take(msgLength).ToArray(), "响应");
        }
      
    • 线圈存储时使用bool值,传输时使用bit,而且还是按位的,这需要用到位运算符。所以需要一对转换方法
        public bool[] BytesToBools(byte[] bytes,ushort dataNumber){}
        public byte[] BoolToBytes(bool[] bools){}
      

    测试类

    我们还需要一个界面区使用这个协议,所以还需要一个测试类。
    命令行程序的话,就是使用while循环了,在循环中接收指令

    private static async Task StartClient(string[] args)
    {
        //其他代码...
    	
        while (true)
        {
            Console.WriteLine("请输入指令");
            string? msg = Console.ReadLine();
            while (msg == null)
            {
                //功能码 数据
                msg = Console.ReadLine();
            }
            try
            {
                string[] data = msg.Split(' ');
                ushort funCode = ushort.Parse(data[0],NumberStyles.HexNumber);
                ushort startIndex;
                ushort length;
                switch (funCode)
                {
                    //读 读写线圈
                    case 0x01:
                        startIndex = ushort.Parse(data[1]);
                        length= ushort.Parse(data[2]);
                        var rs_01 = await webModbusClient.Request_01(startIndex, length);
                        PrintBools(rs_01);
                        break;
                    //读 只读线圈
                    case 0x02:
                        startIndex = ushort.Parse(data[1]);
                        length = ushort.Parse(data[2]);
                        var rs_02 = await webModbusClient.Request_02(startIndex, length);
                        PrintBools(rs_02);
                        break;
                    //读 读写寄存器
                    case 0x03:
                        startIndex = ushort.Parse(data[1]);
                        length = ushort.Parse(data[2]);
                        var rs_03 = await webModbusClient.Request_03(startIndex, length);
                        for (global::System.Int32 i = 0; i < length; i++)
                        {
                            Console.Write(rs_03[i]+" ");
                        }
                        Console.WriteLine();
                        break;
                    //读 只读寄存器
                    case 0x04:
                        startIndex = ushort.Parse(data[1]);
                        length = ushort.Parse(data[2]);
                        var rs_04 = await webModbusClient.Request_04(startIndex, length);
                        for (global::System.Int32 i = 0; i < length; i++)
                        {
                            Console.Write(rs_04[i] + " ");
                        }
                        Console.WriteLine();
                        break;
                    //写 读写一个线圈
                    case 0x05:
                        startIndex = ushort.Parse(data[1]);
                        var coil = bool.Parse(data[2]);
                        var rs_05 = await webModbusClient.Request_05(startIndex, coil);
                        break;
                    //写 读写一个寄存器
                    case 0x06:
                        startIndex = ushort.Parse(data[1]);
                        var register = ushort.Parse(data[2]);
                        var rs_06 = await webModbusClient.Request_06(startIndex, register);
                        break;
                    //写 读写多个线圈
                    case 0x0f:
                        startIndex = ushort.Parse(data[1]);
                        bool[] coils = new bool[data.Length - 2];
                        for (global::System.Int32 i = 2; i < data.Length; i++)
                        {
                            coils[i - 2] = bool.Parse(data[i]);
                        }
                        var rs_0f = await webModbusClient.Request_0f(startIndex, coils);
                        break;
                    //写 读写多个寄存器
                    case 0x10:
                        startIndex = ushort.Parse(data[1]);
                        ushort[] registers = new ushort[data.Length - 2];
                        for (global::System.Int32 i = 2; i < data.Length; i++)
                        {
                            registers[i - 2] = ushort.Parse(data[i]);
                        }
                        var rs_10 = await webModbusClient.Request_10(startIndex, registers);
                        break;
                    default:
                        //return Response_01(request);
                        break;
                }
            }
            catch (Exception e)
            {
    
            }
        }
    }
    

    完整代码

    WebModbus.cs
    /// 
    /// 数据仓库,144KB
    /// 
    public class DataStore
    {
        /// 
        /// 读写16位寄存器,64KB
        /// 
        public ushort[] HoldingRegisters;
        /// 
        /// 只读16位寄存器,64KB
        /// 
        public ushort[] InputRegisters;
        /// 
        /// 读写1位线圈,8KB
        /// 
        public bool[] CoilDiscretes;
        /// 
        /// 只读1位线圈,8KB
        /// 
        public bool[] CoilInputs;
    
        public DataStore()
        {
            HoldingRegisters = new ushort[65536];
            InputRegisters = new ushort[65536];
            CoilDiscretes = new bool[65536];
            CoilInputs = new bool[65536];
        }
    
    
    
        /// 
        /// 读 读写16位寄存器
        /// 
        /// 
        /// 
        /// 
        public ushort[] ReadHoldingRegisters(ushort startIndex, ushort length)
        {
            return HoldingRegisters.Take(new Range(new Index(startIndex), new Index(startIndex + length))).ToArray();
        }
        /// 
        /// 读 只读16位寄存器
        /// 
        /// 
        /// 
        /// 
        public ushort[] ReadInputRegisters(ushort startIndex, ushort length)
        {
            return InputRegisters.Take(new Range(new Index(startIndex), new Index(startIndex + length))).ToArray();
        }
        /// 
        /// 读 读写1位线圈
        /// 
        /// 
        /// 
        /// 
        public bool[] ReadCoilDiscretes(ushort startIndex, ushort length)
        {
            return CoilDiscretes.Take(new Range(new Index(startIndex), new Index(startIndex + length))).ToArray();
        }
        /// 
        /// 读 只读1位线圈
        /// 
        /// 
        /// 
        /// 
        public bool[] ReadCoilInputs(ushort startIndex, ushort length)
        {
            return CoilInputs.Take(new Range(new Index(startIndex), new Index(startIndex + length))).ToArray();
        }
        /// 
        /// 写 读写16位寄存器
        /// 
        /// 
        /// 
        public void WriteHoldingRegisters(ushort startIndex, ushort[] data)
        {
            for (int i = 0; i < data.Length; i++)
            {
                if (startIndex+i < 65536)
                {
                    HoldingRegisters[startIndex + i] = data[i];
                }
            }
        }
        /// 
        /// 写 读写1位线圈
        /// 
        /// 
        /// 
        public void WriteCoilDiscretes(ushort startIndex, bool[] data)
        {
            for (int i = 0; i < data.Length; i++)
            {
                if (startIndex + i < 65536)
                {
                    CoilDiscretes[startIndex + i] = data[i];
                }
            }
        }
    }
    
    /// 
    /// Modbus报文
    /// 
    public class ADUMessage
    {
        /// 
        /// 事务标识符
        /// 
        public ushort Transaction { get; set; }
        /// 
        /// 协议标识符
        /// 
        public ushort Protocol { get; set; }
        /// 
        /// 报文长度
        /// 
        public ushort Length { get; set; }
        /// 
        /// 单元标识符
        /// 
        public byte Unit { get; set; }
        /// 
        /// 功能码
        /// 
        public byte FunctionCode { get; set; }
        /// 
        /// 数据
        /// 
        public byte[] Data { get; set; }
    
        public static ADUMessage Deserialize(byte[] buffer) 
        {
            //BinaryReader读取方式是小端(右边是高字节),而modbus是大端传输(左边是高字节)
            BinaryReader reader = new BigEndianBinaryReader(new MemoryStream(buffer));
            ADUMessage adu = new ADUMessage()
            {
                Transaction = reader.ReadUInt16(),
                Protocol = reader.ReadUInt16(),
                Length = reader.ReadUInt16(),
                Unit = reader.ReadByte(),
                FunctionCode = reader.ReadByte(),
                Data = reader.ReadBytes(buffer.Length - 8)
            };
            return adu;
        }
    
        public static byte[] Serialze(ADUMessage message)
        {
            using (MemoryStream ms=new MemoryStream())
            {
                BinaryWriter writer = new BigEndianBinaryWriter(ms);
                writer.Write(message.Transaction);
                writer.Write(message.Protocol);
                writer.Write(message.Length);
                writer.Write(message.Unit);
                writer.Write(message.FunctionCode);
                writer.Write(message.Data);
                return ms.ToArray();
            }
        }
    }
    
    /// 
    /// Modbus服务器
    /// 
    public class WebModbusServer
    {
        public DataStore store = new DataStore();
    
        public ADUMessage HandleRequest(byte[] buffer)
        {
            ADUMessage request = ADUMessage.Deserialize(buffer);
            switch (request.FunctionCode)
            {
                //读 读写线圈
                case 0x01:
                    return Response_01(request);
                //读 只读线圈
                case 0x02:
                    return Response_02(request);
                //读 读写寄存器
                case 0x03:
                    return Response_03(request);
                //读 只读寄存器
                case 0x04:
                    return Response_04(request);
                //写 读写一个线圈
                case 0x05:
                    return Response_05(request);
                //写 读写一个寄存器
                case 0x06:
                    return Response_06(request);
                //写 读写多个线圈
                case 0x0f:
                    return Response_0f(request);
                //写 读写多个寄存器
                case 0x10:
                    return Response_10(request);
                default:
                    return Response_01(request);
            }
        }
    
        public byte[] CoilToBytes(bool[] bools)
        {
            int byteCount = (bools.Length + 7) / 8; // 计算所需的字节数
            byte[] bytes = new byte[byteCount];
    
            for (int i = 0; i < bools.Length; i++)
            {
                int byteIndex = i / 8; // 计算当前布尔值应该存储在哪个字节中
                int bitIndex = i % 8; // 计算当前布尔值应该存储在字节的哪个位上
    
                if (bools[i])
                {
                    // 设置对应位为 1
                    bytes[byteIndex] |= (byte)(1 << bitIndex);
                }
                else
                {
                    // 对应位保持为 0,无需额外操作
                }
            }
    
            return bytes;
        }
    
        /// 
        /// 读 读写线圈
        /// 
        /// 
        /// 
        private ADUMessage Response_01(ADUMessage request)
        {
            BinaryReader reader = new BigEndianBinaryReader(new MemoryStream(request.Data));
            BinaryWriter writer;
            ushort StartAddress, DataNumber;
            StartAddress = reader.ReadUInt16();
            DataNumber = reader.ReadUInt16();
            bool[] data = store.ReadCoilDiscretes(StartAddress, DataNumber);
            byte[] coilBytes = CoilToBytes(data);
            byte[] dataBytes = new byte[coilBytes.Length + 1];
            writer = new BinaryWriter(new MemoryStream(dataBytes));
            writer.Write((byte)coilBytes.Length);
            writer.Write(coilBytes);
            ADUMessage response = new ADUMessage()
            {
                Transaction = request.Transaction,
                Protocol = request.Protocol,
                Length = (ushort)(dataBytes.Length + 2),
                Unit = request.Unit,
                FunctionCode = request.FunctionCode,
                Data = dataBytes,
            };
            return response;
        }
    
        /// 
        /// 读 只读线圈
        /// 
        /// 
        /// 
        private ADUMessage Response_02(ADUMessage request)
        {
            BinaryReader reader = new BigEndianBinaryReader(new MemoryStream(request.Data));
            BinaryWriter writer;
            ushort StartAddress, DataNumber;
            StartAddress = reader.ReadUInt16();
            DataNumber = reader.ReadUInt16();
            bool[] data = store.ReadCoilInputs(StartAddress, DataNumber);
            byte[] coilBytes = CoilToBytes(data);
            byte[] dataBytes = new byte[coilBytes.Length + 1];
            writer = new BinaryWriter(new MemoryStream(dataBytes));
            writer.Write((byte)coilBytes.Length);
            writer.Write(coilBytes);
            ADUMessage response = new ADUMessage()
            {
                Transaction = request.Transaction,
                Protocol = request.Protocol,
                Length = (ushort)(dataBytes.Length + 2),
                Unit = request.Unit,
                FunctionCode = request.FunctionCode,
                Data = dataBytes,
            };
            return response;
        }
    
        /// 
        /// 读 读写寄存器
        /// 
        /// 
        /// 
        private ADUMessage Response_03(ADUMessage request)
        {
            BinaryReader reader = new BigEndianBinaryReader(new MemoryStream(request.Data));
            BinaryWriter writer;
            ushort StartAddress, DataNumber;
            StartAddress = reader.ReadUInt16();
            DataNumber = reader.ReadUInt16();
            ushort[] data = store.ReadHoldingRegisters(StartAddress, DataNumber);
            byte[] dataBytes = new byte[data.Length * 2 + 1];
            writer = new BigEndianBinaryWriter(new MemoryStream(dataBytes));
            writer.Write((byte)(data.Length * 2));
            foreach (ushort value in data)
            {
                writer.Write(value);
            }
            Array.Resize(ref dataBytes, dataBytes.Length + 1);
            ADUMessage response = new ADUMessage()
            {
                Transaction = request.Transaction,
                Protocol = request.Protocol,
                Length = (ushort)(dataBytes.Length + 2),
                Unit = request.Unit,
                FunctionCode = request.FunctionCode,
                Data = dataBytes,
            };
            return response;
        }
    
        /// 
        /// 读 只读寄存器
        /// 
        /// 
        /// 
        private ADUMessage Response_04(ADUMessage request)
        {
            BinaryReader reader = new BigEndianBinaryReader(new MemoryStream(request.Data));
            BinaryWriter writer;
            ushort StartAddress, DataNumber;
            StartAddress = reader.ReadUInt16();
            DataNumber = reader.ReadUInt16();
            ushort[] data = store.ReadInputRegisters(StartAddress, DataNumber);
            byte[] dataBytes = new byte[data.Length * 2 + 1];
            writer = new BigEndianBinaryWriter(new MemoryStream(dataBytes));
            writer.Write((byte)(data.Length * 2));
            foreach (ushort value in data)
            {
                writer.Write(value);
            }
            Array.Resize(ref dataBytes, dataBytes.Length + 1);
            ADUMessage response = new ADUMessage()
            {
                Transaction = request.Transaction,
                Protocol = request.Protocol,
                Length = (ushort)(dataBytes.Length + 2),
                Unit = request.Unit,
                FunctionCode = request.FunctionCode,
                Data = dataBytes,
            };
            return response;
        }
    
        /// 
        /// 写 读写一个线圈
        /// 
        /// 
        /// 
        private ADUMessage Response_05(ADUMessage request)
        {
            BinaryReader reader = new BigEndianBinaryReader(new MemoryStream(request.Data));
            ushort StartAddress, coli;
            StartAddress = reader.ReadUInt16();
            coli = reader.ReadUInt16();
            store.WriteCoilDiscretes(StartAddress, new bool[] { coli ==0xff00?true:false});
            return request;
        }
    
        /// 
        /// 写 读写一个寄存器
        /// 
        /// 
        /// 
        private ADUMessage Response_06(ADUMessage request)
        {
            BinaryReader reader = new BigEndianBinaryReader(new MemoryStream(request.Data));
            ushort StartAddress, register;
            StartAddress = reader.ReadUInt16();
            register = reader.ReadUInt16();
            store.WriteHoldingRegisters(StartAddress, new ushort[] { register });
            return request;
        }
    
        /// 
        /// 写 读写多个线圈
        /// 
        /// 
        /// 
        private ADUMessage Response_0f(ADUMessage request)
        {
            BinaryReader reader = new BigEndianBinaryReader(new MemoryStream(request.Data));
            ushort StartAddress, DataNumber;
            StartAddress = reader.ReadUInt16();
            DataNumber = reader.ReadUInt16();
            byte byteNumber = reader.ReadByte();
            //线圈是小端传输
            byte[] bytes = reader.ReadBytes(byteNumber);
            bool[] data=new bool[DataNumber];
            byte index = 0;
            foreach (var item in bytes)
            {
                //1000 0000
                byte rr = (byte)0x01;
                for (int i = 0; i < 8; i++)
                {
                    if (index< DataNumber)
                    {
                        var result = rr & item;
                        if (result > 0)
                        {
                            data[index] = true;
                        }
                        else
                        {
                            data[index] = false;
                        }
                        //0100 0000
                        rr <<= 1;
                        index++;
                    }
                    else
                    {
                        break;
                    }
                }
            }
            store.WriteCoilDiscretes(StartAddress, data);
            return request;
        }
    
        /// 
        /// 写 读写多个寄存器
        /// 
        /// 
        /// 
        private ADUMessage Response_10(ADUMessage request)
        {
            //寄存器是大端传输
            BinaryReader reader = new BigEndianBinaryReader(new MemoryStream(request.Data));
            ushort StartAddress, DataNumber;
            StartAddress = reader.ReadUInt16();
            DataNumber = reader.ReadUInt16();
            byte byteNumber = reader.ReadByte();
            ushort[] data = new ushort[byteNumber / 2];
            for (int i = 0; i < data.Length; i++)
            {
                data[i] = reader.ReadUInt16();
            }
            store.WriteHoldingRegisters(StartAddress, data);
            return request;
        }
    }
    
    /// 
    /// Modbus客户端
    /// 
    public class WebModbusClient
    {
        public ushort Transaction { get; set; }
        public TcpClient Client { get; }
        public WebSocket WebSocket { get; set; }
        public ADUMessage request { get; set; }
        public ADUMessage response { get; set; }
    
        public WebModbusClient(TcpClient client)
        {
            Transaction = 0x00;
            Client = client;
        }
    
        public WebModbusClient(WebSocket webSocket)
        {
            Transaction = 0x00;
            WebSocket = webSocket;
        }
    
        private ADUMessage CreateMsg()
        {
            ADUMessage message = new ADUMessage();
            message.Transaction = Transaction;
            Transaction++;
            message.Protocol = 0x00;
            message.Unit = 0x00;
            this.request = message;
            return message;
        }
        public void PrintBytes(byte[] bytes, string prefix = "")
        {
            Console.Write(prefix);
            for (int i = 0; i < bytes.Length; i++)
            {
                if (i < 2)
                {
                    Console.ForegroundColor = ConsoleColor.Red;
                }
                else if (i < 4)
                {
                    Console.ForegroundColor = ConsoleColor.Green;
                }
                else if (i < 6)
                {
                    Console.ForegroundColor = ConsoleColor.Blue;
                }
                else if (i < 7)
                {
                    Console.ForegroundColor = ConsoleColor.Yellow;
                }
                else if (i < 8)
                {
                    Console.ForegroundColor = ConsoleColor.DarkCyan;
                }
                else
                {
                    Console.ForegroundColor = ConsoleColor.White;
                }
                Console.Write(bytes[i].ToString("X2") + " ");
            }
            Console.WriteLine();
        }
        public bool[] BytesToBools(byte[] bytes,ushort dataNumber)
        {
            int index = 0;
            bool[] bools = new bool[dataNumber];
            foreach (var item in bytes)
            {
                //1000 0000
                byte rr = (byte)0x01;
                for (int i = 0; i < 8; i++)
                {
                    if (index < dataNumber)
                    {
                        var result = rr & item;
                        if (result > 0)
                        {
                            bools[index] = true;
                        }
                        else
                        {
                            bools[index] = false;
                        }
                        //0100 0000
                        rr <<= 1;
                        index++;
                    }
                    else
                    {
                        break;
                    }
                }
            }
            return bools;
        }
    
        private async Task SendWithResponse(ADUMessage request)
        {
            PrintBytes(ADUMessage.Serialze(request), "请求");
            if (Client != null)
            {
                await Client.Client.SendAsync(new Memory<byte>(ADUMessage.Serialze(request)));
                byte[] bytes = new byte[1024];
                int msgLength = await Client.Client.ReceiveAsync(new ArraySegment<byte>(bytes));
                this.response = ADUMessage.Deserialize(bytes.Take(msgLength).ToArray());
                PrintBytes(bytes.Take(msgLength).ToArray(), "响应");
                return response;
            }
            else if(WebSocket != null)
            {
                await WebSocket.SendAsync(new ArraySegment<byte>(ADUMessage.Serialze(request)),WebSocketMessageType.Binary,true,CancellationToken.None);
                byte[] bytes = new byte[1024];
                var result = await WebSocket.ReceiveAsync(new ArraySegment<byte>(bytes),CancellationToken.None);
                this.response = ADUMessage.Deserialize(bytes.Take(result.Count).ToArray());
                PrintBytes(bytes.Take(result.Count).ToArray(), "响应");
                return response;
            }
            else
            {
                throw new Exception("没有传入连接");
            }
        }
        private async Task SendNoResponse(ADUMessage request)
        {
            PrintBytes(ADUMessage.Serialze(request), "请求");
            if (Client != null)
            {
                await Client.Client.SendAsync(new Memory<byte>(ADUMessage.Serialze(request)));
            }
            else if (WebSocket != null)
            {
                await WebSocket.SendAsync(new ArraySegment<byte>(ADUMessage.Serialze(request)), WebSocketMessageType.Binary, true, CancellationToken.None);
            }
            else
            {
                throw new Exception("没有传入连接");
            }
        }
    
        public byte[] BoolToBytes(bool[] bools)
        {
            int byteCount = (bools.Length + 7) / 8; // 计算所需的字节数
            byte[] bytes = new byte[byteCount];
    
            for (int i = 0; i < bools.Length; i++)
            {
                int byteIndex = i / 8; // 计算当前布尔值应该存储在哪个字节中
                int bitIndex = i % 8; // 计算当前布尔值应该存储在字节的哪个位上
    
                if (bools[i])
                {
                    // 设置对应位为 1
                    bytes[byteIndex] |= (byte)(1 << bitIndex);
                }
                else
                {
                    // 对应位保持为 0,无需额外操作
                }
            }
    
            return bytes;
        }
    
        /// 
        /// 读 读写线圈
        /// 
        /// 
        /// 
        /// 
        public async Task<bool[]> Request_01(ushort startIndex, ushort length)
        {
            var request = CreateMsg();
            request.Length = 0x06;
            request.FunctionCode= 0x01;
            request.Data = new byte[4];
            BinaryWriter writer = new BigEndianBinaryWriter(new MemoryStream(request.Data));
            writer.Write(startIndex);
            writer.Write(length);
            var response = await SendWithResponse(request);
            BinaryReader reader = new BinaryReader(new MemoryStream(response.Data));
            byte byteLength=reader.ReadByte();
            byte[] bytes = reader.ReadBytes(byteLength);
            bool[] bools= BytesToBools(bytes,length);
            return bools;
        }
    
        /// 
        /// 读 只读线圈
        /// 
        /// 
        /// 
        /// 
        public async Task<bool[]> Request_02(ushort startIndex, ushort length)
        {
            var request = CreateMsg();
            request.Length = 0x06;
            request.FunctionCode = 0x02;
            request.Data = new byte[4];
            BinaryWriter writer = new BigEndianBinaryWriter(new MemoryStream(request.Data));
            writer.Write(startIndex);
            writer.Write(length);
            var response = await SendWithResponse(request);
            BinaryReader reader = new BinaryReader(new MemoryStream(response.Data));
            byte byteLength = reader.ReadByte();
            byte[] bytes = reader.ReadBytes(byteLength);
            bool[] bools = BytesToBools(bytes, length);
            return bools;
        }
    
        /// 
        /// 读 读写寄存器
        /// 
        /// 
        /// 
        /// 
        public async Task<ushort[]> Request_03(ushort startIndex, ushort length)
        {
            var request = CreateMsg();
            request.Length = 0x06;
            request.FunctionCode = 0x03;
            request.Data = new byte[4];
            BinaryWriter writer = new BigEndianBinaryWriter(new MemoryStream(request.Data));
            writer.Write(startIndex);
            writer.Write(length);
            var response = await SendWithResponse(request);
            BinaryReader reader = new BigEndianBinaryReader(new MemoryStream(response.Data));
            byte byteLength = reader.ReadByte();
            ushort[] registers = new ushort[length];
            for (int i = 0; i < length; i++)
            {
                registers[i] = reader.ReadUInt16();
            }
            return registers;
        }
    
        /// 
        /// 读 只读寄存器
        /// 
        /// 
        /// 
        /// 
        public async Task<ushort[]> Request_04(ushort startIndex, ushort length)
        {
            var request = CreateMsg();
            request.Length = 0x06;
            request.FunctionCode = 0x04;
            request.Data = new byte[4];
            BinaryWriter writer = new BigEndianBinaryWriter(new MemoryStream(request.Data));
            writer.Write(startIndex);
            writer.Write(length);
            var response = await SendWithResponse(request);
            BinaryReader reader = new BigEndianBinaryReader(new MemoryStream(response.Data));
            byte byteLength = reader.ReadByte();
            ushort[] registers = new ushort[length];
            for (int i = 0; i < registers.Length; i++)
            {
                registers[i] = reader.ReadUInt16();
            }
            return registers;
        }
    
        /// 
        /// 写 读写一个线圈
        /// 
        /// 
        /// 
        /// 
        public async Task Request_05(ushort startIndex, bool coil)
        {
            var request = CreateMsg();
            request.Length = 0x06;
            request.FunctionCode = 0x05;
            request.Data = new byte[4];
            BinaryWriter writer = new BigEndianBinaryWriter(new MemoryStream(request.Data));
            writer.Write(startIndex);
            if (coil)
            {
                writer.Write((ushort)0xff00);
            }
            else
            {
                writer.Write((ushort)0x0000);
            }
            await SendNoResponse(request);
            return request;
        }
    
        /// 
        /// 写 读写一个寄存器
        /// 
        /// 
        /// 
        /// 
        public async Task Request_06(ushort startIndex, ushort register)
        {
            var request = CreateMsg();
            request.Length = 0x06;
            request.FunctionCode = 0x06;
            request.Data = new byte[4];
            BinaryWriter writer = new BigEndianBinaryWriter(new MemoryStream(request.Data));
            writer.Write(startIndex);
            writer.Write(register);
            await SendNoResponse(request);
            return request;
        }
    
        /// 
        /// 写 读写多个线圈
        /// 
        /// 
        /// 
        /// 
        public async Task Request_0f(ushort startIndex, bool[] coils)
        {
            var request = CreateMsg();
            request.FunctionCode = 0x0f;
            request.Data = new byte[4+1+(coils.Length+7)/8];
            BinaryWriter writer = new BigEndianBinaryWriter(new MemoryStream(request.Data));
            writer.Write((ushort)startIndex);
            var coilBytes = BoolToBytes(coils);
            request.Length = (ushort)(7 + coilBytes.Length);
            writer.Write((ushort)coils.Length);
            writer.Write((byte)coilBytes.Length);
            writer.Write(coilBytes);
            await SendNoResponse(request);
            return request;
        }
    
        /// 
        /// 写 读写多个寄存器
        /// 
        /// 
        /// 
        /// 
        public async Task Request_10(ushort startIndex, ushort[] registers)
        {
            var request = CreateMsg();
            request.Length = (ushort)(7+ registers.Length * 2);
            request.FunctionCode = 0x10;
            request.Data = new byte[4+1+registers.Length*2];
            BinaryWriter writer = new BigEndianBinaryWriter(new MemoryStream(request.Data));
            writer.Write((ushort)startIndex);
            writer.Write((ushort)registers.Length);
            writer.Write((byte)(registers.Length * 2));
            for (int i = 0; i < registers.Length; i++)
            {
                writer.Write(registers[i]);
            }
            await SendNoResponse(request);
            return request;
        }
    }
    
    Program.cs
        internal class Program
        {
            static WebModbusServer webModbusServer;
            static void Main(string[] args)
            {
                webModbusServer = new WebModbusServer();
                //服务器
                if (args.Length == 1)
                {
                    //webModbusServer.store.WriteCoilDiscretes(0, new bool[] { true, true });
                    //webModbusServer.store.CoilInputs[0] = true;
                    //webModbusServer.store.CoilInputs[1] = true;
                    StartServer(args[0]);
                }
                //客户端
                else
                {
                    Task.Run(async () =>
                    {
                        await StartClient(args);
                    }).Wait();
                }
            }
    
            private static void StartServer(string args)
            {
    
                int serverPort = Convert.ToInt32(args);
                var server = new TcpListener(IPAddress.Parse("127.0.0.1"), serverPort);
                Console.WriteLine($"TCP服务器  127.0.0.1:{serverPort}");
                server.Start();
                int cnt = 0;
                Task.Run(async () =>
                {
                    List clients = new List();
                    while (true)
                    {
                        TcpClient client = await server.AcceptTcpClientAsync();
                        clients.Add(client);
                        cnt++;
                        var ep = client.Client.RemoteEndPoint as IPEndPoint;
                        Console.WriteLine($"TCP客户端_{cnt}  {ep.Address}:{ep.Port}");
                        //给这个客户端开一个聊天线程
                        //操作系统将会根据游客端口对应表将控制权交给对应游客线程
                        //StartChat(client);
                        StartModbus(client);
                    }
                }).Wait();
            }
    
            private static async Task StartClient(string[] args)
            {
                int clientPort = Convert.ToInt32(args[0]);
                int serverPort = Convert.ToInt32(args[1]);
                var client = new TcpClient(new IPEndPoint(IPAddress.Parse("127.0.0.1"), clientPort));
                Console.WriteLine($"TCP客户端  127.0.0.1:{clientPort}");
                await client.ConnectAsync(new IPEndPoint(IPAddress.Parse("127.0.0.1"), serverPort));
                Console.WriteLine($"连接到 127.0.0.1:{serverPort}");
                WebModbusClient webModbusClient = new WebModbusClient(client);
                Console.WriteLine("【功能码】 【地址】 【数量|数据】");
                while (true)
                {
                    Console.WriteLine("请输入指令");
                    string? msg = Console.ReadLine();
                    while (msg == null)
                    {
                        //功能码 数据
                        msg = Console.ReadLine();
                    }
                    try
                    {
                        string[] data = msg.Split(' ');
                        ushort funCode = ushort.Parse(data[0],NumberStyles.HexNumber);
                        ushort startIndex;
                        ushort length;
                        switch (funCode)
                        {
                            //读 读写线圈
                            case 0x01:
                                startIndex = ushort.Parse(data[1]);
                                length= ushort.Parse(data[2]);
                                var rs_01 = await webModbusClient.Request_01(startIndex, length);
                                PrintBools(rs_01);
                                break;
                            //读 只读线圈
                            case 0x02:
                                startIndex = ushort.Parse(data[1]);
                                length = ushort.Parse(data[2]);
                                var rs_02 = await webModbusClient.Request_02(startIndex, length);
                                PrintBools(rs_02);
                                break;
                            //读 读写寄存器
                            case 0x03:
                                startIndex = ushort.Parse(data[1]);
                                length = ushort.Parse(data[2]);
                                var rs_03 = await webModbusClient.Request_03(startIndex, length);
                                for (global::System.Int32 i = 0; i < length; i++)
                                {
                                    Console.Write(rs_03[i]+" ");
                                }
                                Console.WriteLine();
                                break;
                            //读 只读寄存器
                            case 0x04:
                                startIndex = ushort.Parse(data[1]);
                                length = ushort.Parse(data[2]);
                                var rs_04 = await webModbusClient.Request_04(startIndex, length);
                                for (global::System.Int32 i = 0; i < length; i++)
                                {
                                    Console.Write(rs_04[i] + " ");
                                }
                                Console.WriteLine();
                                break;
                            //写 读写一个线圈
                            case 0x05:
                                startIndex = ushort.Parse(data[1]);
                                var coil = bool.Parse(data[2]);
                                var rs_05 = await webModbusClient.Request_05(startIndex, coil);
                                break;
                            //写 读写一个寄存器
                            case 0x06:
                                startIndex = ushort.Parse(data[1]);
                                var register = ushort.Parse(data[2]);
                                var rs_06 = await webModbusClient.Request_06(startIndex, register);
                                break;
                            //写 读写多个线圈
                            case 0x0f:
                                startIndex = ushort.Parse(data[1]);
                                bool[] coils = new bool[data.Length - 2];
                                for (global::System.Int32 i = 2; i < data.Length; i++)
                                {
                                    coils[i - 2] = bool.Parse(data[i]);
                                }
                                var rs_0f = await webModbusClient.Request_0f(startIndex, coils);
                                break;
                            //写 读写多个寄存器
                            case 0x10:
                                startIndex = ushort.Parse(data[1]);
                                ushort[] registers = new ushort[data.Length - 2];
                                for (global::System.Int32 i = 2; i < data.Length; i++)
                                {
                                    registers[i - 2] = ushort.Parse(data[i]);
                                }
                                var rs_10 = await webModbusClient.Request_10(startIndex, registers);
                                break;
                            default:
                                //return Response_01(request);
                                break;
                        }
                    }
                    catch (Exception e)
                    {
    
                    }
                }
            }
    
            public static async Task StartModbus(TcpClient client)
            {
                var buffer = new byte[1024 * 4];
                while (client.Connected)
                {
                    int msgLength = await client.Client.ReceiveAsync(new ArraySegment<byte>(buffer));
                    //关闭连接时会接收到一次空消息,不知道为什么
                    if (msgLength>0)
                    {
                        PrintBytes(buffer.Take(msgLength).ToArray(), "请求 ");
                        ADUMessage response = webModbusServer.HandleRequest(buffer.Take(msgLength).ToArray());
                        await client.Client.SendAsync(ADUMessage.Serialze(response));
                        PrintBytes(ADUMessage.Serialze(response), "响应 ");
                    }
                }
            }
    
            public static void PrintBytes(byte[] bytes,string prefix="")
            {
                Console.Write(prefix);
                for (int i = 0; i < bytes.Length; i++)
                {
                    if (i < 2)
                    {
                        Console.ForegroundColor = ConsoleColor.Red;
                    }
                    else if(i<4)
                    {
                        Console.ForegroundColor = ConsoleColor.Green;
                    }
                    else if(i<6)
                    {
                        Console.ForegroundColor= ConsoleColor.Blue;
                    }
                    else if (i < 7)
                    {
                        Console.ForegroundColor = ConsoleColor.Yellow;
                    }
                    else if (i<8)
                    {
                        Console.ForegroundColor = ConsoleColor.DarkCyan;
                    }
                    else
                    {
                        Console.ForegroundColor = ConsoleColor.White;
                    }
                    Console.Write(bytes[i].ToString("X2") + " ");
                }
                Console.WriteLine();
            }
            public static void PrintBools(bool[] bools)
            {
                for (int i = 0; i < bools.Length; i++)
                {
                    Console.Write(bools[i] + " ");
                }
                Console.WriteLine();
            }
        }
    
    BigEndianBinaryReader.cs
    public class BigEndianBinaryReader : BinaryReader
    {
        public BigEndianBinaryReader(Stream input) : base(input)
        {
        }
    
        public override short ReadInt16()
        {
            var data = base.ReadBytes(2);
            Array.Reverse(data);
            return BitConverter.ToInt16(data, 0);
        }
    
        public override ushort ReadUInt16()
        {
            var data = base.ReadBytes(2);
            Array.Reverse(data);
            return BitConverter.ToUInt16(data, 0);
        }
    
        public override int ReadInt32()
        {
            var data = base.ReadBytes(4);
            Array.Reverse(data);
            return BitConverter.ToInt32(data, 0);
        }
    
        public override uint ReadUInt32()
        {
            var data = base.ReadBytes(4);
            Array.Reverse(data);
            return BitConverter.ToUInt32(data, 0);
        }
    
        public override long ReadInt64()
        {
            var data = base.ReadBytes(8);
            Array.Reverse(data);
            return BitConverter.ToInt64(data, 0);
        }
    
        public override float ReadSingle()
        {
            var data = base.ReadBytes(4);
            Array.Reverse(data);
            return BitConverter.ToSingle(data, 0);
        }
    
        public override double ReadDouble()
        {
            var data = base.ReadBytes(8);
            Array.Reverse(data);
            return BitConverter.ToDouble(data, 0);
        }
    
        // 可以继续添加其他方法来支持更多数据类型的大端读取
    }
    public class BigEndianBinaryWriter : BinaryWriter
    {
        public BigEndianBinaryWriter(Stream input) : base(input)
        {
        }
    
        public override void Write(ushort value)
        {
            var bytes = BitConverter.GetBytes(value);
            Array.Reverse(bytes);
            base.Write(bytes);
        }
    
        public override void Write(short value)
        {
            var bytes = BitConverter.GetBytes(value);
            Array.Reverse(bytes);
            base.Write(bytes);
        }
    
        public override void Write(uint value)
        {
            var bytes = BitConverter.GetBytes(value);
            Array.Reverse(bytes);
            base.Write(bytes);
        }
    
        public override void Write(int value)
        {
            var bytes = BitConverter.GetBytes(value);
            Array.Reverse(bytes);
            base.Write(bytes);
        }
    
        public override void Write(ulong value)
        {
            var bytes = BitConverter.GetBytes(value);
            Array.Reverse(bytes);
            base.Write(bytes);
        }
    
        public override void Write(long value)
        {
            var bytes = BitConverter.GetBytes(value);
            Array.Reverse(bytes);
            base.Write(bytes);
        }
    
    
    
        // 可以继续添加其他方法来支持更多数据类型的大端写入
    }
    

    程序所需命令行参数的一种方式是在项目文件种指定,这在调试时比较方便

    <PropertyGroup>
    	<StartArguments>5234StartArguments>
    PropertyGroup>
    

    可以注意到ModbusTcp消息的解析和Tcp没有什么关系。因此,验证了服务器和客户端的正确性之后,就可以把Tcp连接改为WebSocket连接了。

  • 相关阅读:
    发电机负载测试方案
    InnoDB之Undo log格式
    【DOE】--方差、自由度、回归分析
    架构师思维学习
    三剑客之 awk
    Yolov4网络详解
    老王谈商业模式:知识付费为什么是风口?
    2022年阿里高频Java面试题:分布式+中间件+高并发+算法+数据库
    网络安全(黑客技术)—2024自学手册
    Mybatis底层实现学习
  • 原文地址:https://www.cnblogs.com/ggtc/p/18225717