文章介绍
之前针对公司的软件进行了一个升级,将一个传送软件单独开发出来了,但是程序健壮性不够,需要添加一些特殊状况的判断!虽说一般对于102规约来讲,短帧报文出现连包的可能性比较小,但是老大要求,还是得做了!
目录
[TOC]
开发思路
基本上总体的功能是已经完善了,但是针对一些连包丢包的状况的处理还不够优化,目前的处理是,针对错误的包,直接丢了,因为tcp本身会自动重传嘛,可惜老大说不行,好吧,重新弄了!
输入部分
为了方便测试,只能在这个类里面添加一些方法了,需要将一些规约的报文,手动输入,以此模拟各种网络的状况了!大致的代码如下:
//专门用于测试各种正常的异常的报文片段
public static void main(String[] args) {
//创建规约对象
IEC102Socket sock = new IEC102Socket(null, null, null);
//接收外部输出
Scanner scan = new Scanner(System.in);
//正常固定帧报文 "107AFFFF7816";
//正常非固定帧报文 "680D006853FFFF96010AFFFF00BA020000AC16";
System.out.println("接收数据中:");
try {
//开始新的规约接收字段
sock.setFrame(true);
// 判断是否还有输入
while (scan.hasNext()) {
String str1 = scan.next();
//输出一下输入的字段
System.out.println("输入的数据为:" + str1);
//将字符串转换为对应的十六进制字节流比方说说如 16 转换为 0x16 那种
byte[] hexStr2Byte = hexStr2Byte(str1);
//测试一下转换是否正常
System.out.println(IECFunctions.byteArrayToHexString(hexStr2Byte, 0, hexStr2Byte.length));
//开始进行正常的接收数据的解析
sock.TestRtuFJ102_MsgRecv(hexStr2Byte);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
scan.close();
}
字节流转换部分
测试的部分是有了,然后还是其他的部分,字节转换的部分在这里:
public static byte[] hexStr2Byte(String hex) {
ByteBuffer bf = ByteBuffer.allocate(hex.length() / 2);
for (int i = 0; i < hex.length(); i++) {
String hexStr = hex.charAt(i) + "";
i++;
hexStr += hex.charAt(i);
byte b = (byte) Integer.parseInt(hexStr, 16);
bf.put(b);
}
return bf.array();
}
public static String byteArrayToHexString(byte[] b, int offset, int count) {
StringBuffer sb = new StringBuffer(count * 2);
for (int i = offset; i < offset + count; i++) {
int v = b[i] & 0xff;
if (v < 16) {
sb.append('0');
}
sb.append(Integer.toHexString(v) + " ");
}
return sb.toString().toUpperCase();
}
这个就是其他的方法了。
开始解析部分
然后最重要的还是对于各种包的一个判断了!由于之前是C语言些的,改编成java会有一些遗漏,所以还得测试完善。
目前大致的样子如下:
/**
* 接收报文存储到缓冲区
*
* @return
* @throws IOException
*/
public boolean TestRtuFJ102_MsgRecv(byte[] bytes) throws IOException {
//原始数据从sock读取,这里从字节流去模拟
//this.IPcount = (char) stream.read(this.RXdata);
this.RXdataAll = bytes;
this.IPcountAll = bytes.length;
//已经关闭,没有获取到数据
if (IPcountAll == -1) {
// closed by remote socket
LOG.log.warn("?remote closed?");
interrupt();
}
//遍历所有的数据
for (int it = 0; it < IPcountAll; it++) {
//判断是否是新的一帧,新帧这进入下面读取字节长度的部分
if (newFrame) {
//是新帧,则在下方获取长度后不再进入,否则直接追加数据到对应长度
//此处不可以先判断是否是短帧,否则会造成长帧的长度首位如何和短帧一直的异常!
//针对连包的处理,如果连包恰巧只剩下开头的三个及之内的状况需要特殊处理,其他的状况可以正常流程处理
if (it + 2 < RXdataAll.length) {
//针对非固定帧,固定帧进行特殊处理
//正常结束之后正常开始完整报文
//如果规约的头三个字段正常则进行这里的操作
if (!isLastOneData) {
APDUPointer = 0;
if (this.RXdataAll[it] == 0x10) {
this.IPcount = 6;
} else if (this.RXdataAll[it] == 0x68) {
//是非固定帧,并且长度位齐全状况下处理
// "长度校验,高低地址弄错,标准帧此处两个是重复位,系统中第二位为0,为了数据完整直接将0取高位"
int lenFrame = IECFunctions.byte2ToInt(this.RXdataAll[it + 1], this.RXdataAll[it + 2]) + 6;
this.IPcount = lenFrame;
}
//设置为新帧为fales,不再获取长度,而是直接向后追加到正常长度位
newFrame = false;
// trace("_ADPU length:"+APDUlength);
} else if (!isHalfLength) {
//长度位如果没有被截取,但是前面只是获取到了规约的第一个字段,需要特殊处理
//这里直接根据前面的截取标记 isHalfLength 直接读取长度,不用判断帧的类别了
int lenFrame = IECFunctions.byte2ToInt(this.RXdataAll[it], this.RXdataAll[it + 1]) + 6;
this.IPcount = lenFrame;
newFrame = false;
isLastOneData = false;
} else {
//如果恰巧将第一,二字段截取,只剩下第三个字段为新的一帧开始时候
//需要已经接收到的第二帧的数据,以及新接收到的数据,构成长度位
int lenFrame = IECFunctions.byte2ToInt(this.RXdata[APDUPointer - 1], this.RXdataAll[it]) + 6;
this.IPcount = lenFrame;
newFrame = false;
isLastOneData = false;
}
} else {
//如果已经处理过了长度被截取的非固定帧,这里不需要再执行!
if (!isLastOneData) {
APDUPointer = 0;
isLastOneData = true;
//针对非固定帧的处理,如果长度位被截取一半,则作标记一下
if (it + 1 < RXdataAll.length) {
isHalfLength = true;
}
//针对固定帧的处理,如果只有10的截取
if (this.RXdataAll[it] == 0x10) {
this.IPcount = 6;
newFrame = false;
}
}
}
}
this.RXdata[APDUPointer] = RXdataAll[it];
if (APDUPointer == this.IPcount - 1) {
if (chechDetail()) {
newFrame = true;
//doRecieve();
System.out.println("正在处理报文" + IECFunctions.byteArrayToHexString(RXdata, 0, IPcount));
}
}
APDUPointer++;
}
//
return true;
}
public void setFrame(boolean b) {
newFrame = b;
}
分支太多太乱了,而且手动测试,输入的内容和考虑的情况始终是有限的,这里的话,
优化
针对前面的那些代码,看起来是在是有些乱,重新整理一下思路之后,新的效果如下:
针对代码的输入部分
大体上是不变的,不过主要的是针对输入做了下处理,手动输入是没有了,变成了全自动化随机输入,不过各种包的情况应该是都考虑到了。
private static void AutoTestDemo() {
//随机构造帧的最大数量
int maxIecNumber = 10;
//创建规约对象
IEC102Socket sock = new IEC102Socket(null, null, null);
//接收外部输出
//构造完整参数
String[] rece = { "107AFFFF7816", "680D006853FFFF96010AFFFF00BA020000AC16" };
System.out.println("接收数据中:");
//开始新的规约接收字段
sock.setFrame(true);
Random d = new Random(System.currentTimeMillis());
// 判断是否还有输入
while (true) {
try {
// 定义随机帧数量,最长不超过10个
int numder = d.nextInt(maxIecNumber);
//初始化帧
StringBuffer receBuf = new StringBuffer();
//构造随机帧
for (int i = 0; i < numder; i++) {
receBuf.append(rece[d.nextInt(rece.length)]);
}
String allRece = receBuf.toString();
String oneRece = new String();
System.out.println("\n\n构造的数据为:" + allRece);
while (allRece.length() > 0) {
//随机截取整数字节
int endIndex = d.nextInt(allRece.length()) * 2;
//长度超出直接取余
if (endIndex > allRece.length()) {
endIndex = endIndex % allRece.length();
}
oneRece = allRece.substring(0, endIndex);
allRece = allRece.substring(endIndex, allRece.length());
//截取到0继续截取
if (oneRece.length() == 0) {
continue;
}
System.out.println("输入的数据为:" + oneRece);
//将字符串转换为对应的十六进制字节流比方说说如 16 转换为 0x16 那种
byte[] hexStr2Byte = hexStr2Byte(oneRece);
//测试一下转换是否正常
System.out.println(IECFunctions.byteArrayToHexString(hexStr2Byte, 0, hexStr2Byte.length));
//开始进行正常的接收数据的解析
sock.TestRtuFJ102_MsgRecv(hexStr2Byte);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (Exception e) {
// TODO: handle exception
}
}
}
针对逻辑处理部分,稍微优化了下,现在是比之前号了很多,清晰了很多,但是可能还是有些绕的。
public boolean TestRtuFJ102_MsgRecv(byte[] bytes) throws IOException {
//原始数据从sock读取,这里从字节流去模拟
//this.IPcount = (char) stream.read(this.RXdata);
this.RXdataAll = bytes;
this.IPcountAll = bytes.length;
//已经关闭,没有获取到数据
if (IPcountAll == -1) {
// closed by remote socket
LOG.log.warn("?remote closed?");
interrupt();
}
//遍历所有的数据
for (int it = 0; it < IPcountAll; it++, APDUPointer++) {
//判断是否是新的一帧,新帧这进入下面读取字节长度的部分
this.RXdata[APDUPointer] = RXdataAll[it];
if (this.APDUPointer < 3) {
continue;
} else if (APDUPointer == this.IPcount - 1) {
if (chechDetail()) {
//doRecieve();
System.out.println("收到报长度为: " + this.IPcount + " 正在处理报文" + IECFunctions.byteArrayToHexString(RXdata, 0, IPcount));
APDUPointer = -1;
if (this.IPcount != 6 && this.IPcount != 19) {
System.out.println();
}
}
} else if (this.RXdata[0] == 0x10) {
//设置固定帧的长度和初始位置
this.IPcount = 6;
} else if (this.RXdata[0] == 0x68) {
int lenFrame = IECFunctions.byte2ToInt(this.RXdata[1], this.RXdata[2]) + 6;
this.IPcount = lenFrame;
}
}
//
return true;
}
说明
参考博客
无
版本记录
20200107 解决问题完成文章
20200110 更新方法以及文章