中國移動企業短信通平臺EMPP協議分析

中國移動企信通地址:http://sms.sh.chinamobile.com/qxt/index.jsp

最近要爲手上一個項目設計短信發送,簡單分析了一下,最終將使用中國移動企信通作爲工具。簡單總結一下:

一、選擇短信發送方案
1.MAS方式:
[img]http://dl2.iteye.com/upload/attachment/0122/9568/a4a4e236-8c53-3a90-81fe-8f1cd643daad.jpg[/img]
需要採用硬件設備,還要上機架,進行軟件安裝與配置,非常繁瑣,並且成本比較高。
聽某使用mas機的項目反映,延時比較嚴重,從幾分鐘,甚至出現過十幾分鐘的情況。當然可能是設備老舊了。
放棄!

2.阿里大於
阿里的東西當然好了,網站上資料很全,但瞭解同事項目還沒有人用過。最主要的一個問題是發送人手機號不能固定吧。
手機端接收顯示的號碼是106開頭的正規號碼 顯號規則:
a.號碼=通道號+擴展碼;
b.在通道不穩定、網絡抖動及機房斷電等不可抗拒的因素下,爲確保到達率及到達時間,會自動切換通道,通道號會發生變化。
不知道不知道拆分的短信之間號碼變不變,不知道可不可以犧牲達率與時間,保證顯示號碼不變呢?

3.移動企信通EMPP
提供了EMPPAPI,並且有相關文檔和簡單示例。

最終不知爲何選擇移動,反正研究起來EMPP。

二、移動EMPP
有一個企信通平臺,企業用戶通過分配給自己的賬號登錄進移動平臺,可以發送短信,收到狀態,收到回覆等功能。自己的應用程序可以使用提供的EmppAPI使用這些功能。
估計通過API發的短信,在平臺裏登錄也能看到。

代碼在看過阿里的DUBBO後,再看EMPP就簡單多了,它也不用NIO。在TCP協議上設計了一個EMPP協議,就簡單的用socket編程,主要是把信息對象轉成設計好的BYTE[],發給平臺,平臺有返回BYTE[]流,再解析出對象來。通常都有消息頭與消息體的設計。

1.EmppApi.java與RecvListener.java
這兩個是使用的核心類,EmppApi中一但與遠程主機進行連接時,會啓動兩個線程:

RecvThread:目的是不斷接收主機發送過來的信息,生成EMPPObject對象。
buffer=connection.receive(12);
buffer.appendBuffer=connection.receive(length - 12);
EMPPObject.createEMPP(buffer);
EMPPObject實際上有很多具體的類型,包括與主機通訊的各情況,分爲emppRequest與emppResponse兩大類。

SendActiveThread:目標是不斷髮送檢測對象,保持與主機的tcp連接。
connection.send(emppActiveTest)。

無論是發送與接收,都是通過socket上的輸入輸出流來處理的,比較簡單。

2.監聽器RecvListener
EmppApi emppApi = new EmppApi();
RecvListener listener = new RecvListener(emppApi);
EMPPConnectResp response = emppApi.connect(host, port, accountId,
password, listener);

簡單的看上面的關係,大BOSS先出生,再生成一個祕書,同時把BOSS傳給它,即給BOSS配置了祕書。BOSS幹活的時候,把祕書傳進去。
祕書有什麼用呢?BOSS不是內部有線程一直在接收嗎?接收到了信息,就讓祕書去處理。有意思的是OnClosed方法,當收到掉線信息了,那祕書的工作是:新建一個祕書,配置給BOSS來用於重聯。

RecvListener listener = new RecvListener(emppApi)
...
emppApi.reConnect(listener);

另外有一個ByteBuffer類,處理byte[]時很方便。

3.與協議相關的比較多的細節技術有:
複製:System.arraycopy.這是一個native方法。
移位:intBuf[3] = (byte)(data & 255);intBuf[2] = (byte)(data >>> 8 & 255);
轉換:getBytes(編碼)。

三、nio與netty
看到這個EMPPAPI中的技術太舊了,源碼是2008年的。簡單回顧一下nio吧。
nio是TCP通信編輯技術io處理的新版本,非阻塞式io,netty是基於nio的一個編程框架。
老的io是服務器收到一個TCP連接就建一個線程,客戶發送完信息就線程等待着讀socket上的通道。這兩個過程都是阻塞線程。當然客戶端發送信息沒有任何關係,發送想發就發,想寫就寫,只是讀被阻塞了,它會一直等到數據到來時(或超時)纔會返回。
服務器與客戶機等待的時候都是阻塞着的。就是因爲阻塞着,怎麼辦呢?那設計一個統一的線程(公共服務員)去偵聽,等到有結果了再通知我,而我不用一直守着,想幹嘛就幹嘛去。有點象異步通信,有點象回調。

對於客戶端:
a.先產生一個selector(公共服務員),本通道再服務員處登記一下。
this.selector = Selector.open();
// 客戶端連接服務器,其實方法執行並沒有實現連接,需要在listen()方法中調
//用channel.finishConnect();才能完成連接
channel.connect(new InetSocketAddress(ip,port));
//將通道管理器和該通道綁定,併爲該通道註冊SelectionKey.OP_CONNECT事件。
channel.register(selector, SelectionKey.OP_CONNECT);
channel現在可以做其它事情了...

b.如果發生了登記的事情
SocketChannel channel = (SocketChannel) key.channel();
相關的channel找到了,就可以接着對這個事情做出反饋了。
channel.write(ByteBuffer.wrap(new String("向服務端發送了一條信息").getBytes()));

對於服務器:
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
上一句也註冊了,接下來可以做其它事情了...
SocketChannel是serverChannel接受好連接產生的通道。是否可以按用戶記錄在map中,服務器想主動發信息時,可以查詢map,找到可用的channel來用?
ServerSocketChannel/SocketChannel前者用於接收呼入的請求(即第一次握手),而後者負責具體的業務處理(即第一次握手成功後,交由其處理)


四、補充
又細看了一部分中國移動empp的源碼,記錄一些知識:
1.協議的設計
協議包括header與body部分,頭部包括三部分,序號號(請求與返回一樣),信息總長度,信息類型。總共是12個byte,這個是固定的。body是不固定的。

2.協議的序列化與把序列化
2.1 發送前要把信息對象轉成byte[12+body.length]
public ByteBuffer getData()
throws ValueNotSetException
{
ByteBuffer bodyBuf = new ByteBuffer();
bodyBuf.appendBuffer(getBody());
setCommandLength(bodyBuf.length() + 12);
ByteBuffer emppBuf = getHeader();
emppBuf.appendBuffer(bodyBuf);
return emppBuf;
}
其中getBody()中就是把信息的所有屬性拼出一個長buffer,比如其中一個:buffer.appendCString(feeTermId, 32);
之後就是用socket的流寫:outputStream.write(data.getBuffer(), 0, data.length());

2.2 收到後要把byte[]轉成對象信息對象
public EMPPObject receive(long timeout)//對應的方法
buffer = connection.receive(12);//先取頭12個byte
ByteBuffer bfBody = connection.receive(length - 12);
buffer.appendBuffer(bfBody);
return EMPPObject.createEMPP(buffer);
//根據頭部信息new出信息對象後,一個個讀取相應長度的byte[]後,設置對象的屬性。
setSrcTermId(buffer.removeCString(21));

2.3 有個疑問:爲何不用java的對象直接寫成流的方式呢???效率嗎?
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章