基於WebSocket的modbus通訊(三)- websocket和串列埠

ggtc發表於2024-06-02

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連線");
        }
    }

相關文章