最近要增加短信平台对移动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协议文档。