首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C# 實戰 Modbus TCP 通訊:從協議解析到完整庫封裝

     編輯:關於C語言

Modbus TCP 是工業自動化領域最通用的協議之一。本文詳細拆解 **MBAP Header、功能碼、大小端轉換、事務標識匹配、緩衝區殘留處理**,並提供可直接落地的 C# 封裝庫代碼結構與核心邏輯。

一、 協議結構拆解

1. 數據包格式


[MBAP Header (7 bytes)] [Function Code (1 byte)] [Data (N bytes)]

2. MBAP Header (7 字節)

字段長度說明
Transaction Identifier2 bytes事務 ID,請求/響應配對,防止串包
Protocol Identifier2 bytes固定 0x0000 (Modbus 協議)
Length2 bytes後續字節數 (Unit ID + Function Code + Data)
Unit Identifier1 byte從站地址 (單機場景通常 0x00/0xFF)

MBAP Header 結構

3. 核心功能碼

  • 0x03 (3):Read Holding Registers (讀保持寄存器)
  • 0x10 (16):Write Multiple Registers (寫多個寄存器)

二、 大小端轉換:跨平台數據一致性

Modbus 規定 **Big-Endian (網絡序)**。x86/x64 為 Little-Endian,必須轉換。

1. 運行時檢測端序


bool isLittleEndian = BitConverter.IsLittleEndian; // .NET 內置屬性
// 或手動檢測
unsafe { int t = 1; bool le = (*(byte*)&t) == 1; }

2. 封裝轉換助手類 (策略模式)


public abstract class ValueHelper {
    public static readonly ValueHelper Instance = BitConverter.IsLittleEndian 
        ? new LittleEndianValueHelper() : new BigEndianValueHelper();
    public abstract byte[] GetBytes(int value);
    public abstract int GetInt32(byte[] data);
}

internal class LittleEndianValueHelper : ValueHelper {
    public override byte[] GetBytes(int v) => BitConverter.GetBytes(v).Reverse().ToArray();
    public override int GetInt32(byte[] d) => BitConverter.ToInt32(d.Reverse().ToArray(), 0);
}

三、 事務標識與緩衝區殘留:可靠通訊的關鍵

1. Transaction Identifier 生成與驗證


private byte _tid = 0;
protected byte NextTid() => ++_tid; // 發送時賦值
// 接收響應時驗證
if (responseTid != _currentTid) DiscardPacket(); // 丟棄舊/亂序包

2. 緩衝區殘留數據處理策略

痛點:TCP 流無邊界,異常斷開/重發導致緩衝區殘留數據污染下次讀取。

方案對比:

策略優點缺點
每次讀超大緩衝 (如 256B) 忽略多餘簡單,無需關閉 Socket浪費帶寬,高併發下內存壓力
讀完精確長度,錯誤時關閉重連 Socket徹底清理狀態重連開銷大,可能丟失進行中的事務
推薦:長度前綴 + 狀態機解析 + 異常重置健壯、高效、符合 TCP 流特性實現復雜度較高

文中採用「讀 256B 忽略多餘」作為暫時方案,並建議後續升級為狀態機解析器。

四、 核心收發流程代碼

1. 寫入多個寄存器 (功能碼 0x10)


public override void Send(byte[] data) {
    // 1. 構建請求幀
    var frame = new List<byte>(255);
    frame.AddRange(ValueHelper.Instance.GetBytes(NextTid())); // TID
    frame.AddRange(new byte[] { 0, 0 });                     // PID
    frame.AddRange(ValueHelper.Instance.GetBytes((ushort)(data.Length + 7))); // Length
    frame.Add(0);                                            // UID
    frame.Add((byte)FunctionCode.Write);                     // FC=16
    frame.AddRange(ValueHelper.Instance.GetBytes(StartingAddress)); // Addr
    frame.AddRange(ValueHelper.Instance.GetBytes((ushort)(data.Length / 2))); // Qty
    frame.Add((byte)data.Length);                            // Byte Count
    frame.AddRange(data);                                    // Data
    
    // 2. 發送
    _socketWrapper.Write(frame.ToArray());
    Application.DoEvents(); // 簡易讓出 UI 線程
    
    // 3. 讀取響應 (固定 12 字節)
    byte[] resp = _socketWrapper.Read(12);
    // TODO: 校驗響應 TID、FC、錯誤碼
}

2. 讀取多個寄存器 (功能碼 0x03)


public override byte[] Receive() {
    var frame = new List<byte>(255);
    frame.AddRange(ValueHelper.Instance.GetBytes(NextTid()));
    frame.AddRange(new byte[] { 0, 0 });
    frame.AddRange(ValueHelper.Instance.GetBytes((short)6)); // Length=6 (UID+FC+Addr+Qty)
    frame.Add(0);
    frame.Add((byte)FunctionCode.Read); // FC=3
    frame.AddRange(ValueHelper.Instance.GetBytes(StartingAddress));
    frame.AddRange(ValueHelper.Instance.GetBytes((short)30)); // Qty=30
    
    _socketWrapper.Write(frame.ToArray());
    Application.DoEvents();
    
    // 讀取響應頭 (8 字節) + 數據
    byte[] header = _socketWrapper.Read(8); // 實際應動態解析 Length
    byte[] all = _socketWrapper.Read(256);  // 讀大緩衝防殘留
    
    // 校驗 TID
    ushort tid = (ushort)((all[0] << 8) | all[1]);
    if (tid != _currentTid) return Array.Empty<byte>();
    
    byte byteCount = all[8];
    byte[] result = new byte[byteCount];
    Array.Copy(all, 9, result, 0, byteCount);
    return result;
}

五、 類圖與擴展建議

  • IModbusWrapper 介面定義 Send/Receive/Connect。
  • ModbusTcpWrapper 實現 TCP 通訊邏輯。
  • ModbusRtuWrapper 預留串口實現 (文章未完成)。
  • ValueHelper 策略類隔離大小端邏輯。

源碼下載:CSharpModBusExample

六、 生產環境強化清單

  1. **完整幀解析器**:基於 Length 字段動態讀取,而非固定長度。
  2. **異常重試與重連**:指數退避、斷線重連、事務 ID 重置。
  3. **並發控制**:SemaphoreSlim 串行化請求/響應,避免多線程串包。
  4. **單元測試**:模擬網絡斷點、亂序、CRC 錯誤、異常功能碼。
  5. **診斷日誌**:記錄原始幀十六進制、耗時、錯誤碼,便於現場排查。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved