【MML】华为MML AAA接口联调,Java版本

时间:2022-08-18 00:05:33

1、我们先设置一些常量数据

package cn.cutter.ztesoft.HuWeiMML.constrant;

/**
* @description: AAA接口常量设置
* @author: xiaof
* @create: 2018-07-26 10:07
**/
public class InfAAAMissionConstrant { /**
*订单号,工单号,宽带账号
*/
public static final String AAA_ORDER_ID = "orderId";
public static final String AAA_WORK_ORDER_ID = "workOrderId";
public static final String AAA_ACCOUNT = "accNbr";
public static final String USER_MOBILE = "mobile"; /**
* 配置信息
*/
// public static final String IOM_IP; public static final String AAA_IP = "IP";
public static final String AAA_PORT = "PORT";
public static final String AAA_USER_NAME = "USERNAME";
public static final String AAA_PASS_WORD = "PASSWORD";
public static final String AAA_CONFIG_TYPE = "AAA_SOCKET_INFO"; public static final String SERVICEFLAG = "AAA"; //消息头 AAA_MSG_STARTING_INT \x1C\x1D\x1E\x1F
public static final String AAA_MSG_STARTING = "`SC`"; public static final int AAA_MSG_STARTING_INT = 0x1C1D1E1F; public static final byte[] AAA_MSG_STARTING_BYTE = {0x1C, 0x1D, 0x1E, 0x1F}; /**
* AAA IIN类型格式发送字节消息 0x60, 0x53, 0x43, 0x60
*/
public static final byte[] AAA_IIN_MSG_STARTING_BYTE = {0x60, 0x53, 0x43, 0x60}; public static final int AAA_MSG_STARTTAG_LEN = 4; //消息开始标识长度 public static final int AAA_MSG_COMM_LEN = 12; /**
* 消息头长度
*/
public static final int AAA_MSG_HEAD_LEN = 20; /**
* 消息长度部位
*/
public static final int AAA_MSG_INFO_LEN = 4; //消息版本号
public static final String AAA_MSG_VERSION = "1.00"; public static final Integer AAA_MSG_VERSION_LEN = 4; public static final int AAA_MSG_STARTING_LEN = 4; /**
* 终端标识
*/
public static final String AAA_MSG_TERMINAL = "internal"; public static final Integer AAA_MSG_TERMINAL_LEN = 8; public static final Integer AAA_SERVICE_CODE_LEN = 8; public static final Integer AAA_MAX_HEAD_LEN = 56; // 会话控制字包括:DLGLGN,DLGBEG,DLGCON,DLGEND。
// 说明
// 操作员登录MML Server时客户端发DLGLGN,在进行其他操作时,客户端均发DLGCON。
// 操作员退出时,MMLServer给营帐的返回消息中会话控制字为DLGEND,表示会话的结束。
/**
* 会话id长度
*/
public static final int AAA_DLG_ID_LEN = 8; public static final String AAA_DLG_LGN = "DLGLGN"; public static final String AAA_DLG_BEG = "DLGBEG"; public static final String AAA_DLG_CON = "DLGCON"; public static final String AAA_DLG_END = "DLGEND"; /**
* 会话控制字长度
*/
public static final int AAA_DLG_CONTROLLER_LEN = 6; /**
* 会话保留字
*/
public static final int AAA_DLG_RSVD_LEN_DLGRSVD = 4;
/**
* 会话头长度
*/
public static final int AAA_DLG_HEAD_LEN = 18; // 事务控制字包括:TXBEG,TXCON,TXEND。
// 说明
// 由Provision发起的操作,其事务控制字填写TXBEG。
// 当一条MML命令的消息结束时,MML Server返回给Provision的事务控制字为TXEND,表示一条事务结束。 /**
* 事务id长度
*/
public static final int AAA_TX_ID_LEN = 8; public static final String AAA_TX_BEG = "TXBEG"; public static final String AAA_TX_CON = "TXCON"; public static final String AAA_TX_END = "TXEND"; public static final Integer AAA_TX_CONTROLLER_LEN = 6; /**
* 事务保留字长度
*/
public static final int AAA_TX_RSVD_LEN_DLGRSVD = 4; /**
* 事务头长度
*/
public static final int AAA_TX_HEAD_LEN = 18; /**
* 校验和长度
*/
public static final Integer AAA_CHK_LEN = 4; /**
* IIN校验和长度
*/
public static final Integer AAA_IIN_CHK_LEN = 8; /**
* 报文字段
*/
/**
* 返回值。十进制整数类型。
* 0表示执行成功,其他返回值的解释请参见DESC字段
*/
public static final String RETN = "RETN"; /**
* 查询属性名列表,以“&”分隔。
*/
public static final String ATTR = "ATTR"; /**
* 返回的记录的值,用&分割结果。s
*/
public static final String RESULT = "RESULT"; }

2、创建对应的信息vo载体

package cn.cutter.ztesoft.HuWeiMML.vo;

/**
* @program:
* @description:
* @author: xiaof
* @create: 2018-07-26 15:25
**/
public class MsgInfo {
/**
* 消息和消息长度
*/
private byte msg[];
private int msgLen; // 查询用户信息获取 用户IP地址 USERIPADDRESS,USERPORT 用户端口号,业务标识SERVICEFLAG 默认AAA
private String userIpAddress;
private String userPort;
private String serviceFlag; //AAA服务类型,默认C280
private String serviceCode = "C280"; //AAA用来做指令标识
private String workOrderId; private String userName;
private String passWord; public byte[] getMsg() {
return msg;
} public void setMsg(byte[] msg) {
this.msg = msg;
} public int getMsgLen() {
return msgLen;
} public void setMsgLen(int msgLen) {
this.msgLen = msgLen;
} public String getUserIpAddress() {
return userIpAddress;
} public void setUserIpAddress(String userIpAddress) {
this.userIpAddress = userIpAddress;
} public String getUserPort() {
return userPort;
} public void setUserPort(String userPort) {
this.userPort = userPort;
} public String getServiceFlag() {
return serviceFlag;
} public void setServiceFlag(String serviceFlag) {
this.serviceFlag = serviceFlag;
} public String getServiceCode() {
return serviceCode;
} public void setServiceCode(String serviceCode) {
this.serviceCode = serviceCode;
} public String getWorkOrderId() {
return workOrderId;
} public void setWorkOrderId(String workOrderId) {
this.workOrderId = workOrderId;
} public String getUserName() {
return userName;
} public void setUserName(String userName) {
this.userName = userName;
} public String getPassWord() {
return passWord;
} public void setPassWord(String passWord) {
this.passWord = passWord;
}
}

3、创建编码解码器,进行报文的编码解码(关键,划重点哦,特别是校验和的计算)

package cn.cutter.ztesoft.HuWeiMML.Template;

import cn.cutter.ztesoft.HuWeiMML.vo.MsgInfo;

import java.io.IOException;

/**
* @program:
* @description: MML消息解码,编码器
* @author: xiaof
* @create: 2018-08-15 11:38
**/
public interface MsgCoder { /**
* 指令编码
* @param msg
* @return
* @throws IOException
*/
byte[] toWire(MsgInfo msg) throws IOException; /**
* 指令解码
* @param input
* @return
* @throws IOException
*/
byte[] fromWire(byte input[]) throws IOException;
}
package cn.cutter.ztesoft.HuWeiMML.Template;

import cn.cutter.ztesoft.HuWeiMML.constrant.InfAAAMissionConstrant;
import cn.cutter.ztesoft.HuWeiMML.vo.MsgInfo;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import java.util.Arrays; /**
*
*
* 1 1 1 1 1 1
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | 0x60, 0x53, 0x43, 0x60 |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | msg length 4B |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | |
* | msg head 20B |
* | |
* | |
* | |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | |
* | Conversation head (18B) |
* | |
* | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | | |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
* | |
* | transaction head (18) |
* | |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | |
* ~ operator msg(N * 4B) ~
* | |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | |
* | check(8B) |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*
* @program:
* @description: MML指令编码解析
* @author: xiaof
* @create: 2018-08-15 11:40
**/
public class MMLMsgBinCoder implements MsgCoder { private static final Log logger = LogFactory.getLog(MMLMsgBinCoder.class); @Override
public byte[] toWire(MsgInfo msg) { //1.输出消息开始标识 4字节
byte beginMarkBytes[] = InfAAAMissionConstrant.AAA_IIN_MSG_STARTING_BYTE; //2.输出消息的长度4字节 从消息头到操作信息结束(包括填充的空格)的长度,16进制字符(0-F)表示的16位整数(4B),取值范围为56到65000(10进制)
int msglen = InfAAAMissionConstrant.AAA_MAX_HEAD_LEN + msg.getMsgLen();
int len = 4 - msglen % 4;
msglen += len; //使消息长度为4字节的倍数
//转换为16进制的字符
byte msgLengthBytes[] = numToHexStr(msglen, InfAAAMissionConstrant.AAA_MSG_INFO_LEN).getBytes(); //3.消息头 20字节 版本号(4B)+终端标识(8B)+服务名(8B)
byte msgHeadBytes[] = msgHead(msg.getServiceCode()); //4.会话头 18字节
byte dlgrsvdBytes[] = dlgrsvd(msg.getUserIpAddress(), msg.getWorkOrderId()); //5.事务头 18字节
byte txheadBytes[] = txHead(msg.getUserIpAddress(), msg.getWorkOrderId()); //6.操作信息N*4字节
int operatorLen = (4 - msg.getMsgLen() % 4) + msg.getMsgLen();
String operatorMsg = changeToByteStr(new String(msg.getMsg()), operatorLen);
byte operatorBytes[] = operatorMsg.getBytes(); //7.校验和 8字节
// 校验和=对“消息头+会话头+事务头+操作信息”组成的字符数组按32位分段后异或所得的结果再取反得到的值。
byte checkBytes[] = new byte[msglen];
//消息头 20B
System.arraycopy(msgHeadBytes, 0, checkBytes, 0, InfAAAMissionConstrant.AAA_MSG_HEAD_LEN);
//会话头 18b
System.arraycopy(dlgrsvdBytes, 0, checkBytes, 0 + InfAAAMissionConstrant.AAA_MSG_HEAD_LEN, InfAAAMissionConstrant.AAA_DLG_HEAD_LEN);
//事务头 18B
System.arraycopy(txheadBytes, 0, checkBytes, InfAAAMissionConstrant.AAA_MSG_HEAD_LEN
+ InfAAAMissionConstrant.AAA_DLG_HEAD_LEN, InfAAAMissionConstrant.AAA_TX_HEAD_LEN);
//操作信息
System.arraycopy(operatorBytes, 0, checkBytes, InfAAAMissionConstrant.AAA_MSG_HEAD_LEN
+ InfAAAMissionConstrant.AAA_DLG_HEAD_LEN + InfAAAMissionConstrant.AAA_TX_HEAD_LEN, operatorLen);
// byte checkModBytes[] = checkSum(msglen, checkBytes);
byte checkModBytes[] = createCheckSumString(checkBytes); //组合所有信息=开始标识+消息长度+消息头+会话头+事务头+操作信息+校验和
byte resultBytes[] = new byte[InfAAAMissionConstrant.AAA_MSG_STARTING_LEN + InfAAAMissionConstrant.AAA_MSG_INFO_LEN
+ msglen + InfAAAMissionConstrant.AAA_IIN_CHK_LEN];
// byte resultBytes[] = new byte[InfAAAMissionConstrant.AAA_MSG_STARTING_LEN + InfAAAMissionConstrant.AAA_MSG_INFO_LEN
// + msglen]; //开始标识 4B
System.arraycopy(InfAAAMissionConstrant.AAA_IIN_MSG_STARTING_BYTE, 0, resultBytes, 0,
InfAAAMissionConstrant.AAA_IIN_MSG_STARTING_BYTE.length);
//消息长度 4B
System.arraycopy(msgLengthBytes, 0, resultBytes, InfAAAMissionConstrant.AAA_IIN_MSG_STARTING_BYTE.length,
msgLengthBytes.length);
//消息头 20B 消息头+会话头+事务头+操作信息
System.arraycopy(checkBytes, 0, resultBytes, InfAAAMissionConstrant.AAA_IIN_MSG_STARTING_BYTE.length
+ msgLengthBytes.length, checkBytes.length);
// 校验和
System.arraycopy(checkModBytes, 0, resultBytes, InfAAAMissionConstrant.AAA_IIN_MSG_STARTING_BYTE.length
+ msgLengthBytes.length + checkBytes.length, checkModBytes.length); return resultBytes;
} @Override
public byte[] fromWire(byte[] input) {
//1.读取MML不包含开始标识和长度字节
byte curBytes[] = input;
byte msgBytes[] = null; try {
//2.解析消息头(20B)=版本号(4B)+终端标识(8B)+服务名(8B)
curBytes = Arrays.copyOfRange(curBytes, InfAAAMissionConstrant.AAA_MSG_VERSION_LEN
+ InfAAAMissionConstrant.AAA_MSG_TERMINAL_LEN + InfAAAMissionConstrant.AAA_SERVICE_CODE_LEN, curBytes.length); //3.解码会话头 会话头(18)=会话ID(8B)+会话控制字(6B)+会话保留字(4B)
curBytes = Arrays.copyOfRange(curBytes, InfAAAMissionConstrant.AAA_DLG_ID_LEN
+ InfAAAMissionConstrant.AAA_DLG_CONTROLLER_LEN + InfAAAMissionConstrant.AAA_DLG_RSVD_LEN_DLGRSVD, curBytes.length); //4.解码事务头 事务头=事务ID(8B)+事务控制字(6B)+事务保留字(4B)
curBytes = Arrays.copyOfRange(curBytes, InfAAAMissionConstrant.AAA_TX_ID_LEN
+ InfAAAMissionConstrant.AAA_TX_CONTROLLER_LEN + InfAAAMissionConstrant.AAA_DLG_RSVD_LEN_DLGRSVD, curBytes.length); //5.解码操作信息
int recMsgLen = input.length - InfAAAMissionConstrant.AAA_MAX_HEAD_LEN - InfAAAMissionConstrant.AAA_IIN_CHK_LEN;
msgBytes = Arrays.copyOfRange(curBytes, 0, recMsgLen); //6.解码校验和 “消息头+会话头+事务头+操作信息”组成的字符数组按32位分段后异或所得的结果再取反得到的值。
curBytes = Arrays.copyOfRange(curBytes, recMsgLen, InfAAAMissionConstrant.AAA_IIN_CHK_LEN);
byte chkBytes[] = Arrays.copyOfRange(input, 0, InfAAAMissionConstrant.AAA_MAX_HEAD_LEN); //计算校验和
byte chkStr[] = createCheckSumString(chkBytes);
//7.判断校验是否通过
if(Arrays.equals(curBytes, chkBytes)) {
throw new Exception("校验不匹配");
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
} //8.返回解码之后的信息
return msgBytes;
} public static byte[] createCkeckSum(byte msg[])
{
int i = 0;
int j = 0;
byte checksum[] = new byte[4];
for (i = 0; i < msg.length / 4; i++)
for (j = 0; j < 4; j++)
checksum[j] ^= msg[i * 4 + j]; for (j = 0; j < msg.length % 4; j++)
checksum[j] ^= msg[i * 4 + j]; for (i = 0; i < 4; i++)
{
int k = ~checksum[i] & 0xff;
checksum[i] = (byte)k;
} return checksum;
} public static byte[] createCheckSumString(byte msg[])
{
byte checksum[] = createCkeckSum(msg);
StringBuffer sb = new StringBuffer();
for (int i = 0; i < 4; i++)
{
String s = Integer.toHexString(checksum[i] & 0xff).toUpperCase();
if (s.length() < 2)
sb.append("0").append(s);
else
sb.append(s);
} return sb.toString().getBytes();
} private String numToHexStr(int numCount, int defaultLen) { // StringBuffer sb = new StringBuffer();
//1.10进制转换为16进制,并且是4位的16进制
StringBuffer hexStr = new StringBuffer(Integer.toHexString(numCount));
for(int i = hexStr.length(); i < defaultLen; ++i) {
hexStr.insert(0, '0');
} return hexStr.toString();
} private String changeToByteStr(String str, int defaultLen) {
StringBuffer sourceSb = new StringBuffer(); //判断目标字符串是否满足对应长度要求
int i = 0;
while(i < defaultLen) { if(i < str.length()) {
sourceSb.append(str.charAt(i));
} else {
sourceSb.append(" ");
} ++i;
}
return sourceSb.toString();
} /**
* 消息头=版本号(4B)+终端标识(8B)+服务名(8B)
* 消息头20个字节
*/
private byte[] msgHead(String serviceCode) {
StringBuffer sb = new StringBuffer();
//版本号 4个字节 sb.append(changeToByteStr(InfAAAMissionConstrant.AAA_MSG_VERSION, InfAAAMissionConstrant.AAA_MSG_VERSION_LEN)); //终端标识
sb.append(changeToByteStr(InfAAAMissionConstrant.AAA_MSG_TERMINAL, InfAAAMissionConstrant.AAA_MSG_TERMINAL_LEN)); //服务名,不出什么意外,默认就是C280
sb.append(changeToByteStr(serviceCode, InfAAAMissionConstrant.AAA_SERVICE_CODE_LEN)); return sb.toString().getBytes();
} /**
* 会话头信息
* 会话头=会话ID(8B)+会话控制字(6B)+会话保留字(4B)
*/
private byte[] dlgrsvd(String ip, String workOrderId) {
StringBuffer sb = new StringBuffer();
//我们会话id 工单id
// String plgid = changeToByteStr(workOrderId, InfAAAMissionConstrant.AAA_DLG_ID_LEN);
String plgid = numToHexStr(Integer.valueOf(workOrderId), InfAAAMissionConstrant.AAA_DLG_ID_LEN);
sb.append(plgid);
// 会话控制字包括:DLGLGN,DLGBEG,DLGCON,DLGEND。
// 说明
// 操作员登录MML Server时客户端发DLGLGN,在进行其他操作时,客户端均发DLGCON。
// 操作员退出时,MMLServer给营帐的返回消息中会话控制字为DLGEND,表示会话的结束。
sb.append(changeToByteStr(InfAAAMissionConstrant.AAA_DLG_LGN, InfAAAMissionConstrant.AAA_DLG_CONTROLLER_LEN));
//会话保留字
// 会话保留字与事务保留字共同存储IP地址。
// 说明 长度为4个字节的十六进制字符
// 访问AAA客户端的IP地址,并使用会话保留字和事务保留字传递客户端的IP地址。
// 例如:十进制的IP地址为长度为4个字节的十六进制字符10.10.25.1,转换为十六进制的IP地址为A.A.19.1,前4个字节存储到会话保留字中,后4个字节保留到事务保留字。
// String ip = "127.0.0.1";
String ipNum[] = ip.split("\\.");
StringBuffer ipSb = new StringBuffer(); //获取十进制的IP地址为长度为4个字节的十六进制字符10.10.25.1,转换为十六进制的IP地址为A.A.19.1,前4个字节存储到会话保留字中,后4个字节保留到事务保留字。
//1.循环遍历4个数据,转换为16进制字符串,取前4个字节
for(int i = 0; i < ipNum.length; ++i) {
String hexStr = " ";
if(!ipNum[i].equals("")) {
hexStr = Integer.toHexString(Integer.valueOf(ipNum[i]));
}
for(int j = hexStr.length(); j < 2; ++j) {
hexStr = "0" + hexStr;
}
ipSb.append(hexStr);
}
//删除最后一个点
// ipSb.deleteCharAt(ipSb.length() - 1); sb.append(changeToByteStr(ipSb.substring(0, 4), InfAAAMissionConstrant.AAA_DLG_RSVD_LEN_DLGRSVD)); return sb.toString().getBytes();
} /**
* 事务头=事务ID(8B)+事务控制字(6B)+事务保留字(4B)
* @return
*/
private byte[] txHead(String ip, String workOrderId) {
// 事务ID由客户端产生。如果没有并行的操作,所有的事务ID都可以填1。如果需要使用并行操作,则客户端必须保证当前并行的所有操作中事务ID是不同的。
// 长度为8个字节的整数,用16进制字符表示。
StringBuffer sb = new StringBuffer(); //事务id
String txId = numToHexStr(Integer.valueOf(workOrderId), InfAAAMissionConstrant.AAA_TX_ID_LEN);
sb.append(txId);
// 事务控制字包括:TXBEG,TXCON,TXEND。
// 说明
// 由Provision发起的操作,其事务控制字填写TXBEG。
// 当一条MML命令的消息结束时,MML Server返回给Provision的事务控制字为TXEND,表示一条事务结束。
String txControl = InfAAAMissionConstrant.AAA_TX_CON;
sb.append(changeToByteStr(txControl, InfAAAMissionConstrant.AAA_TX_CONTROLLER_LEN));
//事务保留字(4B)
// 访问AAA客户端的IP地址,并使用会话保留字和事务保留字传递客户端的IP地址。
// AAA访问客户端的IP地址,并使用会话保留字和事务保留字传递客户端的IP地址。 例如:十进制的IP地址为长度为4个字节的十六进制字符10.10.25.1,
// 转换为十六进制的IP地址为A.A.19.1,前4个字节存储到会话保留字中,后4个字节保留到事务保留字。 长度为4个字节的十六进制字符
//1.分解ip,取后置的4个字节
String ipNum[] = ip.split("\\.");
//组装16进制值
StringBuffer hexStr = new StringBuffer();
for(int i = 0; i < ipNum.length; ++i) {
hexStr.append(Integer.toHexString(Integer.valueOf(ipNum[i]))).append(".");
} hexStr = hexStr.deleteCharAt(hexStr.length() - 1);
//取最后4个字节
// String ipResult = hexStr.substring(hexStr.length() - 4, hexStr.length());
//事务头=事务ID(8B)+事务控制字(6B)+事务保留字(4B)
sb.append(changeToByteStr(hexStr.substring(hexStr.length() - 4, hexStr.length()), InfAAAMissionConstrant.AAA_TX_RSVD_LEN_DLGRSVD)); return sb.toString().getBytes();
} /**
* 校验和IIN模式 4字节
* 校验和=对“消息头+会话头+事务头+操作信息”组成的字符数组按32位分段后异或所得的结果再取反得到的值
* @return
*/
private byte[] checkSum(int msgLen, byte msg[]) {
byte res[] = new byte[4]; for(int i = 0; i < msgLen; i+=4) {
res[0] ^= msg[i + 0];
res[1] ^= msg[i + 1];
res[2] ^= msg[i + 2];
res[3] ^= msg[i + 3];
} //最后取反
res[0] = (byte) ~res[0];
res[1] = (byte) ~res[1];
res[2] = (byte) ~res[2];
res[3] = (byte) ~res[3]; String resStr = new String("");
for (int i = 0; i < 4; i++) {
resStr = resStr + byte2hex(res[i]);
} // String resStr = new String(res);
// for (int i = 0; i < 4; i++) {
// resStr = resStr + byte2hex(res[i]);
// } // 将16进制数扩展为对应字符数组(如0xE8--->"E8")
// for(int i = 7; i >= 0; --i) {
// if(i % 2 == 1) {
// //低4位所代表16进制表字符扩展为一个字节
// res[i] = (byte) (res[i / 2] & 0x0F + '0');
// if(res[i] > '9') {
// res[i] = (byte) (res[i] + 'A' - '0' - 10);
// }
// } else {
// ////高4位所代表16进制表字符扩展为一个字节
// res[i] = (byte) (((res[i / 2] >> 4) & 0x0F) + '0');
// if(res[i] > '9') {
// res[i] = (byte) (res[i] + 'A' - '0' - 10);
// }
// }
// } return resStr.getBytes();
} /**
* 将单字节转成16进制
*
* @param b
* @return
*/
private String byte2hex(byte b) {
StringBuffer buf = new StringBuffer();
char[] hexChars = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
int high = ((b & 0xf0) >> 4);
int low = (b & 0x0f);
buf.append(hexChars[high]);
buf.append(hexChars[low]);
return buf.toString();
} }

4、创建对应的成帧器,来获取发送每一帧信息

package cn.cutter.ztesoft.HuWeiMML.Template;

import java.io.IOException;
import java.io.OutputStream; /**
* @program:
* @description: 成帧器
* @author: xiaof
* @create: 2018-08-15 10:34
**/
public interface Framer { /**
* 添加成帧信息并将制定消息输出到制定流
* @param message
* @param out
* @throws IOException
*/
void frameMsg(byte message[], OutputStream out) throws IOException; /**
* 扫描指定的流,抽取下一条消息
* @return
* @throws IOException
*/
byte[] nextMsg() throws IOException; }
package cn.cutter.ztesoft.HuWeiMML.Template;

import cn.cutter.ztesoft.HuWeiMML.constrant.InfAAAMissionConstrant;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays; /**
* @program:
* @description: MML成帧器
* @author: xiaof
* @create: 2018-08-15 10:35
**/
public class MMLFramer implements Framer { private static final Log logger = LogFactory.getLog(MMLFramer.class); private static final int MAX_MESSAGE_LENGTH = 65535;
private static final int BYTE_MASK = 0xff;
private static final int SHORT_MASK = 0xffff;
private static final int BYTE_SHIFT = 8; private DataInputStream in; public MMLFramer(InputStream in) {
this.in = new DataInputStream(in);
} @Override
public void frameMsg(byte[] message, OutputStream out) throws IOException {
//1.判断消息是否超长了
if(message.length > MAX_MESSAGE_LENGTH) {
throw new IOException("消息超长了");
} //输出消息
out.write(message);
out.flush();
} @Override
public byte[] nextMsg() throws IOException {
boolean isMsg = false;
int length = 0; try {
//1.读取2个字节,用来获取信息长度信息
byte beginMark[] = new byte[4];
byte msgLength[] = new byte[4];
in.read(beginMark);
//判断是否,IIN开始标识
if(Arrays.equals(beginMark, InfAAAMissionConstrant.AAA_IIN_MSG_STARTING_BYTE)) {
isMsg = true;
} else {
return new byte[0];
} //读取消息长度 从消息头到操作信息结束(包括填充的空格)的长度,16进制字符(0-F)表示的16位整数(4B),取值范围为56到65000(10进制)
in.read(msgLength);
length = tranByteToInt(msgLength); } catch (IOException e) {
// e.printStackTrace(); //输出报错信息
logger.error(e.getMessage(), e);
return null;
}
//2.创建相应长度的字节数组
byte msg[] = new byte[length + InfAAAMissionConstrant.AAA_IIN_CHK_LEN];
//3.读取相应数据长度字节进入数组
in.readFully(msg); //这个方法会不断读取数据,直到数组填满,否则阻塞 return msg;
} /**
* 转换消息长度
* @return
*/
private int tranByteToInt(byte byteMsgLength[]) {
//获取数据16进制
String hexStr = new String(byteMsgLength); int result = Integer.parseInt(hexStr, 16); return result;
} }

5、根据模板模式,设计模板类,用来与MML服务器通信

package cn.cutter.ztesoft.HuWeiMML.Template;

import java.io.InputStream;
import java.io.OutputStream; /**
* @program:
* @description: MML操作接口
* @author: xiaof
* @create: 2018-08-15 15:17
**/
public interface MMLOperatorInvoke { void doCammand(InputStream in, OutputStream os); }
package cn.cutter.ztesoft.HuWeiMML.Template;

import cn.cutter.ztesoft.HuWeiMML.vo.MsgInfo;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import java.io.*;
import java.net.Socket; /**
* @program: 湖北移动智慧装维支撑系统
* @description: 华为MML指令IIN类型
* @author: xiaof
* @create: 2018-08-15 10:29
**/
public class AAAMMLIINTemplate { private static final Log logger = LogFactory.getLog(AAAMMLIINTemplate.class); private static final String MML_LOGOUT = "logout:";
private static final String MML_LOGIN_COMMAND = "LOGIN:USER={1},PSWD={2}"; public static void sendMML(String ip, int port, MsgInfo msgInfo, MMLOperatorInvoke mmlOperatorInvoke) { //1.声明相应输入输出对象变量
InputStream is = null;
OutputStream os = null;
BufferedReader br = null; try {
//2.创建socket对象
Socket socket = new Socket(ip, port);
socket.setSoTimeout(10 * 1000); //10s超时
//3.建立相应输入输出流
is = socket.getInputStream();
br = new BufferedReader(new InputStreamReader(is));
os = socket.getOutputStream(); //先发送登陆指令
MsgCoder msgCoder = new MMLMsgBinCoder();
String loginMsgCommand = MML_LOGIN_COMMAND.replace("{1}", msgInfo.getUserName())
.replace("{2}", msgInfo.getPassWord());
msgInfo.setMsg(loginMsgCommand.getBytes());
msgInfo.setMsgLen(loginMsgCommand.length());
msgInfo.setServiceCode("C280");
byte loginMsg[] = msgCoder.toWire(msgInfo);
int loginTimes = 0;
boolean isOk = false;
os.write(loginMsg);
os.flush();
byte buf[] = new byte[65500]; logger.info("发送AAA登陆信息:" + new String(loginMsg)); while(loginTimes < 3 && !isOk) {
is.read(buf);
logger.info(new String(buf));
++loginTimes;
if(buf.length > 0)
isOk = true;
} if(isOk)
mmlOperatorInvoke.doCammand(is, os); } catch (Exception e) {
logger.error(e.getMessage(), e);
} finally {
try {
//退出登陆logut
MsgCoder msgCoder = new MMLMsgBinCoder();
msgInfo.setMsg(MML_LOGOUT.getBytes());
msgInfo.setMsgLen(MML_LOGOUT.length());
byte logoutMsg[] = msgCoder.toWire(msgInfo);
logger.info("发送AAA登出信息:" + new String(logoutMsg));
os.write(logoutMsg);
os.flush();
if(br != null) {
br.close();
}
if(is != null) {
is.close();
}
if(os != null) {
os.close();
}
} catch (IOException e) {
logger.error(e.getMessage(), e);
}
}
} public static void sendMMLHeartBeat(String ip, int port, MsgInfo msgInfo, MMLOperatorInvoke mmlOperatorInvoke) { //1.声明相应输入输出对象变量
InputStream is = null;
OutputStream os = null;
BufferedReader br = null; try {
//2.创建socket对象
Socket socket = new Socket(ip, port);
socket.setSoTimeout(10 * 1000); //10s超时
//3.建立相应输入输出流
is = socket.getInputStream();
br = new BufferedReader(new InputStreamReader(is));
os = socket.getOutputStream(); //先发送登陆指令 mmlOperatorInvoke.doCammand(is, os); } catch (Exception e) {
logger.error(e.getMessage(), e);
} finally {
try {
//退出登陆logut
if(br != null) {
br.close();
}
if(is != null) {
is.close();
}
if(os != null) {
os.close();
}
} catch (IOException e) {
logger.error(e.getMessage(), e);
}
}
} }

6、发送指令操作

直接调用(各个地方的某些字段可能不同,这个参考常量文件设置,还有部分参数在msginfo中设置,其余部分基本不需要修改,直接使用)

AAAMMLIINTemplate.sendMML 方法即可

最后想说一句,这个接口是真的不友好,特别是跟我联调的那哥们都不了解他们自己的服务器,接口文档也不详细,问啥都是不知道,哎,真的是伤,

脑壳疼,希望这里能帮助广大没办法只能调华为的这个鬼MML接口的同行们了。。。