基于WebSocket的modbus通信(三)- websocket和串口

时间:2024-06-02 19:40:54

WebSocket传递ModbusTCP数据包

  • 错误纠正
    上一篇还有个错误,就是客户端写数据时服务端不需要响应,但我的服务端响应了的。我选择改客户端,把写数据时接收到的响应丢弃。
PrintBytes(ADUMessage.Serialze(request), "请求");
if (Client != null)
{
    await Client.Client.SendAsync(new Memory<byte>(ADUMessage.Serialze(request)));
    //丢弃可能的响应
    await WebSocket.ReceiveAsync(new ArraySegment<byte>(new byte[1024*4]), CancellationToken.None);
}

现在我们同时有了服务器和客户端,就可以在tcp连接上面覆盖一个websocket连接,然后用WebSocket传递Modbus数据帧。

效果

这是基于WebSocket连接的modbus通信,读写都没问题

  • 服务器
    image
  • 客户端
    image

主程序改造

我们的服务器和客户端可以*选择使用TCP或者WebSocket通信,所以

  • 需要提供对应的命令行参数
  • 根据参数选择TCP或者WebSocket通信

首先是命令行参数
参数设计如下

  • 服务器: tcp|websocket 服务器端口
  • 客户端: tcp|websocket 客户端端口 服务器端口
static void Main(string[] args)
{
    webModbusServer = new WebModbusServer();
    //服务器
    if (args.Length == 2)
    {
        if (args[0]=="tcp")
        {
            StartTcpServer(args[1]);
        }
        else if(args[0] == "websocket")
        {
            StartWebSocketServer(args[1]);
        }
    }
    //客户端
    else
    {
        if (args[0] == "tcp")
        {
            Task.Run(async () =>
            {
                await StartClient(args);
            }).Wait();
        }
        else if (args[0] == "websocket")
        {
            Task.Run(async () =>
            {
                await StartWebsocketClient(args);
            }).Wait();
        }
    }
}

然后就是实现StartTcpServer,StartWebSocketServer,StartClient,StartWebsocketClient这四个方法。
具体实现比较繁琐,我就放到最后的完整代码里面了。

串口传递ModbusTCP数据包

不同于网络通信的7层协议或者TCP/IP协议族为我们所熟悉。串口通信是如何进行的?也是分层的吗?串口通信与网络通信能融合吗?这个我们比较陌生。
串口通信和网络通信(例如通过以太网进行的网络通信)在其基本原理和工作方式上有一些显著的区别。

串口通信与网络通信比较

  1. 物理介质:

    • 串口通信:通常通过物理导线(例如串口线)直接连接两个设备进行通信,例如 RS-232、RS-485、USB 等串口标准。
      串口通信通常通过导线进行,但也可以通过其他媒介进行,如光纤或无线电波。就是插一个转换器到串口接口上,比如串口到光纤转换器。串口到无线电波转换器。我们平时经常用到的就有USB转WIFI、USB转4G。
    • 网络通信:通过各种不同的物理介质进行,如以太网使用的双绞线、光纤、无线电波等。
  2. 协议栈和分层:

    • 串口通信:虽然串口通信也可以分层,但通常它的分层结构较简单,主要包含物理层和数据链路层。常见的串口通信协议如
      物理层 传输介质 接口 数据链路层
      RS-232 串口电缆 9 针 D-Sub Modbus、CAN,自定义协议 传输距离相对较短,通常为数米
      RS-422 两对绝缘的双绞线 9 针 D-Sub Modbus、CAN,自定义协议 通常可达几百米的距离
      RS-485 双绞线 9 针或者 15 针 D-Sub Modbus、CAN,自定义协议 传输距离可达数百至数千米
    • 网络通信:基于 TCP/IP 协议族的网络通信通常遵循 OSI 模型的七层协议结构,包括物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。每一层都有特定的功能和协议,如物理层负责传输比特流,网络层负责数据包的路由等。
      相应的网络通信的标准:
      物理层 传输介质 数据链路层
      10BASE-T 双绞线 IEEE 802.3 以太网
      10BASE2 同轴电缆 IEEE 802.3 以太网
      802.11a系列 无线电 IEEE 802.11 MAC WIFI
      LTE 无线电 LTE MAC 4G
      1000BASE-SX 光纤 IEEE 802.3 以太网
    • 串口通信只是物理层和数据链路层的标准,同样可以上层架设TCP/IP协议栈,实现远程监控、远程控制等功能。
  3. 速率和带宽:

    • 串口通信:通常速率较低,受限于物理介质和串口协议的限制,不适合大量数据的高速传输。
    • 网络通信:具有较高的传输速率和带宽,能够支持大规模数据的传输。

在程序中使用串口

就像网络通讯我们不用管TCP及以下的协议层一样,串口通信物理层不用管,操作系统已经帮我们搞定了。
我们只需要写数据链路层就行了。
使用System.IO.Ports.SerialPort这个类就行了。
但有个问题是串口只负责发送接收比特流组织成字节放在缓冲区,至于里面是什么意思,我们一概不知。

在串口上覆盖ModbusTCP协议作为数据链路层实现复杂通信

为了减少代码,这里就由我手搓的ModbusTcp服务器和客户端来测试,但是串口的物理层只提供了数据编码、每个字符的数据校验的能力,不提供封装成帧的能力。
而ModbusTcp原本设计是在tcp上面使用,tcp及其下面的层已经提供了这些功能,所以ModbusTcp本身不提供封装成帧。
我们会面临看到数据来了就跑去接收,结果数据才接收了一半这些问题。
我们有两种选择。

  • 写一个封装成帧、透明传输的协议作为数据链路层在物理层之上,然后把ModbusTcp作为第三层的协议
  • 使用ModbusRTU协议作为数据链路层。

因为我们要使用ModbusTCP,所以就选第一种。

串口数据链路层

现成的是没有的,我们只好自己再来搓一个数据链路层。把一个ModbusTcp数据帧接收完了再交给上层处理。

  • 这个协议应该有一个数据帧栈。
  • 这一层不断读串口缓冲区,完整读出来一个帧后,就添加到数据帧栈中。
  • 读下一个帧。

帧格式

帧开始符 (modbustcp)数据 帧结束符
SOH(0x01) bytes EOT(0x04)
  • 不透明的帧
    帧开始符 (modbustcp)数据 帧结束符
    SOH(0x01) ESC bytes SOH bytes EOT bytes EOT(0x04)
  • 透明的帧
    用ESC来进行字节填充解决透明传输
    帧开始符 (modbustcp)数据 帧结束符
    SOH(0x01) ESC ESC bytes ESC SOH bytes ESC EOT bytes EOT(0x04)

数据链路层实现

数据链路层的实现较为复杂,主要实现了透明传输、封装成帧。
其中有一个难点是在没有获取到数据帧时等待,但有数据帧到来后又要完成这个等待任务。
这就用到了TaskCompletionSource对象

//没有计算完成时等待
await dataReceived.Task;
//触发完成计算
dataReceived.TrySetResult(true);

使用时直接传入串口号创建一个数据链路层对象,然后阻塞调用他的发送数据和接收数据方法

//创建数据链路层对象
SerialCommunication serialComm = new SerialCommunication("COM1", 9600);
//开启modbustcp服务器
StartCommModbus(serialComm);

...
//在开启服务器里面
public static async Task StartCommModbus(SerialCommunication serialComm)
{
    while (serialComm.isOpen)
    {
        // 接收数据
        byte[] buffer = await serialComm.ReceiveDataAsync();
        if (buffer.Length > 0)
        {
            PrintBytes(buffer, "请求 ");
            ADUMessage response = webModbusServer.HandleRequest(buffer);
            // 发送数据
            await serialComm.SendDataAsync(ADUMessage.Serialze(response));
            PrintBytes(ADUMessage.Serialze(response), "响应 ");
        }
    }
}

之后还要在主程序中添加一个使用串口的分支,以便我们指定使用那种方式传输数据。

效果

  • 客户端
    image

  • 服务端
    image

完整代码

SerialCommunication.cs
public class SerialCommunication
{
    private const byte SOH = 0x01; // 起始标志
    private const byte EOT = 0x04;   // 结束标志
    private const byte ESC = 0x1B;   // 透明填充

    private SerialPort serialPort;

    public bool isOpen;

    private Stack<byte[]> frames = new Stack<byte[]>();
    private object lockObject = new object();
    private TaskCompletionSource<bool> dataReceived = new TaskCompletionSource<bool>();

    public SerialCommunication(string portName, int baudRate)
    {
        isOpen = false;
        frames = new Stack<byte[]>();
        serialPort = new SerialPort(portName, baudRate);
        serialPort.Open();
        isOpen = true;
        readData();
    }

    private void readData()
    {
        Task.Run(() =>
        {
            byte[] frame = new byte[100]; // 假设最大帧长度为100字节
            int index = -1;
            while (true)
            {
                int rs= serialPort.BaseStream.ReadByte();
                if (rs == -1)
                {
                    index = -1;
                    continue;
                }
                byte b = (byte)rs;
                if (b == SOH) // 如果读到起始标志
                {
                    if (index>0 && frame[index-1]== ESC)
                    {
                        index--;
                        frame[index] = SOH;
                        index++;
                    }
                    else
                    {
                        index = 0;
                        frame[index] = b;
                        index++;
                    }
                }
                else if (b==ESC)
                {
                    if (index==-1)
                    {
                        //丢弃byte
                        continue;
                    }
                    else if (index > 0 && frame[index - 1] == ESC)
                    {
                        continue;
                    }
                    else
                    {
                        frame[index] = b;
                        index++;
                    }
                }
                else if (b == EOT) // 如果读到结束标志
                {
                    if (index == -1)
                    {
                        //丢弃byte
                        continue;
                    }
                    else if (index>0 && frame[index - 1] == ESC)
                    {
                        index--;
                        frame[index] = EOT;
                        index++;
                    }
                    else
                    {
                        frame[index] = EOT;
                        index++;
                        byte[] data = ParseFrame(frame, index);
                        if (data != null)
                        {
                            lock (lockObject)
                            {
                                frames.Push(data);
                                dataReceived.TrySetResult(true);
                            }
                            index = -1;
                        }
                    }
                }
                else
                {
                    if (index==-1)
                    {
                        //丢弃byte
                        continue;
                    }
                    else
                    {
                        frame[index] = b;
                        index++;
                    }
                }
            }
        });
    }

    // 发送数据
    public async Task SendDataAsync(byte[] data)
    {
        byte[] frame = EncapsulateFrame(data);
        await serialPort.BaseStream.WriteAsync(frame, 0, frame.Length);
    }

    // 接收数据
    public async Task<byte[]> ReceiveDataAsync()
    {
        byte[] frame;
        lock (lockObject)
        {
            if (frames.Count > 0)
            {
                frame = frames.Pop();
                return frame;
            }
        }
        // 没有数据时等待
        bool rs = await dataReceived.Task;
        frame = frames.Pop();
        lock (lockObject)
        {
            dataReceived = new TaskCompletionSource<bool>();
        }
        return frame;
    }

    // 封装数据帧
    private byte[] EncapsulateFrame(byte[] data)
    {
        byte[] frame = new byte[data.Length + 3];
        frame[0] = SOH;                // 添加起始标志
        Array.Copy(data, 0, frame, 1, data.Length); // 添加数据内容
        byte checksum = CalculateChecksum(data); // 计算校验字段
        frame[data.Length + 1] = checksum;       // 添加校验字段
        frame[data.Length + 2] = EOT;   // 添加结束标志
        //透明传输处理
        using (MemoryStream ms=new MemoryStream())
        {
            ms.Write(frame, 0, 1);
            for (global::System.Int32 i = 1; i < frame.Length-1; i++)
            {
                if (frame[i]==SOH || frame[i] == ESC || frame[i] == EOT)
                {
                    ms.Write(new byte[2] { ESC, frame[i] });
                }
                else
                {
                    ms.Write(new byte[1] { frame[i] });
                }
            }
            ms.Write(frame, (int)frame.Length-1, 1);
            byte[] bytes = ms.ToArray();
            //PrintBytes(bytes, "透明传输");
            return bytes;
        }
    }

    // 解析数据帧
    private byte[] ParseFrame(byte[] frame, int length)
    {
        byte checksum = frame[length - 2];
        byte[] data = new byte[length - 3];
        Array.Copy(frame, 1, data, 0, length - 3);
        if (CalculateChecksum(data) == checksum)
        {
            return data;
        }
        return null;
    }

    // 计算校验字段(简单求和校验)
    private byte CalculateChecksum(byte[] data)
    {
        int sum = 0;
        foreach (byte b in data)
        {
            sum += b;
        }
        return (byte)(sum % 256);
    }

    // 关闭串口
    public void Close()
    {
        isOpen = false;
        serialPort.Close();
    }

    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();
    }
}
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 SerialCommunication SerialComm { get; }
    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;
    }

    public WebModbusClient(SerialCommunication serialComm)
    {
        Transaction = 0x00;
        SerialComm = serialComm;
    }

    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 if (SerialComm!=null)
        {
            await SerialComm.SendDataAsync(ADUMessage.Serialze(request));
            byte[] bytes = await SerialComm.ReceiveDataAsync();
            this.response = ADUMessage.Deserialize(bytes);
            PrintBytes(bytes, "响应");
            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)));
            //丢弃可能的响应
            await WebSocket.ReceiveAsync(new ArraySegment<byte>(new byte[1024*4]), CancellationToken.None);
        }
        else if (WebSocket != null)
        {
            await WebSocket.SendAsync(new ArraySegment<byte>(ADUMessage.Serialze(request)), WebSocketMessageType.Binary, true, CancellationToken.None);
            //丢弃可能的响应
            await WebSocket.ReceiveAsync(new ArraySegment<byte>(new byte[1024 * 4]), CancellationToken.None);
        }
        else if (SerialComm != null)
        {
            await SerialComm.SendDataAsync(ADUMessage.Serialze(request));
            byte[] bytes = await SerialComm.ReceiveDataAsync();
            this.response = ADUMessage.Deserialize(bytes);
            PrintBytes(bytes, "响应");
        }
        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 == 2)
            {
                if (args[0]=="tcp")
                {
                    StartTcpServer(args[1]);
                }
                else if(args[0] == "websocket")
                {
                    StartWebSocketServer(args[1]);
                }
                else if (args[0] == "comm")
                {
                    StartCommServer(args[1]);
                }
            }
            //客户端
            else
            {
                if (args[0] == "tcp")
                {
                    Task.Run(async () =>
                    {
                        await StartClient(args);
                    }).Wait();
                }
                else if (args[0] == "websocket")
                {
                    Task.Run(async () =>
                    {
                        await StartWebsocketClient(args);
                    }).Wait();
                }
                else if (args[0] == "comm" && args[2]=="client")
                {
                    Task.Run(async () =>
                    {
                        await StartCommClient(args);
                    }).Wait();
                }
            }
        }

        private static void StartTcpServer(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 void StartWebSocketServer(string args)
        {

            int serverPort = Convert.ToInt32(args);
            WebsocketLisener websocketLisener = new WebsocketLisener(IPAddress.Parse("127.0.0.1"), serverPort);
            Console.WriteLine($"Websocket服务器  127.0.0.1:{serverPort}");
            Task.Run(async () =>
            {
                while (true)
                {
                    WebSocket websocketServer = await websocketLisener.AcceptWebsocketConnectionAsync();
                    StartWebsocketModbus(websocketServer);
                }
            }).Wait();
        }

        private static void StartCommServer(string args)
        {
            SerialCommunication serialComm = new SerialCommunication(args, 9600);
            Console.WriteLine($"串口服务器  {args}");
            Task.Run(async () =>
            {
                await StartCommModbus(serialComm);
            }).Wait();
        }

        private static async Task StartClient(string[] args)
        {
            int clientPort = Convert.ToInt32(args[1]);
            int serverPort = Convert.ToInt32(args[2]);
            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)
                {

                }
            }
        }

        private static async Task StartWebsocketClient(string[] args)
        {
            int clientPort = Convert.ToInt32(args[1]);
            int serverPort = Convert.ToInt32(args[2]);
            Uri uri = new($"ws://127.0.0.1:{serverPort}");
            ClientWebSocket ws = new();
            Console.WriteLine($"Websocket客户端");
            await ws.ConnectAsync(uri, default);
            Console.WriteLine($"连接到 127.0.0.1:{serverPort}");
            WebModbusClient webModbusClient = new WebModbusClient(ws);
            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)
                {

                }
            }
        }

        private static async Task StartCommClient(string[] args)
        {
            string clientPort = args[1];
            SerialCommunication serialComm = new SerialCommunication(clientPort, 9600);
            Console.WriteLine($"串口客户端  :{clientPort}");
            WebModbusClient webModbusClient = new WebModbusClient(serialComm);
            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: