基于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<ADUMessage> Request_05(ushort startIndex, bool coil){}
      // 写 读写一个寄存器
      public async Task<ADUMessage> Request_06(ushort startIndex, ushort register){}
      // 写 读写多个线圈
      public async Task<ADUMessage> Request_0f(ushort startIndex, bool[] coils){}
      // 写 读写多个寄存器
      public async Task<ADUMessage> 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
/// <summary>
/// 数据仓库,144KB
/// </summary>
public class DataStore
{
    /// <summary>
    /// 读写16位寄存器,64KB
    /// </summary>
    public ushort[] HoldingRegisters;
    /// <summary>
    /// 只读16位寄存器,64KB
    /// </summary>
    public ushort[] InputRegisters;
    /// <summary>
    /// 读写1位线圈,8KB
    /// </summary>
    public bool[] CoilDiscretes;
    /// <summary>
    /// 只读1位线圈,8KB
    /// </summary>
    public bool[] CoilInputs;

    public DataStore()
    {
        HoldingRegisters = new ushort[65536];
        InputRegisters = new ushort[65536];
        CoilDiscretes = new bool[65536];
        CoilInputs = new bool[65536];
    }



    /// <summary>
    /// 读 读写16位寄存器
    /// </summary>
    /// <param name="startIndex"></param>
    /// <param name="length"></param>
    /// <returns></returns>
    public ushort[] ReadHoldingRegisters(ushort startIndex, ushort length)
    {
        return HoldingRegisters.Take(new Range(new Index(startIndex), new Index(startIndex + length))).ToArray();
    }
    /// <summary>
    /// 读 只读16位寄存器
    /// </summary>
    /// <param name="startIndex"></param>
    /// <param name="length"></param>
    /// <returns></returns>
    public ushort[] ReadInputRegisters(ushort startIndex, ushort length)
    {
        return InputRegisters.Take(new Range(new Index(startIndex), new Index(startIndex + length))).ToArray();
    }
    /// <summary>
    /// 读 读写1位线圈
    /// </summary>
    /// <param name="startIndex"></param>
    /// <param name="length"></param>
    /// <returns></returns>
    public bool[] ReadCoilDiscretes(ushort startIndex, ushort length)
    {
        return CoilDiscretes.Take(new Range(new Index(startIndex), new Index(startIndex + length))).ToArray();
    }
    /// <summary>
    /// 读 只读1位线圈
    /// </summary>
    /// <param name="startIndex"></param>
    /// <param name="length"></param>
    /// <returns></returns>
    public bool[] ReadCoilInputs(ushort startIndex, ushort length)
    {
        return CoilInputs.Take(new Range(new Index(startIndex), new Index(startIndex + length))).ToArray();
    }
    /// <summary>
    /// 写 读写16位寄存器
    /// </summary>
    /// <param name="startIndex"></param>
    /// <param name="data"></param>
    public void WriteHoldingRegisters(ushort startIndex, ushort[] data)
    {
        for (int i = 0; i < data.Length; i++)
        {
            if (startIndex+i < 65536)
            {
                HoldingRegisters[startIndex + i] = data[i];
            }
        }
    }
    /// <summary>
    /// 写 读写1位线圈
    /// </summary>
    /// <param name="startIndex"></param>
    /// <param name="data"></param>
    public void WriteCoilDiscretes(ushort startIndex, bool[] data)
    {
        for (int i = 0; i < data.Length; i++)
        {
            if (startIndex + i < 65536)
            {
                CoilDiscretes[startIndex + i] = data[i];
            }
        }
    }
}

/// <summary>
/// Modbus报文
/// </summary>
public class ADUMessage
{
    /// <summary>
    /// 事务标识符
    /// </summary>
    public ushort Transaction { get; set; }
    /// <summary>
    /// 协议标识符
    /// </summary>
    public ushort Protocol { get; set; }
    /// <summary>
    /// 报文长度
    /// </summary>
    public ushort Length { get; set; }
    /// <summary>
    /// 单元标识符
    /// </summary>
    public byte Unit { get; set; }
    /// <summary>
    /// 功能码
    /// </summary>
    public byte FunctionCode { get; set; }
    /// <summary>
    /// 数据
    /// </summary>
    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();
        }
    }
}

/// <summary>
/// Modbus服务器
/// </summary>
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;
    }

    /// <summary>
    /// 读 读写线圈
    /// </summary>
    /// <param name="request"></param>
    /// <returns></returns>
    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;
    }

    /// <summary>
    /// 读 只读线圈
    /// </summary>
    /// <param name="request"></param>
    /// <returns></returns>
    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;
    }

    /// <summary>
    /// 读 读写寄存器
    /// </summary>
    /// <param name="request"></param>
    /// <returns></returns>
    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;
    }

    /// <summary>
    /// 读 只读寄存器
    /// </summary>
    /// <param name="request"></param>
    /// <returns></returns>
    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;
    }

    /// <summary>
    /// 写 读写一个线圈
    /// </summary>
    /// <param name="request"></param>
    /// <returns></returns>
    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;
    }

    /// <summary>
    /// 写 读写一个寄存器
    /// </summary>
    /// <param name="request"></param>
    /// <returns></returns>
    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;
    }

    /// <summary>
    /// 写 读写多个线圈
    /// </summary>
    /// <param name="request"></param>
    /// <returns></returns>
    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;
    }

    /// <summary>
    /// 写 读写多个寄存器
    /// </summary>
    /// <param name="request"></param>
    /// <returns></returns>
    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;
    }
}

/// <summary>
/// Modbus客户端
/// </summary>
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<ADUMessage> 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;
    }

    /// <summary>
    /// 读 读写线圈
    /// </summary>
    /// <param name="startIndex"></param>
    /// <param name="length"></param>
    /// <returns></returns>
    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;
    }

    /// <summary>
    /// 读 只读线圈
    /// </summary>
    /// <param name="startIndex"></param>
    /// <param name="length"></param>
    /// <returns></returns>
    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;
    }

    /// <summary>
    /// 读 读写寄存器
    /// </summary>
    /// <param name="startIndex"></param>
    /// <param name="length"></param>
    /// <returns></returns>
    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;
    }

    /// <summary>
    /// 读 只读寄存器
    /// </summary>
    /// <param name="startIndex"></param>
    /// <param name="length"></param>
    /// <returns></returns>
    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;
    }

    /// <summary>
    /// 写 读写一个线圈
    /// </summary>
    /// <param name="startIndex"></param>
    /// <param name="coil"></param>
    /// <returns></returns>
    public async Task<ADUMessage> 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;
    }

    /// <summary>
    /// 写 读写一个寄存器
    /// </summary>
    /// <param name="startIndex"></param>
    /// <param name="register"></param>
    /// <returns></returns>
    public async Task<ADUMessage> 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;
    }

    /// <summary>
    /// 写 读写多个线圈
    /// </summary>
    /// <param name="startIndex"></param>
    /// <param name="coils"></param>
    /// <returns></returns>
    public async Task<ADUMessage> 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;
    }

    /// <summary>
    /// 写 读写多个寄存器
    /// </summary>
    /// <param name="startIndex"></param>
    /// <param name="registers"></param>
    /// <returns></returns>
    public async Task<ADUMessage> 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<TcpClient> clients = new List<TcpClient>();
                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>5234</StartArguments>
</PropertyGroup>

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

posted @ 2024-06-01 13:40  ggtc  阅读(247)  评论(0编辑  收藏  举报
//右下角目录