短信編碼

目前,發送短消息常用Text和PDU(Protocol Data Unit,協議數據單元)模式。使用Text模式收發短信代碼簡單,實現起來十分容易,但最大的缺點是不能收發中文短信;而PDU模式不僅支持中文短信,也能發送英文短信。PDU模式收發短信可以使用3種編碼:7-bit、8-bit和UCS2編碼。7-bit編碼用於發送普通的ASCII字符,8-bit編碼通常用於發送數據消息,UCS2編碼用於發送Unicode字符。一般的PDU編碼由A B C D E F G H I J K L M十三項組成。

A:短信息中心地址長度,2位十六進制數(1字節)。
B:短信息中心號碼類型,2位十六進制數。
C:短信息中心號碼,B+C的長度將由A中的數據決定。
D:文件頭字節,2位十六進制數。
E:信息類型,2位十六進制數。
F:被叫號碼長度,2位十六進制數。
G:被叫號碼類型,2位十六進制數,取值同B。
H:被叫號碼,長度由F中的數據決定。
I:協議標識,2位十六進制數。
J:數據編碼方案,2位十六進制數。
K:有效期,2位十六進制數。
L:用戶數據長度,2位十六進制數。
M:用戶數據,其長度由L中的數據決定。J中設定採用UCS2編碼,這裏是中英文的Unicode字符。

PDU編碼協議簡單說明

例1 發送:SMSC號碼是+8613800250500,對方號碼是13693092030,消息內容是Hello!。從手機發出的PDU串可以是
08 91 68 31 08 20 05 05 F0 11 00 0D 91 68 31 96 03 29 30 F0 00 00 00 06 C8 32 9B FD 0E 01
對照規範,具體分析:
分段 含義 說明
08 SMSC地址信息的長度 共8個八位字節(包括91)
91 SMSC地址格式(TON/NPI) 用國際格式號碼(在前面加‘+’)
68 31 08 20 05 05 F0 SMSC地址 8613800250500,補‘F’湊成偶數個
11 基本參數(TP-MTI/VFP) 發送,TP-VP用相對格式
00 消息基準值(TP-MR) 0
0D 目標地址數字個數 共13個十進制數(不包括91和‘F’)
91 目標地址格式(TON/NPI) 用國際格式號碼(在前面加‘+’)
68 31 96 03 29 30 F0 目標地址(TP-DA) 8613693092030,補‘F’湊成偶數個
00 協議標識(TP-PID) 是普通GSM類型,點到點方式
00 用戶信息編碼方式(TP-DCS) 7-bit編碼
00 有效期(TP-VP) 5分鐘
06 用戶信息長度(TP-UDL) 實際長度6個字節
C8 32 9B FD 0E 01 用戶信息(TP-UD) “Hello!”

例2 接收:SMSC號碼是+8613800250500,對方號碼是13693092030,消息內容是你好!。手機接收到的PDU串可以是
08 91 68 31 08 20 05 05 F0 84 0D 91 68 31 96 03 29 30 F0 00 08 30 30 21 80 63 54 80 06 4F 60 59 7D 00 21
對照規範,具體分析:
分段 含義 說明
08 地址信息的長度 個八位字節(包括91)
91 SMSC地址格式(TON/NPI) 用國際格式號碼(在前面加‘+’)
68 31 08 20 05 05 F0 SMSC地址 8613800250500,補‘F’湊成偶數個
84 基本參數(TP-MTI/MMS/RP) 接收,無更多消息,有回覆地址
0D 回覆地址數字個數 共13個十進制數(不包括91和‘F’)
91 回覆地址格式(TON/NPI) 用國際格式號碼(在前面加‘+’)
68 31 96 03 29 30 F0 回覆地址(TP-RA) 8613693092030,補‘F’湊成偶數個
00 協議標識(TP-PID) 是普通GSM類型,點到點方式
08 用戶信息編碼方式(TP-DCS) UCS2編碼
30 30 21 80 63 54 80 時間戳(TP-SCTS) 2003-3-12 08:36:45 +8時區
06 用戶信息長度(TP-UDL) 實際長度6個字節
4F 60 59 7D 00 21 用戶信息(TP-UD) “你好!”


若基本參數的最高位(TP-RP)爲0,則沒有回覆地址的三個段。從Internet上發出的短消息常常是這種情形。
注意號碼和時間的表示方法,不是按正常順序順着來的,而且要以‘F’將奇數補成偶數。


在PDU Mode中,可以採用三種編碼方式來對發送的內容進行編碼,它們是7-bit、8-bit和UCS2編碼。7-bit編碼用於發送普通的ASCII字符,它將一串7-bit的字符(最高位爲0)編碼成8-bit的數據,每8個字符可壓縮成7個;8-bit編碼通常用於發送數據消息,比如圖片和鈴聲等;而UCS2編碼用於發送Unicode字符。PDU串的用戶信息(TP-UD)段最大容量是140字節,所以在這三種編碼方式下,可以發送的短消息的最大字符數分別是160、140和70。這裏,將一個英文字母、一個漢字和一個數據字節都視爲一個字符。


需要注意的是,PDU串的用戶信息長度(TP-UDL),在各種編碼方式下意義有所不同。7-bit編碼時,指原始短消息的字符個數,而不是編碼後的字節數。8-bit編碼時,就是字節數。UCS2編碼時,也是字節數,等於原始短消息的字符數的兩倍。如果用戶信息(TP-UD)中存在一個頭(基本參數的TP-UDHI爲1),在所有編碼方式下,用戶信息長度(TP-UDL)都等於頭長度與編碼後字節數之和。如果採用GSM 03.42所建議的壓縮算法(TP-DCS的高3位爲001),則該長度也是壓縮編碼後字節數或頭長度與壓縮編碼後字節數之和。

2 參見詳細英文說明:http://www.dreamfabric.com/sms/

將源串每8個字符分爲一組(這個例子中不滿8個)進行編碼,在組內字符間壓縮,但每組之間是沒有什麼聯繫的。

用C實現7-bit編碼和解碼的算法如下:

// 7-bit編碼

// pSrc: 源字符串指針

// pDst: 目標編碼串指針

// nSrcLength: 源字符串長度

// 返回: 目標編碼串長度

int gsmEncode7bit(const char* pSrc, unsigned char* pDst, int nSrcLength)

{

     int nSrc;         // 源字符串的計數值

     int nDst;         // 目標編碼串的計數值

     int nChar;        // 當前正在處理的組內字符字節的序號,範圍是0-7

     unsigned char nLeft;     // 上一字節殘餘的數據

    

     // 計數值初始化

     nSrc = 0;

     nDst = 0;

    

     // 將源串每8個字節分爲一組,壓縮成7個字節

     // 循環該處理過程,直至源串被處理完

     // 如果分組不到8字節,也能正確處理

     while(nSrc<nSrcLength)

     {

         // 取源字符串的計數值的最低3位

         nChar = nSrc & 7;

    

         // 處理源串的每個字節

         if(nChar == 0)

         {

             // 組內第一個字節,只是保存起來,待處理下一個字節時使用

             nLeft = *pSrc;

         }

         else

         {

             // 組內其它字節,將其右邊部分與殘餘數據相加,得到一個目標編碼字節

             *pDst = (*pSrc << (8-nChar)) | nLeft;

    

             // 將該字節剩下的左邊部分,作爲殘餘數據保存起來

             nLeft = *pSrc >> nChar;

             // 修改目標串的指針和計數值 pDst++;

             nDst++;

         }

        

         // 修改源串的指針和計數值

         pSrc++; nSrc++;

     }

    

     // 返回目標串長度

     return nDst;

}

    

// 7-bit解碼

// pSrc: 源編碼串指針

// pDst: 目標字符串指針

// nSrcLength: 源編碼串長度

// 返回: 目標字符串長度

int gsmDecode7bit(const unsigned char* pSrc, char* pDst, int nSrcLength)

{

     int nSrc;         // 源字符串的計數值

     int nDst;         // 目標解碼串的計數值

     int nByte;        // 當前正在處理的組內字節的序號,範圍是0-6

     unsigned char nLeft;     // 上一字節殘餘的數據

    

     // 計數值初始化

     nSrc = 0;

     nDst = 0;

    

     // 組內字節序號和殘餘數據初始化

     nByte = 0;

     nLeft = 0;

    

     // 將源數據每7個字節分爲一組,解壓縮成8個字節

     // 循環該處理過程,直至源數據被處理完

     // 如果分組不到7字節,也能正確處理

     while(nSrc<nSrcLength)

     {

         // 將源字節右邊部分與殘餘數據相加,去掉最高位,得到一個目標解碼字節

         *pDst = ((*pSrc << nByte) | nLeft) & 0x7f;

         // 將該字節剩下的左邊部分,作爲殘餘數據保存起來

         nLeft = *pSrc >> (7-nByte);

    

         // 修改目標串的指針和計數值

         pDst++;

         nDst++;

    

         // 修改字節計數值

         nByte++;

    

         // 到了一組的最後一個字節

         if(nByte == 7)

         {

             // 額外得到一個目標解碼字節

             *pDst = nLeft;

    

             // 修改目標串的指針和計數值

             pDst++;

             nDst++;

    

             // 組內字節序號和殘餘數據初始化

             nByte = 0;

             nLeft = 0;

         }

    

         // 修改源串的指針和計數值

         pSrc++;

         nSrc++;

     }

    

     *pDst = 0;

    

     // 返回目標串長度

     return nDst;

}

需要指出的是,7-bit的字符集與ANSI標準字符集不完全一致,在0x20以下也排布了一些可打印字符,但英文字母、阿拉伯數字和常用符號的位置兩者是一樣的。用上面介紹的算法收發純英文短消息,一般情況應該是夠用了。如果是法語、德語、西班牙語等,含有 “å” “é”這一類字符,則要按上面編碼的輸出去查表,請參閱GSM 03.38的規定。

8-bit編碼其實沒有規定什麼具體的算法,不需要介紹。

UCS2編碼是將每個字符(1-2個字節)按照ISO/IEC106的規定,轉變爲16位的Unicode寬字符。在Windows系統中,特別是在2000/XP中,可以簡單地調用API 函數實現編碼和解碼。如果沒有系統的支持,比如用單片機控制手機模塊收發短消息,只好用查表法解決了。

Windows環境下,用C實現UCS2編碼和解碼的算法如下:

// UCS2編碼

// pSrc: 源字符串指針

// pDst: 目標編碼串指針

// nSrcLength: 源字符串長度

// 返回: 目標編碼串長度

int gsmEncodeUcs2(const char* pSrc, unsigned char* pDst, int nSrcLength)

{

     int nDstLength;         // UNICODE寬字符數目

     WCHAR wchar[128];       // UNICODE串緩衝區

    

     // 字符串-->UNICODE串

     nDstLength = ::MultiByteToWideChar(CP_ACP, 0, pSrc, nSrcLength, wchar, 128);

    

     // 高低字節對調,輸出

     for(int i=0; i<nDstLength; i++)

     {

         // 先輸出高位字節

         *pDst++ = wchar[i] >> 8;

         // 後輸出低位字節

         *pDst++ = wchar[i] & 0xff;

     }

    

     // 返回目標編碼串長度

     return nDstLength * 2;

}

    

// UCS2解碼

// pSrc: 源編碼串指針

// pDst: 目標字符串指針

// nSrcLength: 源編碼串長度

// 返回: 目標字符串長度

int gsmDecodeUcs2(const unsigned char* pSrc, char* pDst, int nSrcLength)

{

     int nDstLength;         // UNICODE寬字符數目

     WCHAR wchar[128];       // UNICODE串緩衝區

    

     // 高低字節對調,拼成UNICODE

     for(int i=0; i<nSrcLength/2; i++)

     {

         // 先高位字節

         wchar[i] = *pSrc++ << 8;

    

         // 後低位字節

         wchar[i] |= *pSrc++;

     }

    

     // UNICODE串-->字符串

     nDstLength = ::WideCharToMultiByte(CP_ACP, 0, wchar, nSrcLength/2, pDst, 160, NULL, NULL);

    

     // 輸出字符串加個結束符    

     pDst[nDstLength] = '\0';    

    

     // 返回目標字符串長度

     return nDstLength;

}

用以上編碼和解碼模塊,還不能將短消息字符串編碼爲PDU串需要的格式,也不能直接將PDU串中的用戶信息解碼爲短消息字符串,因爲還差一個在可打印字符串和字節數據之間相互轉換的環節。可以循環調用sscanf和sprintf函數實現這種變換。下面提供不用這些函數的算法,它們也適用於單片機、DSP編程環境。

// 可打印字符串轉換爲字節數據

// 如:"C8329BFD0E01" --> {0xC8, 0x32, 0x9B, 0xFD, 0x0E, 0x01}

// pSrc: 源字符串指針

// pDst: 目標數據指針

// nSrcLength: 源字符串長度

// 返回: 目標數據長度

int gsmString2Bytes(const char* pSrc, unsigned char* pDst, int nSrcLength)

{

     for(int i=0; i<nSrcLength; i+=2)

     {

         // 輸出高4位

         if(*pSrc>='0' && *pSrc<='9')

         {

             *pDst = (*pSrc - '0') << 4;

         }

         else

         {

             *pDst = (*pSrc - 'A' + 10) << 4;

         }

    

         pSrc++;

    

         // 輸出低4位

         if(*pSrc>='0' && *pSrc<='9')

         {

             *pDst |= *pSrc - '0';

         }

         else

         {

             *pDst |= *pSrc - 'A' + 10;

         }

         pSrc++;

         pDst++;

     }

    

     // 返回目標數據長度

     returnnSrcLength / 2;

}

    

// 字節數據轉換爲可打印字符串

// 如:{0xC8, 0x32, 0x9B, 0xFD, 0x0E, 0x01} --> "C8329BFD0E01"

// pSrc: 源數據指針

// pDst: 目標字符串指針

// nSrcLength: 源數據長度

// 返回: 目標字符串長度

int gsmBytes2String(const unsigned char* pSrc, char* pDst, int nSrcLength)

{

     const char tab[]="0123456789ABCDEF";     // 0x0-0xf的字符查找表

    

     for(int i=0; i<nSrcLength; i++)

     {

         // 輸出低4位

         *pDst++ = tab[*pSrc >> 4];

    

         // 輸出高4位

         *pDst++ = tab[*pSrc & 0x0f];

    

         pSrc++;

     }

    

     // 輸出字符串加個結束符

     *pDst = '\0';

    

     // 返回目標字符串長度

     return nSrcLength * 2;

}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章