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

C# 串口通訊實作 SMS 簡訊發送完整原始碼解析

     編輯:關於C語言

專案背景

早期 GSM Modem/手機透過序列埠 (COM Port) 以 AT 指令集發送簡訊,是嵌入式系統、工業監控、早期企業通訊系統的常見方案。本文解析一個完整的 WinForms 專案,涵蓋 MSComm 控制項封裝、PDU 模式編碼、中文 Unicode 處理、非同步收發流程。

架構概覽

[UI 層] FrmMain
  ├─ 通訊設定區 (COM Port, Baud Rate, 連線/斷開)
  ├─ 簡訊發送區 (簡訊中心號碼, 手機號, 有效期, 內容)
  └─ 狀態顯示區 (原始回應, 發送狀態)
       ↓
[通訊核心] AxMSComm (ActiveX Wrapper)
  ├─ PortOpen / Settings / Output / Input
  └─ OnComm 事件 (資料到達通知)
       ↓
[協定層] AT 指令 + PDU 編碼
  ├─ AT 指令流程控制 (連線測試、設定 PDU 模式、發送)
  └─ PDU 編碼器 (UCS2 中文支援、號碼半位元組交換、時間戳)

關鍵技術點深度解析

1. MSComm 控制項初始化與連線流程

// 設計器自動產生的初始化 (關鍵屬性)
this.MSCOMM.Settings = "19200,n,8,1"; // Baud, Parity, DataBits, StopBits
this.MSCOMM.PortOpen = true;          // 開啟埠口
this.MSCOMM.RThreshold = 1;           // 收到 1 位元組觸發 OnComm
this.MSCOMM.InputLen = 0;             // 讀取緩衝區所有資料

// 連線測試流程 (btnCnn_Click)
private void TestConnection()
{
    MSCOMM.Output = "AT\r";           // 發送測試指令
    // 等待 "OK\r\n" 回應,逾時 2.5 秒
    string buffer = "";
    int start = Environment.TickCount;
    while (Environment.TickCount - start < 2500)
    {
        Application.DoEvents();       // 保持 UI 回應
        buffer += MSCOMM.Input;       // 累積接收資料
        if (buffer.Contains("OK\r\n")) break;
    }
    // 判斷成功/失敗並更新 UI 狀態
}

2. PDU 模式完整編碼流程 (核心難點)

PDU (Protocol Description Unit) 是 GSM 規定的二進位封裝格式。發送中文簡訊需使用 UCS2 編碼 (16-bit Unicode)。

private string BuildPduString(string centerNumber, string mobile, string message, string validityPeriod)
{
    // 1. 簡訊中心號碼編碼 (BCD 半位元組交換 + 長度 + TON/NPI)
    // 範例: +886932123456 → 長度 08, TON/NPI 91, 號碼 868392214365F0
    string pdu = "00"; // SMSC 由網路提供,這裡填 00 表示使用預設
    
    // 2. 第一個位元組: TP-MTI(2bit) + TP-VPF(2bit) + TP-RP + TP-UDHI + TP-SRR
    // 0x11 = 0001 0001 → SMS-SUBMIT, 有效期相對格式, 不要求狀態報告
    pdu += "11";
    
    // 3. TP-MR (訊息參考號) 00 讓網路自動指派
    pdu += "00";
    
    // 4. 目的號碼: 長度(BCD) + TON/NPI(91=國際號碼) + 號碼半位元組交換
    pdu += mobile.Length.ToString("X2"); // 長度
    pdu += "91";                         // 國際格式
    pdu += SwapNibbles(mobile);          // 0912345678 → 9021436587
    
    // 5. TP-PID (協定識別碼) 00 = 隱含
    pdu += "00";
    
    // 6. TP-DCS (資料編碼方案) 08 = UCS2 (16-bit Unicode)
    pdu += "08";
    
    // 7. TP-VP (有效期) 範例: A7 = 1 天
    pdu += validityPeriod;
    
    // 8. TP-UDL (用戶資料長度) = 位元組數 (UCS2 每字 2 位元組)
    byte[] msgBytes = Encoding.BigEndianUnicode.GetBytes(message); // UCS2 Big Endian
    pdu += msgBytes.Length.ToString("X2");
    
    // 9. TP-UD (用戶資料) - 直接附加 UCS2 位元組的 Hex 字串
    pdu += BitConverter.ToString(msgBytes).Replace("-", "");
    
    // 10. 總長度 (不含前兩位 SMSC 長度) 用於 AT+CMGS 長度參數
    int pduLength = (pdu.Length / 2) - 1; // 減去 SMSC 長度位元組
    
    return (pdu, pduLength);
}

// 半位元組交換輔助函式
private string SwapNibbles(string input)
{
    string s = input + "F"; // 奇數長度補 F
    var sb = new StringBuilder();
    for (int i = 0; i < s.Length; i += 2)
        sb.Append(s[i+1]).Append(s[i]);
    return sb.ToString();
}

3. 發送流程與回應處理

private void SendSms(string pdu, int pduLength)
{
    // 1. 設定 PDU 模式
    MSCOMM.Output = "AT+CMGF=0\r";
    WaitFor("OK", 2000);
    
    // 2. 發送 CMGS 指令,長度 = PDU 位元組數
    MSCOMM.Output = $"AT+CMGS={pduLength}\r";
    WaitFor("> ", 2000); // 等待提示符
    
    // 3. 發送 PDU 內容 + Ctrl+Z (0x1A) 結束
    MSCOMM.Output = pdu + (char)26;
    
    // 4. 等待最終回應 (最長 5.5 秒)
    string result = WaitFor("OK", 5500);
    if (result.Contains("OK")) ShowSuccess();
    else ShowFailure();
}

private string WaitFor(string expected, int timeoutMs)
{
    string buffer = "";
    int start = Environment.TickCount;
    while (Environment.TickCount - start < timeoutMs)
    {
        Application.DoEvents();
        buffer += MSCOMM.Input;
        if (buffer.Contains(expected)) return buffer;
    }
    return buffer; // 逾時返回已收內容
}

4. 介面輸入驗證與狀態管理

  • KeyPress 事件限制簡訊中心號碼只能輸入 +0-9、手機號碼只能輸入 0-9
  • 狀態列顯示版權、作者、網址資訊
  • 按鈕啟用/停用邏輯:連線成功 → 啟用發送/斷開;發送中 → 停用所有操作

完整專案結構建議

SmsSender/
├── Models/
│   ├── PduMessage.cs          # PDU 封裝類別
│   ├── SmsSubmitPdu.cs        # 發送 PDU 建構器
│   └── SmsDeliverPdu.cs       # 接收 PDU 解析器 (擴充)
├── Services/
│   ├── SerialPortWrapper.cs   # 封裝 SerialPort (替代 MSComm)
│   ├── AtCommandExecutor.cs   # AT 指令流程控制
│   └── SmsService.cs          # 高階發送/接收 API
├── UI/
│   └── MainForm.cs            # WinForms 介面
└── Program.cs

現代化遷移建議

  1. 替換 MSComm:改用 System.IO.Ports.SerialPort (標準 .NET 類別,無 ActiveX 相依)
  2. 非同步重構:Task.Run + async/await 避免 Application.DoEvents() 造成重入問題
  3. PDU 庫獨立化:抽離為 NuGet 套件,支援長簡訊 (Concatenated SMS)、Class 0/1/2/3 簡訊
  4. 多埠支援:連線池管理多個 Modem,實現併發發送
  5. 通訊協定擴充:支援 HTTP SMPP、REST API (Twilio、Vonage、國內簡訊閘道商)

常見問題排查

現象可能原因解決方向
AT 指令無回應接線錯誤、Baud Rate 不符、Modem 未就緒檢查硬體、嘗試 9600/115200、加啟動延遲
發送失敗 CMS ERROR號碼格式錯、中心號碼錯、SIM 卡欠費/鎖定查閱 GSM 07.05 錯誤碼表、確認 PDU 編碼正確性
中文顯示亂碼DCS 未設 0x08 (UCS2)、編碼非 Big Endian強制使用 Encoding.BigEndianUnicode
收到亂碼/不完整讀取時機過早、緩衝區殘留OnComm 累積緩衝、以 \r\n 或 > 為界定符解析

結語

雖然現代簡訊發送多已轉向 HTTP API/SMPP,但序列埠 AT 指令模式在離線環境、工業現場、成本敏感專案仍具價值。深入理解 PDU 編碼、AT 指令流程、序列埠非同步處理,不僅能維護既有系統,更能培養底層通訊協定實作的核心功力。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved