CMPP2.0 協議SP端的·NET開發
內容簡介:本文介紹了CMPP2.0協議SP端.net實現需要注意的問題,並提供解決方案和參考意見,對CMPP協議做一個解讀參考。
關鍵字:CMPP 2.0 SMS ISMG Socket 線程 線程同步 .Net事件模型
一、CMPP協議簡介
中國移動通信互聯網簡訊網關介面協議(China Mobile Peer to Peer CMPP),是中國移動夢網內部各SMS參與節點相互交換SMS的官方協議。作為夢網的參與方,移動夢網的增值服務商(Service Provider SP )要按照此協議規範實現SP的部分,才可以將自己的簡訊通過移動的GSM網路的數據通道傳輸到最終手機用戶上。
實際上,協議規範了3個方面的內容:
。SP與移動的互聯網簡訊網關(Internet Short Message Gateway,ISMG)之間的介面協議
。ISMG之間的介面協議(譬如移動各省、市之間的簡訊息交換通過ISMG之間進行)
。ISMG與匯接網關(Gateway Name Server GNS,類似互聯網上的DNS伺服器)之間的介面協議,譬如跨省之類的簡訊需要GNS的幫助指出當前ISMG該如何傳遞簡訊。
其中,後二方面屬於移動簡訊息系統內部實現,對於SP來講大概可以「透明」來看待,只要實現了SP同ISMG的正確交互,就可以實現接入移動夢網簡訊系統。我們關心的只是SP端的開發細節。
二、CMPP交互模式
從手機用戶角度講,按簡訊的發起/接收路徑來講,有兩個叫法:
MT(Short Message Mobile Terminated, SMMT),簡訊接收,簡訊從SP發送到手機用戶。
MO (Short Message Mobile Originate,SMMO),簡訊發送,簡訊從手機用戶端發送到目標SP。
這兩類簡訊交互,從SP端來看,都是屬於Socket傳輸應用,CMPP的協議是以TCP/IP協議作為底層承載協議的,屬於TCP/IP協議棧之上的應用。
SP同ISMG的交互連接分長連接和短連接。
所謂短連接,就是一次連接,傳輸一個消息,然後等待回復後拆除連接,顯然,效率很低,所以,基本上不被考慮(實際應用移動也不允許SP採用短連接,只是不明白移動為什麼還要寫入文檔? ISMG間會需要?)
所謂長連接,就是SP建立同ISMG連接,然後不斷將數據包(一個個CMPP消息)發送到ISMG,此處發送不必等待某條消息的ISMG回應消息返回,就接著發送下一個消息。同時,等待ISMG返回信息或者等待ISMG發送給SP的消息。發送同接收消息不是一定要同步的,實際採用非同步(同時也時雙工)模式。從效率上,顯然,必須全雙工的非同步模式才能夠滿足實際應用需求。
如下圖(摘自CMPP2.0官方文檔)所示,演示了長連接模式數據傳輸過程:
三、SP端開發
1. 消息分類
首先,圖中的CMPP消息有很多種,SP同ISMG之間交流這些消息。大體上這些消息發出後,對方往往需要回復一個應答(RESP)類消息。注意,這些消息大多具有方向性,也就是說只能夠從一端到另一端,而不可反方向進行,有些(少數)則可兩端都能夠發出。以下信息主要來源於移動的文檔,但針對大家易混淆或源文檔解釋不夠詳細做了明確和補充。具體見下表:
消息名 |
傳遞方向 |
解釋說明 |
CMPP-_CONNECT |
SP---àISMG |
CMPP_CONNECT操作的目的是SP向ISMG註冊作為一個合法SP身份,此消息需要向ISMG發出驗證信息,驗證方式採用md5加密密碼方式,若註冊成功後即建立了應用層的連接(否則ISMG會立即斷開Socket),此後SP可以通過此ISMG接收和發送簡訊。 ISMG以CMPP_CONNECT_RESP消息響應SP的請求。具體的演算法實現參考CMPP2.0文檔和本文附件代碼。 |
CMPP_CONNECT_RESP |
SP?---ISMG |
ISMG對CMPP_CONNECT消息的回復(無論是否驗證成功);如果未通過,會在消息中包含參考信息,但ISMG會立即斷開連接。 |
CMPP-_ACTIVE_TEST |
SP?àISMG |
這個消息通信雙方都可以發出,目的是在沒有其他消息發送時,保持雙方的通信鏈路的連接,避免系統認為通信通道已經關閉。每一個收到此消息的實體應當返回CMPP_ACTIVE_TEST_RESP消息,以「禮節性」表示自己的還在通信,維持數據連接有效性。 不過,據網友交流,有些廠家實現的ISMG,僅僅靠自己發出此消息等待SP回答CMPP_ACTIVE_TEST_RESP來確定數據鏈路的有效性,而忽略SP的CMPP_ACTIVE_TEST消息(有些霸道吧?)這個值得注意,不要僅僅實現發送而不響應此消息,避免數據連接失效。 |
CMPP_ACTIVE_TEST_RESP |
SP?àISMG |
對通信的另一端的CMPP_ACTIVE_TEST消息的回復。作用參考CMPP_ACTIVE_TEST的解釋。 |
CMPP-_SUBMIT |
SP---àISMG |
在正確建立了數據連接後,SP向ISMG發送一個SMS數據包。本消息需要仔細研究。接收到此消息後,ISMG需要以CMPP_SUBMIT_RESP消息作為回答。如果在一定時間時間內(移動給出的參考值60秒)內未得到消息回應,那麼SP需要重新發送此數據包,以確保消息得到投遞。如果重發達到3次後仍然得不到回應,SP端應該考慮可能ISMG已經失效,應當停止發送此短消息。 |
CMPP-_SUBMIT_RESP |
SP?---ISMG |
該消息由ISMG發送給SP,同時返回一個「收條」(源CMPP_SUBMIT消息的ISMG端的標示MSGID)給SP,表示「我ISMG已經確認收到你這條消息了」。收到此消息後,SP需要保留此「收條」,因為後面ISMG會最終報告本消息是否正確發送到用戶手機。那個報告就是以此消息的「收條」作為確認那一條消息的。 |
CMPP_QUERY |
SP---àISMG |
這個查詢不是查詢單條消息的,是查詢SP發送給ISMG的簡訊的業務情況。可以查總計數,還可以分類查詢。(基本就是發起對移動sms業務資料庫的查詢統計) |
CMPP_QUERY_RESP |
SP?---ISMG |
ISMG將查詢的數據返回給SP。 |
CMPP_CANCEL |
SP---àISMG |
SP發起的取消某條消息的命令消息,其中包含了之前已經發送給ISMG消息的「收條」以便ISMG可以確定是那一條消息。如果消息已經發送給用戶了,那麼此消息/命令會無效,ISMG返回失敗。 |
CMPP_CANCEL_RES |
SP?---ISMG |
ISMG返回的對CMPP_CANCEL的回復,並告知是否刪除成功。 |
CMPP_DELIVER |
SP?---ISMG |
當有MO或者狀態報告時,ISMG發送此消息。注意,此消息的數據可以是用戶手機發送給SP的消息,也可是對於之前SP發送到ISMG的簡訊的最終狀態的回復,報告簡訊的最終狀態。 |
CMPP_DELIVER_RESP |
SP---àISMG |
SP禮節性的回復告知收到CMPP_DELIVER消息。要指出SP報告的CMPP_DELIVER消息的MSGID,以便ISMG知道那一條消息SP已經確認收到。 |
CMPP_TERMINAT |
SP?àISMG |
SP和ISMG都可以主動發消息給對方,自己這端由於某種原因需要終止當前的數據連接。終止後,要經過重新Connection(驗證)之後才可以(進入事務階段)發送SMS數據消息。 |
CMPP-_TERMINATE_RES |
SP?àISMG |
通知對方,本端已經最好撤除連接的準備。 |
2. 交互階段
整個CMPP協議交互分為驗證、事務兩個階段。驗證階段,發送CMPP_CONNECTION消息進行驗證,通過驗證後(必須要通過才)進入CMPP事務階段,可以發送簡訊數據了。上表中的CMPP_CONNECTION以下的消息都屬於事務階段的消息。
3. 消息數據結構
每一個消息包含 消息頭 和 消息體兩個部分,頭固定長度為12位元組,其他消息長度各異,但是同一類型消息的長度是固定的。所有消息的各個欄位基本上僅有3種類型:Unsigned Integer (無符號整型) 、Integer(整型)、Octet String(字元串),每種類型具體長度不定,網路位元組順序。
1、 消息頭(3個Unsigned Integer欄位組成):
4位元組的Total_Length (Unsigned Integer),包含了此消息的總計(包括了頭部分)長度。
4位元組的Command_Id(Unsigned Integer),指明了此消息到底是什麼消息,就是上表中消息的枚舉值。應用程序根據此值確定本數據包到底是什麼消息,從而可以按照確定的消息類型,解析餘下的消息體。
4位元組的Sequence_Id(Unsigned Integer),指明了此數據包在發送此消息端的唯一編號。這個唯一編號,實際上可以看作流水操作編號。因為分析到交互模式我們看到,SP發送數據到ISMG,不是每發送一個就停下來等待ISMG的回復,而是「一下子」發送多個數據包過去,然後等待ISMG的回應。然而,怎麼知道回應的消息是到底對應之前發送過去的消息中的那一條呢?本欄位就是解決此難題。SP按照編號發送消息過去,等待ISMG的回應—一般情形下回應消息數據結構都有表明本消息回應的是SP發出的哪一條消息,這個對應就是依靠Sequence_Id。它並不要求一定要嚴格唯一,但是在給定的一段時間內,必須唯一(基本上只要SP發送過去的消息中沒有重複就行了)。如果是需要SP回答的消息,SP也必須將ISMG發送過來的消息的Sequence_Id填入相應欄位,表明這是某個消息的回應。SP端和ISMG端Sequence_ID都沒有確定具體的演算法。SP可以(但不推薦)採用資料庫的唯一Id作為此值。
2、消息體。消息體長度根據消息不同,長度不一。其他的參考移動的文檔《中國移動通信互聯網簡訊網關介面協議(China Mobile Peer to Peer, CMPP)(V2.0)》,這裡著重講講2個重要消息的消息體數據結構:
CMPP-_SUBMIT的消息體:
欄位名 |
長度(byte) |
類型 |
描述 |
Msg_Id |
8 |
Unsigned Integer |
信息標識,應該由SP側ISMG本身產生,本處填空,供ISMG傳輸時使用。SP提交時候應當留空。 |
Pk_total |
1 |
Unsigned Integer |
相同Msg_Id的信息總條數,從1開始。如果一條消息長度超多一條簡訊,可能需要分解成多條消息,那麼實際上這多條消息屬於一條完整消息,所以可以根據此給分解得到的多條簡訊進行編號,那麼總計需要編成多少條簡訊,此處就填寫多少。 |
Pk_number |
1 |
Unsigned Integer |
相同Msg_Id的信息序號,從1開始。編號決定消息的相對位置。 |
Registered_Delivery |
1 |
Unsigned Integer |
是否要求返回狀態確認報告: 0:不需要 1:需要 2:產生SMC話單(該類型簡訊僅供網關計費使用,不發送給目的終端)。 一般情況下,都需要確認報告。SMC話單也需要返回是否成功的報告。這條消息用於包月SMC時,當你發送消息給移動的ISMG,移動的計費系統會一次性扣除用戶的信息費,但是此消息不會送到用戶手機。但是注意,有的ISMG廠商(很可能是移動要求)實現此消息時候,如果你並沒有發送任何此包月類型的消息給用戶手機,是不發生扣費行為的。移動會認為這是屬於違規的「代收費」行為,會影響同移動的合作關係。 |
Msg_level |
1 |
Unsigned Integer |
信息級別,信息的優先順序。不過實際當中,感覺ISMG端並沒有區分優先順序。 |
Service_Id |
10 |
Octet String |
業務類型,是數字、字母和符號的組合。這個表示業務的字元串可以給發出的簡訊分類。通過此欄位大約可以知道每個服務項目的業務量,有利於統計和計費以及結算。 |
Fee_UserType |
1 |
Unsigned Integer |
計費用戶類型欄位 0:對目的終端MSISDN計費; 1:對源終端MSISDN計費; 2:對SP計費; 3:表示本欄位無效,對誰計費參見Fee_terminal_Id欄位。 |
Fee_terminal_Id |
21 |
Unsigned Integer |
被計費用戶的號碼(如本位元組填空,則表示本欄位無效,對誰計費參見Fee_UserType欄位,本欄位與Fee_UserType欄位取0、1、2時互斥) |
TP_pId |
1 |
Unsigned Integer |
GSM協議類型。詳細是解釋請參考GSM03.40中的9.2.3.9 |
TP_udhi |
1 |
Unsigned Integer |
GSM協議類型。詳細是解釋請參考GSM03.40中的9.2.3.23,僅使用1位,右對齊 |
Msg_Fmt |
1 |
Unsigned Integer |
信息格式 0:ASCII串 3:簡訊寫卡操作 4:二進位信息 8:UCS2編碼 15:含GB漢字 這個決定了Msg_Content欄位的位元組內容應該按照什麼編碼來解碼/編碼。 |
Msg_src |
6 |
Octet String |
信息內容來源(SP的企業代碼),例如919000。 |
FeeType |
2 |
Octet String |
資費類別 01:對「計費用戶號碼」免費 02:對「計費用戶號碼」按條計信息費 03:對「計費用戶號碼」按包月收信息費 04:對「計費用戶號碼」的信息費封頂 05:對「計費用戶號碼」的收費是由SP實現。 通常值為02,注意這是一個字元串,並非整型。 |
FeeCode |
6 |
Octet String |
資費代碼(以分為單位),如:「0050」代表人民幣0.50元。 |
ValId_Time |
17 |
Octet String |
存活有效期,格式遵循SMPP3.3協議 |
At_Time |
17 |
Octet String |
定時發送時間,格式遵循SMPP3.3協議。這個欄位可以讓簡訊在規定的時間給手機用戶。一般情況下不填,保留為空字元串。 |
Src_Id |
21 |
Octet String |
源號碼 SP的服務代碼或前綴為服務代碼的長號碼, 網關將該號碼完整的填到SMPP協議Submit_SM消息相應的source_addr欄位,該號碼最終在用戶手機上顯示為短消息的主叫號碼。實際上就是服務代碼,可以是長號碼 |
DestUsr_tl |
1 |
Unsigned Integer |
接收信息的用戶數量(小於100個用戶),通常是1。移動是忌諱一條消息發給多個用戶的。 |
Dest_terminal_Id |
21*DestUsr_tl |
Octet String |
接收簡訊的MSISDN號碼,一個類似字元串數組的結構。受DestUsr_tl的約束,決定了本欄位的長度。 |
Msg_Length |
1 |
Unsigned Integer |
信息長度(Msg_Fmt值為0時:<160個位元組;其它<=140個位元組)。如果是ASCII碼,可以達到160個英文字母。原因是因為英文字母僅佔用7bit,而中文等雙位元組代碼需要16位,同時每一個位元組最高為都佔用,所以最多140個位元組,也就是70個漢字。 |
Msg_Content |
Msg_length |
Octet String |
信息內容 |
Reserve |
8 |
Octet String |
保留 |
CMPP_SUBMIT消息長度是可變的,將SP端的消息發送給ISMG,ISMG將返回一個MSGID給SP標示此消息,之後(48小時以內,但一般最多幾分鐘內就可),ISMG返回關於此消息的遞送報告。遞送報告同MO短消息是通過另外一個重要消息CMPP-_DELIVER來提交給SP的:
CMPP-_DELIVER的各個欄位:
欄位名 |
位元組數 |
屬性 |
描述 |
Msg_Id |
8 |
Unsigned Integer |
信息標識 生成演算法如下: 採用64位(8位元組)的整數: (1)時間(格式為MMDDHHMMSS,即月日時分秒):bit64~bit39,其中 bit64~bit61:月份的二進位表示; bit60~bit56:日的二進位表示; bit55~bit51:小時的二進位表示; bit50~bit45:分的二進位表示; bit44~bit39:秒的二進位表示; (2)簡訊網關代碼:bit38~bit17,把簡訊網關的代碼轉換為整數填寫到該欄位中。 (3)序列號:bit16~bit1,順序增加,步長為1,循環使用。 各部分如不能填滿,左補零,右對齊。 |
Dest_Id |
21 |
Octet String |
目的號碼 SP的服務代碼,一般4--6位,或者是前綴為服務代碼的長號碼;該號碼是手機用戶短消息的被叫號碼。 |
Service_Id |
10 |
Octet String |
業務類型,是數字、字母和符號的組合。 |
TP_pid |
1 |
Unsigned Integer |
GSM協議類型。詳細解釋請參考GSM03.40中的9.2.3.9 |
TP_udhi |
1 |
Unsigned Integer |
GSM協議類型。詳細解釋請參考GSM03.40中的9.2.3.23,僅使用1位,右對齊 |
Msg_Fmt |
1 |
Unsigned Integer |
信息格式 0:ASCII串 3:簡訊寫卡操作 4:二進位信息 8:UCS2編碼 15:含GB漢字 |
Src_terminal_Id |
21 |
Octet String |
源終端MSISDN號碼(狀態報告時填為CMPP_SUBMIT消息的目的終端號碼) |
Registered_Delivery |
1 |
Unsigned Integer |
是否為狀態報告 0:非狀態報告(MO SMS) 1:狀態報告 此欄位決定了CMPP-_DELIVER消息到底是手機上行一條消息到SP還是ISMG向SP報告之前發送的消息最終遞送狀態。 |
Msg_Length |
1 |
Unsigned Integer |
消息長度。是指Msg_Content欄位的長度。 |
Msg_Content |
Msg_length |
Octet String |
消息內容。如果消息不是狀態報告,那麼按照Msg_Fmt指示解碼為特定編碼的字元串內容。 |
Reserved |
8 |
Octet String |
保留項 |
如果是報告,那麼Msg_Content將按照狀態報告結構來解釋:
欄位名 |
位元組數 |
屬性 |
描述 |
Msg_Id |
8 |
Unsigned Integer |
信息標識 SP提交簡訊(CMPP_SUBMIT)操作時,與SP相連的ISMG產生的Msg_Id。 這個MSGID實際上就是SP之前發送一個CMPP_SUBMIT消息之後的CMPP_SUBMIT_RESP消息中返回的關於CMPP_SUBMIT消息的ISMG編號.,根據此MSGID可以知道那條消息最終確定的遞送狀態。 |
Stat |
7 |
Octet String |
發送簡訊的應答結果,含義與SMPP協議要求中stat欄位定義相同,詳見下面。SP根據該欄位確定被報告的CMPP_SUBMIT消息的處理狀態。 |
Submit_time |
10 |
Octet String |
YYMMDDHHMM(YY為年的後兩位00-99,MM:01-12,DD:01-31,HH:00-23,MM:00-59) |
Done_time |
10 |
Octet String |
YYMMDDHHMM |
Dest_terminal_Id |
21 |
Octet String |
目的終端MSISDN號碼(SP發送CMPP_SUBMIT消息的目標終端) |
SMSC_sequence |
4 |
Unsigned Integer |
取自SMSC發送狀態報告的消息體中的消息標識。 |
關於State欄位,如下解釋:
消息狀態名 |
最終狀態 |
描述 |
DELIVERED |
DELIVRD |
消息到達目標 |
EXPIRED |
EXPIRED |
消息過期 |
DELETED |
DELETED |
消息被刪除 |
UNDELIVERABLE |
UNDELIV |
消息未被送達 |
ACCEPTED |
ACCEPTD |
消息被認可 |
UNKNOWN |
UNKNOWN |
未知狀態 |
REJECTED |
REJECTD |
消息被彈回 |
其他消息結構,具體說明見中移動的CMPP協議。
4. 安全驗證
CMPP協議在CMPP_CONNECT中傳遞驗證消息。驗證消息為9位元組的0+移動給出的密碼+當前時間戳位元組數組的MD5演算法後的位元組。時間戳為 月日時分秒,10位。代碼演算法如下:
private byte[] getMd5Code()
{
byte[] buf=new byte[6+9+_Password.Length+10] ;
byte[] s_a=Encoding.ASCII.GetBytes(_SystemID); //就是企業代碼
byte[] s_0={0,0,0,0,0,0,0,0,0}; //9位元組的0,此處當作右補0
byte[] s_p=Encoding.ASCII.GetBytes(_Password); //密碼
this._timestamp =getTimestamp(); //取得認證碼時賦值字元串
byte[] s_t=Encoding.ASCII.GetBytes(_timestamp); //10位字元串位元組數組
s_a.CopyTo(buf,0);
s_0.CopyTo(buf,6);
s_p.CopyTo(buf,6+9);
s_t.CopyTo(buf,6+9+_Password.Length);
MD5 md5= new MD5CryptoServiceProvider(); //創建MD5類別
return(md5.ComputeHash(buf,0,buf.Length));
}
其中getTimestamp函數為返回例如「0710125959」(7月10號12點59分59秒)這樣的字元串,詳細代碼略過,有興趣請查看本文的附件代碼。
5. 廠商API問題
筆者公司所處廣東,廣東移動提供了華為的以C 形式的API(SMEIDLL.dll),來幫助大家初期熟悉CMPP協議。但是,經過開發測試,發現華為的API至少存在幾個問題:
1、 封裝成幾個API函數,但是由於CMPP自身的複雜性,導致這些函數醜陋無比,參數多,而且難以明晰含義。華為的API,內部將CMPP的驗證、事務階段分成幾個函數實現,其中將發送SMS到ISMG功能以函數提供,竟然出現SubmitAExExEx之類的函數說明。
2、 CMPP的交互是非同步的,需要多線程實現一邊發送,一邊接收反饋信息。此API應當是內部維護一個線程進行CMPP_SUBMIT消息發送,但是華為API卻通過空循環之類的操作等待ISMG返回CMPP_SUBMIT_RESP得到相應的MSGID再返回(從而實現消息同步返回)。經過測試,大約需要200毫秒,這個在實際SP的高性能需求場合根本無法滿足系統要求。
3、 接收簡訊必須依靠程序主動先發出函數HasDeliverMessage調用 ,得到有消息才可通過GetDeliverSMEx函數獲取消息,顯然,這種方式是低效率的,而且容易產生消息數據包丟失,表現為有些MO消息,SP接收不到。而且,令人疑惑的是,你還不能夠新開一個線程專門來做判斷並接收MO的動作,實際開發中一旦採用線程來做就回發生內存保護錯誤(大概屬於同API自身的線程有衝突)。
4、 返回錯誤碼,往往又是華為自己定的一套錯誤碼(大概華為設計此API為了適應SMGP CMPP等多個協議),而且經常變動,很是傷腦筋。
基於以上理由,我認為自己按照CMPP協議開發一個SP端程序,比較能夠滿足一般SP的需求。
四、C#實現
1、CMPP協議實現類CMPPClient
通過研究,筆者用C#寫了一組類實現自己的CMPP SP端程序(CMPPClient)。為了實現相關類,還需要編寫一些輔助類,並且首先要解決CMPP協議的數據結構同C#的數據之間的轉換問題。
CMPP的Octet String 實際上相當於C#中的byte[],所有CMPP消息的Octet String欄位出了CMPP_SUBMIT和CMPP_DELIVER的msg_content欄位外,其他的都可以認為是ASCII編碼,所以全部可以採用System.Text.Encoding.ASCII進行編碼和解碼;對於Msg_Content欄位,由於一般情況下存在漢字信息傳輸.,所以默認的編/解碼應該為Encoding.Default,實際是什麼編碼還要考察MSG_Fmt欄位指示正文到底是什麼編碼。
對於Unsigned Integer 和Interger欄位,需要按照網路位元組順序和x86機器的位元組編碼順序對照關係進行轉換,具體我設計了一個工具類提一些轉換方法使用:
public class BIConvert //位元組 整形 轉換類 網路格式轉換為內存格式
{
public static byte[] Int2Bytes(uint i) //轉換整形數據的網路次序位元組數組
{
byte[] t=BitConverter.GetBytes(i) ;
byte b=t[0];
t[0]=t[3];
t[3]=b;
b=t[1];
t[1]=t[2];
t[2]=b;
return(t);
}
public static uint Bytes2UInt(byte[] bs,int startIndex) //返回位元組數組代表的整數數字,4個位元組長度的數組
{
byte[] t=new byte[4];
for(int i=0;i<4 && i< bs.Length-startIndex ;i++)
{
t[i]=bs[startIndex+i];
}
byte b=t[0];
t[0]=t[3];
t[3]=b;
b=t[1];
t[1]=t[2];
t[2]=b;
return(BitConverter.ToUInt32(t,0));
}
public static uint Bytes2UInt(byte[] bs) //沒有指定起始索引
{
return( Bytes2UInt(bs,0));
}
}
其次,為了實現收發數據的「全雙工」,需要設計至少兩個線程處理socket的讀取和數據包寫入。另外,為了自動實現對數據鏈路的保持,以及自動實現數據包重發機值,我還增加了一個值守線程,自動處理以上問題。詳細見後代碼。另外,消息中有很多同時間有關的欄位,但是這些時間相關欄位並非按照統一規格編碼的,這個需要仔細研究協議或者實現代碼。
其三,為了解析/編碼數據包方便,我將SP端涉及到的消息以類的形式實現,根據具體的消息類型,將數據包位元組解析還原為特定的消息;另一方面,當需要發送一些消息時,將消息的各個欄位,根據類型和編碼類型「組裝」成位元組數組,以便Socket能夠發送出去。
其四,為了達到及時處理短消息的收發,我大量採用了C#事件觸發來達到消息通知目的,而且,也設計一組事件參數,供事件的具體監聽者可以掌握需要的信息。
其五,需要注意多線程間的同步問題。
其六,填寫CMPP_SUBMIT消息需要注意內容編碼、計費欄位正確填寫
2、事件模型
大體上實現了十多個事件,這些事件具體為:
當CMPP_DELIVER消息送來的是短消息送達報告時,發生消息送達報告事件:
public delegate void ReportEventHandler(object sender, ReportEventArgs e); 當CMPP_DELIVER消息送來的是用戶手機MO短消息時,發生簡訊到達事件,其他程序可以在處理此事件獲得消息的正文、手機號碼、SP服務號碼等信息:
public delegate void SMSEventHandler(object sender, SMSEventArgs e); 當ISMG發出CMPP_TERMINATE消息時,發生,具體的回應,我在具體實現中先自動進行了回復,產生此事件僅僅向外部程序(此事件的截取者)表達收到此消息,需要進行「善後清場」操作:
public delegate void TerminateEventHandler(object sender,TerminateEventArgs e); 當SP主動終止連接時,發出CMPP_TERMINATE消息,ISMG會響應CMPP_TERMINATE_RESP消息,此事件表示收到此回應
public delegate void TerminateRespEventHandler(object sender,TerminateRespEventArgs e);
以下兩個事件針對鏈路保持消息CMPP_ACTIVE_TEST及CMPP_ACTIVE_TEST_RESP發生:
public delegate void TestEventHandler(object sender,TestEventArgs e);
public delegate void TestRespEventHandler(object sender,TestRespEventArgs e); SP發出CMPP_CONNECT消息後,ISMG驗證,然後發出CMPP_CONNECT_RESP消息,此時激活此事件:
public delegate void ConnectRespEventHandler(object sender,ConnectRespEventArgs e);
SP取消某條端消息,發出CMPP_CANCEL後,ISMG響應此消息返回CMPP_CANCEL_RESP消息時,激活事件:
public delegate void CancelRespEventHandler(object sender,CancelRespEventArgs e); SP提交簡訊後,ISMG返回一個CMPP_SUBMIT_RESP 消息,包含「收條」(MSG_ID)在內,觸發此事件:
public delegate void SubmitRespEventHandler(object sender,SubmitRespEventArgs e);
查詢ISMG返回消息後,發生:
public delegate void QueryRespEventHandler(object sender,QueryRespEventArgs e); 當SP驗證通過後,作為應用邏輯需要得到通知,我特此加了此事件:
public delegate void LogonSuccEventHandler(object sender,EventArgs e); //當成功登錄系統
以下事件,不是基於CMPP消息,而是根據SP同ISMG消息隊列掃描後判斷觸發事件:
public delegate void SocketClosedEventHandler(object sender,EventArgs e); //當套接字被檢測到關閉
public delegate void FailedItemDeletedEventHandler(object sender,WaitingQueueItemEventArgs e); //當一條存在於等待隊列的消息超過60秒沒有回應
以上這些事件預設實現保證了SP端CMPP客戶對於ISMG的響應自動化,提供觸發事件保證調用此客戶端類的系統可以通過事件發生準確的控制SP端的內部狀態,獲取交互信息。
另一方面,由於這些大多數事件發生於數據包達到後的處理,實際上需要處理事件程序一定要「迅速」解決,或者乾脆將消息轉換為可以暫存的消息形式,由其他程序進一步處理。CMPP的SP端要滿足大量簡訊息應用需求,就必須嚴格控制消息交互處理邏輯不要太複雜,特別不要設計大量I/O處理;如果實在要處理,最好採用非同步線程的方式來處理。
3、為了提高效率,開了3個線程:
RecvISMGMsgThread 用於處理接收ISMG發送過來的消息,並根據消息、消息解析後的欄位內容觸發相應的事件。
SendSPMsgThread 用於處理向ISMG發送數據包。注意,有些消息(譬如CMPP_ACTIVE_TEST)是系統自己產生的。另外,有些消息是收到ISMG的消息後需要立即回應給ISMG的,那麼這些消息,全部進入內部維護的消息隊列(_outSeqQueue)。該隊列會自動排序消息,所有需要發送的消息,進入此隊列,本線程不斷從隊列取出需要發送的消息,轉換成數據包,通過Socket發送到ISMG.
DeamonThread 用於監測數據連接socket是否可用,是否需要發出維持數據連接的測試數據包;有些消息發送過去了,過了協議規定的時間仍然沒有收到RESP消息,那麼需要將消息從已經發送的隊列中提取,重新加入到發送隊列中,排隊後等待送出。
可以仔細分析提供的代碼,研究其中的具體實現。
五、問題小結
根據自己的經驗,覺得以下幾點對於整個系統開發較為重要:
1、一定要正確理解協議。
很多網友交流時候,總抱怨協議濫,搞不定,其實很多原因屬於自己沒有清楚理解協議。從我的接觸的移動ISMG來看,應該說實現協議還是很嚴格遵守CMPP的描述。倒是,一些網友自己開發的模擬器不是很規範(不是批評,鄭重聲明),需要自己在開發時候引起注意。
2、多線程互斥問題。
多個線程之間涉及一些隊列的操作,需要進行同步鎖定,否則容易引起問題。出現異常也需要及時捕獲,並紀錄作為錯誤信息參考,便於排除bug。
3、自己控制數據包流向和處理時間。
由於設計目標是高性能,所以在處理socket數據讀寫時候要注意對於一些事件處理不要過多消耗系統資源,避免引起數據包來不及處理而導致數據丟失。特別在數據繁忙時刻往往會使ISMG的吞吐性能下降,需要考慮對這種情況下的流量控制。有時,你不能夠指望ISMG如你所願及時回應你,更為常見的是ISMG根本不返回RESP類型的數據包。另外,本協議處理數據收發採用阻塞Socket,有網友建議我採用非同步非阻塞Socket,我想可能非同步非阻塞Socket會更好。
4、服務監控問題。
由於一些意外,往往會導致數據連接被中斷,這是,需要建立超時重建連接的機制。我給出的例子並未很好解決,希望其他方家指正。
5、位元組順序問題。
這個問題,對於初接觸socket編程的人士往往造成很大麻煩。不過,CMPP協議設計的基本數據類型很簡單,僅需要按照本例子參考即可解決。
6、具體協議應用問題。
本例僅僅是一個按照協議要求實現CMPP協議的類,完整的SP端方案需要結合自己公司的實際要求,改造或者重用本例。限於篇幅,文中僅僅能夠列出重點片斷,詳細細節清參考供下載的代碼(附註釋)。本文僅僅是針對CMPP2.0協議進行討論開發,協議的詳情請從移動夢網(http://www.monternet.com/moneditor/cs/SP/cmcc/)下載。其他技術參考可以到如下處獲取,也可察看網友的貼字獲取進一步詳細說明:
天堂鳥交流論壇(http://www.spzone.net/bbs/index.asp)
CSDN社區 移動平台(http://community.csdn.net/expert/forum.asp?url=/Expert/ForumsList.asp?roomid=63&typenum=1&whichpage=1)
SP論壇(http://www.spforum.net/jishu/Index.asp)
如果你發現本人的實例存在問題(我想那簡直是一定的了),請不吝賜教myjobsdk@yahoo.com.cn。
後記:現在CMPP3。0也就是移動的MISC平台版本的需要訂購關係確定才可以計費。不過不是本文討論的重點。
推薦閱讀:
※雲石:廢除《伊核協議》,特朗普到底想幹嘛?
※張柏芝兩度抱子求死 ,謝霆鋒已簽好離婚協議書
※MS主叫流程 分析
※「泄憤式」婚前協議的思考
TAG:協議 |