? 最近要增加短信平臺對移動CMPP3協議的支持,所以就研究了下他的實現。所謂的 CMPP就是 中國移動通信互聯網 短信網關 接口協議 。
CMPP?協議以TCP/IP?作為底層通信承載,所以開發這塊需要對 TCP/IP網絡編程要有一定的了解。
原理:個人理解就是雙方建立以什么方式來通信,就好比信是暗號寫的,只有雙方看的懂。
本文主要針對于長連接形式發送短信為例, 而我們編寫程序也只用編寫 在C / S架構的通訊過程中的C,然后根據服務商提供的帳號、參數經行測試。
一、實現協議
步驟:
? ? ? ? ? ? 1、 建立 SOKCET,啟動一個線程,發送數據。
?
? ? ? ? ? ? 2、進行 鏈路檢查,判斷服務端通信是否正常等等。
? ? ? ? ? ? 3、 啟動接收socket數據的線程。
二、協議代碼實現:
? ? ?1、協議基本類型如下:
|
Unsigned Integer |
無符號整數 |
|
Integer |
整數,可為正整數、負整數或零 |
|
Octet String |
定長字符串,位數不足時,如果左補 ? 0? 則補 ASCII? 表示的零以填充,如果右補 ? 0? 則補二進制的零以表示字符串的結束符 |
? ? ?2、消息結構:
? ? ? ? ? ? 1)消息頭( 所有消息公共包頭 )PS:注意紅色的部分是所有消息的公共頭
? ? ? ? ? ? 2)和消息體
?
三、接下來就是說說如何封裝CMPP3的消息格式了,取幾個談談就行了,原理都差不多。
?
?
?
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import org.apache.log4j.Logger;
//totalLength 消息總長度
//commandID命令消息類型
//sequenceID消息流水號碼,順序累加,步長為1,循環使用(一隊請求和應答消息的流水號必須相同)
//Unsigned Integer 無符號整型
//Integer 整數,可為正整數、負整數、零
//Octet String 定長字符串,位數不足時,如果左補0則補ASCII表示的0以填充,如果右補0則補二進制的零表示字符串的結束符
public class MsgHead {
private Logger logger = Logger.getLogger(MsgHead.class);
private int totalLength; // unsigned Integer;
private int commandID; // unsigned Integer;
private int sequenceID; // unsigned Integer;
public byte[] toByteArray() {
ByteArrayOutputStream bous = new ByteArrayOutputStream();
DataOutputStream dous = new DataOutputStream(bous);
try {
dous.writeInt(getTotalLength());
dous.writeInt(this.getCommandID());
dous.writeInt(this.getSequenceID());
dous.close();
return bous.toByteArray();
} catch (Exception e) {
if (dous != null)
try {
dous.close();
} catch (Exception ee) {
}
logger.error("封裝CMPP消息頭二進制數組失敗!");
return null;
}
}
}
?代碼片段:
?
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import org.apache.log4j.Logger;
import common.msg.util.MsgUtils;
/**
* sp請求連接到ISMG消息體定義CMPP_CONNECT操作的目的是向ISMG注冊為一個合法SP身份,
* 若注冊成功后建立了應用層的連接,此后SP可以通過此ISMG接收和發送短信。 Source_Addr:Octet String
* 源地址,此處為SP_id,即SP的企業代碼 AuthenticatorSource:Octet String 用于鑒別源地址,其值通過單向MD5
* hash計算得出,表示如下;
* AuthenticatorSource=MD5(source_addr+9個字節的null+secret+timestamp)
* Version:unsigned Integer 雙方協商的版本號,對3.0的版本,高4BIT為3,低4位為0
*/
public class MsgConnect extends MsgHead {
private static Logger logger = Logger.getLogger(MsgConnect.class);
private String sourceAddr;// 源地址,此處為spID;
private byte[] authenticatorSource;// 用于鑒別源地址
private byte version;
private String timeStamp;
public byte[] toByteArray() {
ByteArrayOutputStream bous = new ByteArrayOutputStream();
DataOutputStream dous = new DataOutputStream(bous);
try {
dous.writeInt(this.getTotalLength());
dous.writeInt(this.getCommandID());
dous.writeInt(this.getSequenceID());
MsgUtils.writeString(dous, this.getSourceAddr(), 6,"US-ASCII");
dous.write(this.getAuthenticatorSource());
dous.writeByte(this.getVersion());
dous.writeInt(Integer.parseInt(getTimeStamp()));
bous.close();
dous.close();
return bous.toByteArray();
} catch (Exception e) {
if (bous != null)
try {
bous.close();
} catch (Exception ee) {
}
if (dous != null)
try {
dous.close();
} catch (Exception ee) {
}
e.printStackTrace();
logger.error("封裝鏈接二進制數組失敗。");
return null;
}
}
}
?
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import org.apache.log4j.Logger;
import common.msg.util.MsgUtils;
public class MsgSubmit extends MsgHead {
private static Logger logger = Logger.getLogger(MsgSubmit.class);
private long msgId = 0;// 信息標識,8個字節的unsigned為空
private byte pkTotal = 0x01;// 相同的msgID信息的總條數,從1開始,本業務填寫1
private byte pkNumber = 0x01;// 系統MSGID的信息序號,從1開始,本業務只填寫1
private byte registeredDelivery = 0x01; // 是否要求返回狀態確認報告0不需要,1需要;
private byte msgLevel = 0x01;
private String serviceId = ""; // MSC4310508,業務標識是數字、字母、符號的組合
// 計費類型0:對目的終端MSISDN計費,1對源終端MSISDN計費,2對SP計費,3表示本字段無效
private byte feeUserType = 0x01;// 上線的確認此內容
private String feeTerminalId = "";// 被計費的號碼,當feeUserType為3時有效
private byte feeTerminalType = 0x00;// 被計費用戶的號碼類型,0真實,1偽號碼
private byte tpPId = 0x00;
private byte tpUdhi = 0x00;
// 0ASCII碼字符串,3短信寫卡操作,4二進制信息,8UCS2編碼,15含GB漢字
private byte msgFmt = 0x0F;
private String msgSrc = "";// 信息內容來源SPID;
// 01:對“計費用戶號碼”免費,02:對“計費用戶號碼”按條計信息費
// 03:對“被計費號碼包月計費,04:對被計費號碼封頂,05:對被計費號碼是由SP實現;
private String feeType = "00";// 默認按條
private String feeCode = "000000";
private String validTime = "";// 存或有效期限,17位長度,暫時不支持此功能
private String atTime = "";// 定時發送時間,17位長度,暫時不支持此功能
private String srcId = "";// 源號碼,用戶手機顯示的號碼
private byte destUsrTl = 0x01;// 接受信息的用戶數量(群發)
private String destTerminalId = "";// 接受短信的號碼
private byte destTerminalType = 0x00;// 真實號碼
private byte msgLength;// 消息長度,小于或等于140字節
private byte[] msgContent;// 信息內容;
private String linkID = "";// 點播業務使用,非點播業務則不使用
public byte[] toByteArray(String code) {
ByteArrayOutputStream bous = new ByteArrayOutputStream();
DataOutputStream dous = new DataOutputStream(bous);
try {
dous.writeInt(this.getTotalLength());
dous.writeInt(this.getCommandID());
dous.writeInt(this.getSequenceID());
dous.writeLong(this.getMsgId());// Msg_Id 信息標識,由SP接入的短信網關本身產生,本處填空
dous.writeByte(this.getPkTotal());// Pk_total 相同Msg_Id的信息總條數
dous.writeByte(this.getPkNumber());// Pk_number 相同Msg_Id的信息序號,從1開始
dous.writeByte(this.getRegisteredDelivery());// Registered_Delivery
// 是否要求返回狀態確認報告
dous.writeByte(this.getMsgLevel());// Msg_level 信息級別
MsgUtils.writeString(dous, this.getServiceId(), 10, code);// Service_Id
// 業務標識,是數字、字母和符號的組合。
dous.writeByte(this.getFeeUserType());// Fee_UserType 計費用戶類型字段
// 0:對目的終端MSISDN計費;1:對源終端MSISDN計費;2:對SP計費;3:表示本字段無效,對誰計費參見Fee_terminal_Id字段。
MsgUtils.writeString(dous, this.getFeeTerminalId(), 32, code);// Fee_terminal_Id
// 被計費用戶的號碼
dous.writeByte(this.getFeeTerminalType());// Fee_terminal_type
// 被計費用戶的號碼類型,0:真實號碼;1:偽碼
dous.writeByte(this.getTpPId());
dous.writeByte(this.getTpUdhi());
dous.writeByte(this.getMsgFmt());
MsgUtils.writeString(dous, this.getMsgSrc(), 6, code);// Msg_src
// 信息內容來源(SP_Id)
MsgUtils.writeString(dous, this.getFeeType(), 2, code);// FeeType
// 資費類別
MsgUtils.writeString(dous, this.getFeeCode(), 6, code);
MsgUtils.writeString(dous, this.getValidTime(), 17, code);// 存活有效期
MsgUtils.writeString(dous, this.getAtTime(), 17, code);// 定時發送時間
MsgUtils.writeString(dous, this.getSrcId(), 21, code);// Src_Id
// spCode
dous.writeByte(this.getDestUsrTl());
MsgUtils.writeString(dous, this.getDestTerminalId(), 32, code);
dous.writeByte(this.getDestTerminalType());// Dest_terminal_type
// 接收短信的用戶的號碼類型,0:真實號碼;1:偽碼
dous.writeByte(this.getMsgLength());
dous.write(this.getMsgContent());
MsgUtils.writeString(dous, this.getLinkID(), 20,code);
bous.close();
dous.close();
return bous.toByteArray();
} catch (Exception e) {
if (bous != null)
try {
bous.close();
} catch (Exception ee) {
}
if (dous != null)
try {
dous.close();
} catch (Exception ee) {
}
logger.error("封裝短信發送二進制數組失敗。");
e.printStackTrace();
return null;
}
}
?
其實這些實現挺簡單的,網上也有很多類似代碼,主要要明確的一點就是,客戶端和服務端的通信流程和消息長度,多看協議文檔。懂了原理,實現起來很簡單。
最后附CMPP3協議文檔。
?
?
?
?
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061
微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元

