浅谈CMPP3协议架构实现

  最近要增加短信平台对移动CMPP3协议的支持,所以就研究了下他的实现。所谓的CMPP就是中国移动通信互联网短信网关接口协议

CMPP 协议以TCP/IP 作为底层通信承载,所以开发这块需要对TCP/IP网络编程要有一定的了解。

原理:个人理解就是双方建立以什么方式来通信,就好比信是暗号写的,只有双方看的懂。

本文主要针对于长连接形式发送短信为例,而我们编写程序也只用编写在C/S架构的通讯过程中的C,然后根据服务商提供的帐号、参数经行测试。

下图是长连接的流程图。


一、实现协议步骤:

            1、建立SOKCET,启动一个线程,发送数据。

 

            2、进行链路检查,判断服务端通信是否正常等等。

            3、启动接收socket数据的线程。

二、协议代码实现:

     1、协议基本类型如下:

Unsigned Integer

无符号整数

Integer

整数,可为正整数、负整数或零

Octet String

定长字符串,位数不足时,如果左补 则补ASCII 表示的零以填充,如果右补 则补二进制的零以表示字符串的结束符

     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协议文档。

 

 

 

 

CMPP模拟器主要是模拟使用中国移动CMPP协议(版本1.x~3.x)的各种网关。此类型网关只使用一个收发连接(短信接收和发送在一个链接上进行)。 模拟器要求使用JDK1.4以上的Java运行环境,请确认相关环境已经安装妥当。如果还没有安装Java环境,请访问java.sun.com下载最新J2SE的SDK。 此模拟器已经使用了全新的核心设计,主要针对应用程序的稳定性、可靠性、效率以及配置、管理和监控方面做了很大的调整。基本界面风格和应用功能上没什么变化。 1.建立Socket连接与登陆 使用自己的CMPP客户端程序,与7890端口建立Socket连接。然后按照CMPP协议发送登陆数据包。模拟器会按照协议处理相关连接和登陆过程。 2.发送短信息 正确建立连接和登陆以后,可以按照CMPP协议的Submit过程提交相关数据并得到应答。模拟器在接收到数据以后,会进行解析并按照协议要求进行应答和回复。相关的处理信息会记录在日志文件中。 3.接收短信息 正确建立连接和登陆以后,在同一连接上等待模拟器的Deliver数据包即可,并且要求客户端按照协议给予应答。模拟器会对相关过程记录在日志信息当中。 4.模拟MT以及状态报告过程 发送submit时,请将registered_delivery设置为1即可。 模拟器收到相关数据包以后,会通过submit_response应答给出message_id;随后模拟出deliver数据包给出状态报告,其中registered_delivery为1。 5.模拟MO过程 发送submit时,请将registered_delivery设置为0即可。 模拟器收到相关数据包以后,会通过deliver请求发送模拟的MO。其中deliver的相关数据全部来自接收到的submit数据。包括来源号码、目标号码、业务代码以及信息内容。 6.模拟压力测试 如果需要进行模拟的完整压力测试过程,只需要以最大速度重复步骤5即可。 模拟器的监控 模拟器有一个基于Web监控后台,系统启动的时候同时启动。缺省端口建立在8081上。监控的URL地址、用户名和密码可以在配置文件中找到。 http://localhost:8081 forest_luo root
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值