中國電信和中國移動的短信協(xié)議要求客戶端主動連接到網(wǎng)關(guān)收取信息,并且接受的連接只能有一個。因此客戶端的接收程序必須盡快處理收到的信息,好盡快接收下一個信息。而客戶端的程序又有很多事情必須處理,比如寫日志并分發(fā)給相關(guān)的應(yīng)用。
開始我用多線程的方式,即接收程序收到從網(wǎng)關(guān)發(fā)來的信息后,啟動一個新線程處理收到的信息,本身接著收下一個信息。新啟動的線程負(fù)責(zé)寫日志,然后分發(fā)給相關(guān)的應(yīng)用。這種方式運(yùn)行起來效果不錯。
接著我又遇到一個問題,寫日志和分發(fā)給應(yīng)用的程序是兩個不相關(guān)的東西,放在一起有點(diǎn)別扭。寫日志的程序?qū)崟r性要求不高,只要保證記錄了就可以;而分發(fā)程序需要盡量快,不應(yīng)該等日志完了再執(zhí)行。再起一個線程寫日志,一個線程分發(fā)?這樣線程多了反而更慢,不太好。
對
與從網(wǎng)關(guān)收到的信息我們可能有許多處理程序,比如一個是寫日志,一個是分發(fā)給應(yīng)用。就日志來說,就有寫數(shù)據(jù)庫、寫文本文件、控制臺輸出等。所以我采用了消
息隊列的方式,這樣接收程序收到從網(wǎng)關(guān)來的信息后送出一個消息到消息隊列中,需要處理的程序自己來取就是了。這個消息隊列用的是
Topic/Subscribe模式,就是接收程序發(fā)出的同一個消息可能被多個處理程序接受。這樣對信息的不同處理,我就可以寫不同的消息處理程序。
接收完了,看看向網(wǎng)關(guān)發(fā)送信息的情況。短信網(wǎng)關(guān)一般也只允許一個連接進(jìn)行發(fā)送,而且中國電信和中國移動使用的短信網(wǎng)關(guān)協(xié)議都是異步的,這樣用消息隊列的發(fā)送也很理想。
整體框架入下面這幅圖,

Transport Service 是和網(wǎng)關(guān)通訊的程序。它負(fù)責(zé)保持連接、發(fā)送信息給網(wǎng)關(guān)、接收網(wǎng)關(guān)來的信息,并把信息發(fā)送到消息隊列
Deliver Controller 是分發(fā)信息給相關(guān)應(yīng)用的控制器。它從消息隊列中收取Deliver信息,然后根據(jù)信息的內(nèi)容或者用戶的狀態(tài),用不同的Application Deliver把信息分發(fā)給應(yīng)用
Application
Deliver
負(fù)責(zé)具體分發(fā)信息給應(yīng)用。不同的應(yīng)用可能要求不同的信息接受方式,比如多數(shù)是用HTTP方式,分發(fā)程序用POST方法把信息提交給應(yīng)用,這中方式用的是
AppDeliverHttpPost;有的是用SOAP,用的就是AppDeliverSoap了。總之,要什么新方法,寫一個Application
Deliver就可以了。
Logger 就是寫日志的程序。它從消息隊列中收取所以消息,然后寫日志。如果要有不同的日志方法可以寫不同的Logger,比如我有一個DBLogger,是向數(shù)據(jù)庫里寫日志的實現(xiàn)。
Sender 是通過Transport Service發(fā)送Submit信息給網(wǎng)關(guān)的程序。短信協(xié)議是異步的,但多數(shù)應(yīng)該要求同步,就是要知道發(fā)送是否成功。因此Sender多提供了一個實現(xiàn)同步的方法。Sender發(fā)送后,從消息隊列中等待發(fā)送的結(jié)果,然后返回結(jié)果。
Web Service 是接受應(yīng)用發(fā)送信息的接口。為什么要這個呢?和接收一樣,多數(shù)應(yīng)用發(fā)送信息的時候是用HTTP的POST方法。而且這樣應(yīng)該不應(yīng)該知道短信協(xié)議的數(shù)據(jù)包格式,這樣同一個應(yīng)該可以給不同的短信協(xié)議使用,比如中國電信的SMGP和中國移動的CMPP。
Application 就是最終處理短信的應(yīng)用了。它和短信協(xié)議無關(guān),比如我們有個游戲同時支持中國短信的小靈通和中國移動的GSM手機(jī)。另外給個Web的例子: http://www.hcmms.com.cn/greetings/
在實際實現(xiàn)中,我使用了J2EE。我本來對J2EE挺煩的,不過這次效果不錯,特別是它的JMS,正好適合我的需要。開始我自己做消息隊列管理,后來發(fā)現(xiàn)做Topic方式的實現(xiàn)比較麻煩,所以還是用現(xiàn)成的吧。Application Server選用了JRun或JBoss。因為這兩個用JMX,我比較容易寫自己的MBean放到作為應(yīng)用服務(wù)的Service。
其
中Transport
Service就是一個JMX的MBean。另外根據(jù)JRun和JBoss的要求,做了一下擴(kuò)展,很順利的作為應(yīng)用服務(wù)器中的一個服務(wù)執(zhí)行了。為什么用
JMX?因為它比較容易管理,比如我要修改Transport
Service的連接超時參數(shù),用HttpAgentAdapter直接修改就可以了,服務(wù)本身就不用重新啟動。
Deliver Controller、Logger我都做成了MessageBean,好接收消息隊列。Sender和Application Deliver是Stateless的SessionBean,方便別人調(diào)用。
用
了EJB還一個好處是,我Deploy和Undeploy其中的某些Bean的時候,不影響到其他Bean提供服務(wù)。比如新做了一個
Application Deliver,直接Deploy后,這個功能就可以用了。其他服務(wù)都不需要重新啟動,有點(diǎn)Plug&Play的味道。
數(shù)據(jù)庫我用的是MySQL,我個人還是比較喜歡這個數(shù)據(jù)庫的。
Web CVS: http://cvs.dragonsoft.net/horde/chora/cvs.php/phs-smgp/
說明
-
TransportService
和網(wǎng)關(guān)通訊的程序。它負(fù)責(zé)保持連接、發(fā)送信息給網(wǎng)關(guān)、把從網(wǎng)關(guān)接收到的信息發(fā)送到消息隊列。
-
DeliverController
分發(fā)信息給相關(guān)應(yīng)用的控制器。它從消息隊列中收取Deliver信息,然后根據(jù)信息的內(nèi)容或者用戶的狀態(tài),用不同的ApplicationDeliver把信息分發(fā)給應(yīng)用程序。
-
ApplicationDeliver
負(fù)責(zé)具體分發(fā)信息給應(yīng)用。不同的應(yīng)用可能要求不同的信息接受方式,比如多數(shù)是用HTTP方式,分發(fā)程序用POST方法把信息提交給應(yīng)用, 這種方式用的是 AppDeliverHttpPost;有的是用SOAP,用的就是AppDeliverSoap了。總之,要什么新方法,寫一個ApplicationDeliver就可以了。
-
Logger
記錄日志的程序。它從消息隊列中收取所有消息,然后寫日志。如果要有不同的日志方法可以寫不同的Logger,比如我有一個SMGPPacketDBLogger,是向數(shù)據(jù)庫里寫日志的實現(xiàn)。
-
Sender
通過TransportService發(fā)送Submit信息給網(wǎng)關(guān)的程序。短信協(xié)議是異步的,但多數(shù)應(yīng)該要求同步,就是要知道發(fā)送是否成功。因此Sender多提供了一個實現(xiàn)同步的方法,在Sender發(fā)送后,從消息隊列中等待發(fā)送的結(jié)果,然后返回這個結(jié)果。
-
WebService
接收應(yīng)用發(fā)送信息的接口。為什么要這個呢?和接收一樣,多數(shù)應(yīng)用發(fā)送信息的時候是用HTTP的POST方法。而且這樣應(yīng)該不應(yīng)該知道短信 協(xié)議的數(shù)據(jù)包格式,這樣同一個應(yīng)該可以給不同的短信協(xié)議使用,比如中國電信的SMGP和中國移動的CMPP。
-
Application
最終處理短信的應(yīng)用。它可以和短信協(xié)議無關(guān),比如我們有個游戲同時支持中國短信的小靈通和中國移動的GSM手機(jī)。
實現(xiàn)
源代碼
cvs -d:pserver:anoncvs@cvs.dragonsoft.net:/opt/cvsroot/dragonsoft/phs-smgp login
cvs -z3 -d:pserver:anoncvs@cvs.dragonsoft.net:/opt/cvsroot/dragonsoft/phs-smgp co phs-smgp