基於WebSocket的modbus通訊(一)- 伺服器

ggtc發表於2024-05-31

ModbusTcp協議是基於tcp的,但不是說一定要透過tcp協議才能傳輸,只要能傳輸二進位制的地方都可以。比如WebSocket協議。
但由於目前我只有tcp上面的modbus伺服器實現,所以我必須先用tcp連線藉助已有工具來驗證我的伺服器是否寫正確。

效果

image

image

ModBusTCP協議報文

ModBusTCP協議報文比較複雜,主要是區分了3組型別

  • 請求和響應
  • 讀和寫
  • 多個資料和單個資料

理論上將報文格式的種類從1種變成了8種。
但是在響應讀資料時,沒有區分資料量,而是到請求中去查詢,聽說是為了減少傳輸量。因此實際格式種類是3組6種。

  1. 請求讀資料包文

    事務識別符號 協議識別符號 報文長度 單元識別符號 功能碼 起始地址 讀取數量
    欄位長度 2 byte 2 byte 2 byte 1 byte 1 byte 2 byte 2 byte
    資料型別 ushort ushort ushort byte byte ushort ushort
    自增 一般為0 6,從單元識別符號開始算 一般為0 連結
  2. 響應讀資料包文

    事務識別符號 協議識別符號 報文長度 單元識別符號 功能碼 資料位元組數 資料
    2 byte 2 byte 2 byte 1 byte 1 byte 1 byte n byte

    值得注意的是,響應報文中沒有包含請求的資料數量,只能從請求中獲得。比如是7個線圈還是8個線圈是看不出來的。
    這其實就限制了程式結構,應該在同一個上下文中宣告請求和響應變數。

  3. 請求寫單個資料包文

    事務識別符號 協議識別符號 報文長度 單元識別符號 功能碼 起始地址 資料
    2 byte 2 byte 2 byte 1 byte 1 byte 2 byte 2 byte
  4. 響應寫單個資料包文

    事務識別符號 協議識別符號 報文長度 單元識別符號 功能碼 起始地址 資料
    2 byte 2 byte 2 byte 1 byte 1 byte 2 byte 2 byte

    請求寫單個資料的響應實際上是原樣返回請求。
    寫1位線圈和寫16位暫存器都傳輸了2個位元組,這很奇怪。
    原來是ModBusTCP定義寫單個線圈用0xff00表示1,0x0000表示0。

  5. 請求寫多個資料包文

    事務識別符號 協議識別符號 報文長度 單元識別符號 功能碼 起始地址 數量 位元組數 資料
    2 byte 2 byte 2 byte 1 byte 1 byte 2 byte 2 byte 1 byte n byte

    請求寫多個資料時,如果是線圈,現在又變成了一位代表一個資料,而不是原來的2位元組代表單個資料
    還需要注意的是modbus採用大端傳輸,意味著一個位元組中如果寫了四個線圈,要從右向左數。

  6. 響應寫多個資料包文

    事務識別符號 協議識別符號 報文長度 單元識別符號 功能碼 起始地址 數量
    2 byte 2 byte 2 byte 1 byte 1 byte 2 byte 2 byte

報文類定義

從功能碼欄位往後,欄位不確定,所以統一用Data表示。
在各個響應方法里根據不同功能碼對資料進行處理。

//WebModbus.cs

// Modbus報文
public class ADUMessage
{
    // 事務識別符號
    public ushort Transaction { get; set; }
    // 協議識別符號
    public ushort Protocol { get; set; }
    // 報文長度
    public ushort Length { get; set; }
    // 單元識別符號
    public byte Unit { get; set; }
    // 功能碼
    public byte FunctionCode { get; set; }
    // 資料
    public byte[] Data { get; set; }

    public static ADUMessage Deserialize(byte[] buffer){}

    public static byte[] Serialze(ADUMessage message){}
}

資料模型

modbusTCP協議規定了伺服器實現線圈、暫存器2種資料模型,每種又分為只讀、可讀可寫。於是伺服器一共要提供4個儲存棧。
因為報文起始地址欄位有2個位元組,所以定址範圍是0-65535

棧地址 線圈棧 離散量輸入棧 只讀暫存器棧 讀寫暫存器棧
65535 0 0 0xFF C1 0x00 00
65534 0 1 0x00 00 0x00 00
... 1 0 0x00 00 0x00 00
1 0 0 0x00 00 0x08 00
0 0 0 0x00 00 0x00 1A

其中線圈棧和離散量輸入棧是bool型別的,實際實現的時候可以是數字0、1,也可以是布林true,false,還可以是字串"on","off"
如何儲存,儲存的資料型別是什麼?這不重要。重要的是傳輸的時候採用規定格式就行。

  • 傳輸格式
    • 採用第3種報文寫單線圈時,如果要傳輸true,要使用0xFF 00。傳輸false,要使用0x00 00

    • 其它時候都用一個bit位表示true和false
      比如線圈棧,從地址0開始讀9個資料。就是小端位元組序0x0f 00。

      0 1 2 3 4 5 6 7 8 ...
      1 1 1 1 0 0 0 0 0 0

      當然,要從請求報文中知道擷取取響應資料的哪幾位。

  • 模型類定義
//WebModbus.cs

// 資料倉儲,144KB
public class DataStore
{
    // 讀寫16位暫存器,64KB
    public ushort[] HoldingRegisters;
    // 只讀16位暫存器,64KB
    public ushort[] InputRegisters;
    // 讀寫1位線圈,8KB
    public bool[] CoilDiscretes;
    // 只讀1位線圈,8KB
    public bool[] CoilInputs;

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

    // 讀 讀寫16位暫存器
    public ushort[] ReadHoldingRegisters(ushort startIndex, ushort length){}
    // 讀 只讀16位暫存器
    public ushort[] ReadInputRegisters(ushort startIndex, ushort length){}
    // 讀 讀寫1位線圈
    public bool[] ReadCoilDiscretes(ushort startIndex, ushort length){}
    // 讀 只讀1位線圈
    public bool[] ReadCoilInputs(ushort startIndex, ushort length){}
    // 寫 讀寫16位暫存器
    public void WriteHoldingRegisters(ushort startIndex, ushort[] data){}
    //寫 讀寫1位線圈
    public void WriteCoilDiscretes(ushort startIndex, bool[] data){}
}

功能碼

功能碼主要用來操作這4個棧,因為棧分讀寫和只讀,寫入還分為多個資料和單個資料。所以主要有8種功能碼

  • 0x01 讀線圈棧
  • 0x02 讀離散量輸入棧
  • 0x03 讀只讀暫存器棧
  • 0x04 讀讀寫暫存器棧
  • 0x05 寫一個資料到線圈棧
  • 0x06 寫一個資料到讀寫暫存器棧
  • 0x0F 寫多個資料到線圈棧
  • 0x10 寫多個資料到讀寫暫存器棧
  • 當然還有其它的,但不影響主要功能,這裡就不說明了

相應的

  • 第1、2種報文使用01、02、03、04功能碼
  • 第3、4種報文使用05、06功能碼
  • 第5、6種報文使用0F、10功能碼

位元組序大小端

位元組序這個東西還是當初上作業系統課時老師講過,以後再沒接觸。不過要自己處理位元組流時又面臨這個問題了。
modbusTcp主要採用大端位元組序方式傳輸資料。
為什麼說主要呢,因為唯獨線圈資料是採用小端的方式傳輸的,其它資料都是大端傳輸。這要特別注意。

  • C#實現時可以使用BinaryReaderBinaryWriter來處理接收到的和要傳輸的位元組流。
    但這兩個類預設都是按照小端位元組序來處理資料。比如reader.ReadUInt16(),如果我們傳的位元組流是0x00 01,它會以為是ushort 256,而不是ushort 1。
    所以需要我們過載override這個方法,翻轉位元組陣列,按照大端方式讀取。
//BigEndianBinaryReader.cs

public override short ReadInt16()
{
    var data = base.ReadBytes(2);
    Array.Reverse(data);
    return BitConverter.ToInt16(data, 0);
}

伺服器處理流程

modbus伺服器要做的就是

  • 接收訊息,反序列化為訊息物件

  • 然後判斷怎麼響應

  • 讀取資料棧,構造訊息

  • 序列化後響應

  • 核心方法

//WebModbus.cs

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

完整程式碼

程式所需命令列引數的一種方式是在專案檔案種指定,這在除錯時比較方便

<PropertyGroup>
	<StartArguments>5234</StartArguments>
</PropertyGroup>
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();
        }
    }
}

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;
    }
}
BigEndianBinaryReader.cs
public class BigEndianBinaryReader : BinaryReader
{
    public BigEndianBinaryReader(Stream input) : base(input)
    {
    }

    public override short ReadInt16()
    {
        var data = base.ReadBytes(2);
        Array.Reverse(data);
        return BitConverter.ToInt16(data, 0);
    }

    public override ushort ReadUInt16()
    {
        var data = base.ReadBytes(2);
        Array.Reverse(data);
        return BitConverter.ToUInt16(data, 0);
    }

    public override int ReadInt32()
    {
        var data = base.ReadBytes(4);
        Array.Reverse(data);
        return BitConverter.ToInt32(data, 0);
    }

    public override uint ReadUInt32()
    {
        var data = base.ReadBytes(4);
        Array.Reverse(data);
        return BitConverter.ToUInt32(data, 0);
    }

    public override long ReadInt64()
    {
        var data = base.ReadBytes(8);
        Array.Reverse(data);
        return BitConverter.ToInt64(data, 0);
    }

    public override float ReadSingle()
    {
        var data = base.ReadBytes(4);
        Array.Reverse(data);
        return BitConverter.ToSingle(data, 0);
    }

    public override double ReadDouble()
    {
        var data = base.ReadBytes(8);
        Array.Reverse(data);
        return BitConverter.ToDouble(data, 0);
    }

    // 可以繼續新增其他方法來支援更多資料型別的大端讀取
}
public class BigEndianBinaryWriter : BinaryWriter
{
    public BigEndianBinaryWriter(Stream input) : base(input)
    {
    }

    public override void Write(ushort value)
    {
        var bytes = BitConverter.GetBytes(value);
        Array.Reverse(bytes);
        base.Write(bytes);
    }

    public override void Write(short value)
    {
        var bytes = BitConverter.GetBytes(value);
        Array.Reverse(bytes);
        base.Write(bytes);
    }

    public override void Write(uint value)
    {
        var bytes = BitConverter.GetBytes(value);
        Array.Reverse(bytes);
        base.Write(bytes);
    }

    public override void Write(int value)
    {
        var bytes = BitConverter.GetBytes(value);
        Array.Reverse(bytes);
        base.Write(bytes);
    }

    public override void Write(ulong value)
    {
        var bytes = BitConverter.GetBytes(value);
        Array.Reverse(bytes);
        base.Write(bytes);
    }

    public override void Write(long value)
    {
        var bytes = BitConverter.GetBytes(value);
        Array.Reverse(bytes);
        base.Write(bytes);
    }



    // 可以繼續新增其他方法來支援更多資料型別的大端寫入
}
Program.cs
    internal class Program
    {
        static WebModbusServer webModbusServer;
        static void Main(string[] args)
        {
            webModbusServer = new WebModbusServer();
            //伺服器
            if (args.Length == 1)
            {
                //webModbusServer.store.WriteCoilDiscretes(0, new bool[] { true, true });
                //webModbusServer.store.CoilInputs[0] = true;
                //webModbusServer.store.CoilInputs[1] = true;
                StartServer(args[0]);
            }
        }

        private static void StartServer(string args)
        {

            int serverPort = Convert.ToInt32(args);
            var server = new TcpListener(IPAddress.Parse("127.0.0.1"), serverPort);
            Console.WriteLine($"TCP伺服器  127.0.0.1:{serverPort}");
            server.Start();
            int cnt = 0;
            Task.Run(async () =>
            {
                List<TcpClient> clients = new List<TcpClient>();
                while (true)
                {
                    TcpClient client = await server.AcceptTcpClientAsync();
                    clients.Add(client);
                    cnt++;
                    var ep = client.Client.RemoteEndPoint as IPEndPoint;
                    Console.WriteLine($"TCP客戶端_{cnt}  {ep.Address}:{ep.Port}");
                    //給這個客戶端開一個聊天執行緒
                    //作業系統將會根據遊客埠對應表將控制權交給對應遊客執行緒
                    StartModbus(client);
                }
            }).Wait();
        }

        public static async Task StartModbus(TcpClient client)
        {
            var buffer = new byte[1024 * 4];
            while (client.Connected)
            {
                int msgLength = await client.Client.ReceiveAsync(new ArraySegment<byte>(buffer));
                //關閉連線時會接收到一次空訊息,不知道為什麼
                if (msgLength>0)
                {
                    PrintBytes(buffer.Take(msgLength).ToArray(), "請求 ");
                    ADUMessage response = webModbusServer.HandleRequest(buffer.Take(msgLength).ToArray());
                    await client.Client.SendAsync(ADUMessage.Serialze(response));
                    PrintBytes(ADUMessage.Serialze(response), "響應 ");
                }
            }
        }

        public static void PrintBytes(byte[] bytes,string prefix="")
        {
            Console.Write(prefix);
            for (int i = 0; i < bytes.Length; i++)
            {
                if (i < 2)
                {
                    Console.ForegroundColor = ConsoleColor.Red;
                }
                else if(i<4)
                {
                    Console.ForegroundColor = ConsoleColor.Green;
                }
                else if(i<6)
                {
                    Console.ForegroundColor= ConsoleColor.Blue;
                }
                else if (i < 7)
                {
                    Console.ForegroundColor = ConsoleColor.Yellow;
                }
                else if (i<8)
                {
                    Console.ForegroundColor = ConsoleColor.DarkCyan;
                }
                else
                {
                    Console.ForegroundColor = ConsoleColor.White;
                }
                Console.Write(bytes[i].ToString("X2") + " ");
            }
            Console.WriteLine();
        }
    }

相關文章