首页 > 其他分享 >基于WebSocket的modbus通信(三)- websocket和串口

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

时间:2024-06-02 19:45:01浏览次数:20  
标签:websocket ushort request modbus startIndex 串口 new byte data

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:
                            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 async Task StartWebsocketModbus(WebSocket websocketServer)
        {
            var buffer = new byte[1024 * 4];
            while (!websocketServer.CloseStatus.HasValue)
            {
                var result = await websocketServer.ReceiveAsync(new ArraySegment<byte>(buffer),CancellationToken.None);
                if (result.Count > 0)
                {
                    PrintBytes(buffer.Take(result.Count).ToArray(), "请求 ");
                    ADUMessage response = webModbusServer.HandleRequest(buffer.Take(result.Count).ToArray());
                    await websocketServer.SendAsync(ADUMessage.Serialze(response),WebSocketMessageType.Binary,true,CancellationToken.None);
                    PrintBytes(ADUMessage.Serialze(response), "响应 ");
                }
            }
        }

        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), "响应 ");
                }
            }
        }

        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();
        }
    }

    public class HttpRequet
    {
        /// <summary>
        /// 解析HTTP消息
        /// </summary>
        public HttpRequet(string str)
        {
            Str = str;
            //开始行
            var startLine = str.Split("\r\n")[0];
            var lines = startLine.Split("\r\n");
            httpMethod = lines[0].Split(' ')[0];
            path = lines[0].Split(' ')[1];
            //头部
            var headerslines = str.Split("\r\n\r\n")[0].Split("\r\n");
            headers = new Dictionary<string, string>();
            for (int i = 1; i < headerslines.Length; i++)
            {
                var header = headerslines[i].Split(": ");
                headers.Add(header[0], header[1]);
            }
        }

        /// <summary>
        /// 请求原始消息
        /// </summary>
        public string Str { get; }
        /// <summary>
        /// 请求方法
        /// </summary>
        public string httpMethod { get; internal set; }
        /// <summary>
        /// 请求路径
        /// </summary>
        public string path { get; set; }
        /// <summary>
        /// 头部字段
        /// </summary>
        public Dictionary<string, string> headers { get; set; }

        /// <summary>
        /// 判断是否是转协议的请求
        /// </summary>
        /// <returns></returns>
        public bool IsWebsocket()
        {
            if (this.headers.ContainsKey("Connection") && this.headers["Connection"] == "Upgrade" && this.headers.ContainsKey("Upgrade") && this.headers["Upgrade"] == "websocket")
                return true;
            else
                return false;
        }

        /// <summary>
        /// 响应转协议请求并未用当前连接创建一个WebSocket对象
        /// </summary>
        /// <param name="client"></param>
        /// <returns></returns>
        public async Task<WebSocket> AcceptWebsocket(TcpClient client, string Sec_WebSocket_Key)
        {
            using (MemoryStream memoryStream = new MemoryStream())
            {
                string header = @$"HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: {GenerateResponseKey(Sec_WebSocket_Key)}

";
                memoryStream.Write(new ArraySegment<byte>(ASCIIEncoding.ASCII.GetBytes(header)));
                await client.Client.SendAsync(new ArraySegment<byte>(memoryStream.ToArray()));
                Console.WriteLine(header);

                return WebSocket.CreateFromStream(client.GetStream(), true, null, TimeSpan.FromSeconds(10));
            }
        }

        public static string GenerateResponseKey(string requestKey)
        {
            const string guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
            string concatenated = requestKey + guid;
            byte[] hashed = System.Security.Cryptography.SHA1.Create().ComputeHash(Encoding.UTF8.GetBytes(concatenated));
            return Convert.ToBase64String(hashed);
        }
    }

    public class WebsocketLisener
    {
        public TcpListener server { get; set; }
        public int cnt;
        public WebsocketLisener(IPAddress address,int port)
        {
            cnt = 0;
            server = new TcpListener(address, port);
            server.Start();
        }
        public async Task<WebSocket> AcceptWebsocketConnectionAsync()
        {
            TcpClient client = await server.AcceptTcpClientAsync();
            var buffer = new byte[1024 * 4];
            int msgLength = await client.Client.ReceiveAsync(new ArraySegment<byte>(buffer));
            string str = UTF8Encoding.UTF8.GetString(buffer, 0, msgLength);
            HttpRequet request = new HttpRequet(str);
            if (request.IsWebsocket())
            {
                cnt++;
                WebSocket webSocket = await request.AcceptWebsocket(client, request.headers["Sec-WebSocket-Key"]);
                var ep = client.Client.RemoteEndPoint as IPEndPoint;
                Console.WriteLine($"Websocket客户端_{cnt}  {ep.Address}:{ep.Port}");
                return webSocket;
            }
            throw new Exception("不是WebSocket连接");
        }
    }

标签:websocket,ushort,request,modbus,startIndex,串口,new,byte,data
From: https://www.cnblogs.com/ggtc/p/18226329

相关文章

  • NET工控,上位机,Modbus485网口/串口通讯(鸣志步进电机,鸣志伺服电机,松下伺服电机,华庆
    先上两个通用Modbus帮助类,下面这个是多线程不安全版,在多线程多电机同一端口通信下,可能造成步进电机丢步或者输出口无响应等,还有个多线程安全版,只是基于这个不安全版加上了LOCK,THISusingModbus.Device;usingSunny.UI;usingSystem;usingSystem.IO.Ports;usingSystem.Li......
  • (11.1)iic串口读写EEPROM实验:EEPROM介绍
    一、EEPROM简介EEPROM(ElectricallyErasableProgrammableReadOnlyMemory),带电可擦除可编程只读存储器,是一种掉电后数据不丢失的非易失性存储器,用户可以通过高于普通电压的作用来擦除和编程(重写)非易失性存储器主要包括:EEPROM:以字节为单位改写;结构复杂,成本高;存储......
  • 基于WebSocket的modbus通信(二)- 客户端
    上一篇已经实现了ModbusTcp服务器和8个主要的功能码,只是还没有实现错误处理功能。但是在测试客户端时却发现了上一篇的一个错误,那就是写数据成功,服务器不需要响应。接下来要做的就是实现ModbusTcp客户端。有了清晰的协议,代码循规蹈矩的写就行了。效果原始数据其中只读寄存......
  • 5.SpringTask,WebSocket
    SpringTask,WebSocketSpringTask介绍:SpringTask是Spring框架提供的任务调度工具,可以按照约定的时间自动执行某个代码逻辑;定位:定时任务框架作用:定时自动执行某段Java代码;应用场景:信用卡每月还款提醒;银行贷款每月还款提醒;火车票售票系统处理未支付订单;入职纪念日为......
  • 基于WebSocket的modbus通信(一)- 服务器
    ModbusTcp协议是基于tcp的,但不是说一定要通过tcp协议才能传输,只要能传输二进制的地方都可以。比如WebSocket协议。但由于目前我只有tcp上面的modbus服务器实现,所以我必须先用tcp连接借助已有工具来验证我的服务器是否写正确。效果ModBusTCP协议报文ModBusTCP协议报文比较复......
  • WiFi串口服务器与工业路由器:局域网应用的协同之力
    在工业物联网(IIoT)迅猛发展的当下,局域网(LAN)作为连接工业设备与数据中心的桥梁,其重要性日益凸显。WiFi串口服务器与工业路由器作为局域网中的关键组件,以其独特的性能和功能,为传统行业的数字化转型提供了强有力的支持。本文将深入探讨WiFi串口服务器与工业路由器如何协同工作,以实现......
  • 工控现场什么情况下会采用Modbus转Profinet网关
    工控现场什么情况下会采用Modbus转Profinet网关一、原因:工控现场需要将Modbus转换为Profinet协议,这是由于在工业控制领域中,不同设备之间的通信协议多样,而Modbus和Profinet分别代表着两种不同的通信协议。Modbus是一种最常见的工业通信协议,但其速度较慢,数据量有限,在一些需要快速......
  • 基恩士PLC与ModbusTCP转Profibus网关实现与激光设备的高效连接
    本文将探讨如何通过使用基恩士PLC以及无锡耐特森ModbusTCP转Profibus网关来实现与激光设备的高效连接。在当今工业自动化领域,不同厂商的硬件设备和软件系统之间的互联互通性成为了提高生产效率、实现智能制造的关键因素。其中,可编程逻辑控制器(PLC)作为工业控制的核心,与其他设备的通......
  • golang开发 gorilla websocket的使用
    很多APP都需要主动向用户推送消息,这就需要用到长连接的服务,即我们通常提到的websocket,同样也是使用socket服务,通信协议是基本类似的,在go中用的最多的、也是最简单的socket服务就是gorilla/websocket,它有21.1K的star,足以说明它的受欢迎程度,它的github地址是https://github.com/g......
  • c#使用Modbus TCP
    c#使用ModbusTCP在C#中,使用第三方库来实现ModbusTCP通讯。其中比较常用的是EasyModbusTCP库。以下是使用EasyModbusTCP库进行ModbusTCP通讯的示例代码:首先需要安装EasyModbusTCP库,可以通过NuGet包管理器来安装。1.创建一个ModbusClient对象,并设置连接参数:usingEasy......