定长消息报文的组包与解包简单封装 (Java 实现)

在实际项目中经常会碰到不同系统之间的数据交换,有些是用 webservice。有些则是使用发 socket 消息的方式,将需要发送的消息组装成特定格式的字符串或 Xml 格式的文件,再通过 socket 编程发送到对方系统。本文主要讨论组装成定长字符串。

抽象任何一个定长消息包 (MsgPackage) 都是由一个或多个消息片 (MsgPiece) 组成。任何一个消息片都是由一个或多个消息域 (MsgField) 组成。消息域的属性有:域名、长度、值、如果值的长度小于定义的最大长度那值是靠左还是右对齐其余的是用什么字符填充。

工具代码

消息域类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
package socket.msg;

/**
* 消息域
* @author Zhenwei.Zhang (2013-9-25)
*/
public class MsgField {

public MsgField (String name, int length, char fillChar, FillSide fillSide) {
this.name = name;
this.length = length;
this.fillChar = fillChar;
this.fillSide = fillSide;
}

/**
* 填充位置
* @author Zhenwei.Zhang (2013-9-25)
*/
public enum FillSide {
LEFT, RIGHT
}

/** 域名称 */
private String name;
/** 长度 */
private int length;
/** 填充字符 */
private char fillChar;
/** 填充位置 */
private FillSide fillSide;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getLength() {
return length;
}

public void setLength(int length) {
this.length = length;
}

public char getFillChar() {
return fillChar;
}

public void setFillChar(char fillChar) {
this.fillChar = fillChar;
}

public FillSide getFillSide() {
return fillSide;
}

public void setFillSide(FillSide fillSide) {
this.fillSide = fillSide;
}

}

报文消息片类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
package socket.msg;

import java.beans.PropertyDescriptor;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Method;
import java.util.LinkedList;
import java.util.List;

import socket.msg.MsgField.FillSide;

/**
* 消息片,由多个消息域按一定的顺序组成
* @author Zhenwei.Zhang (2013-9-25)
*/
public abstract class MsgPiece {

/** 消息域列表 */
private List<MsgField> itemList = new LinkedList<MsgField>();

public MsgPiece(MsgField[] items) {
itemList = new LinkedList<MsgField>();
for (int i = 0; i < items.length; i++) {
itemList.add(items[i]);
}
}

/**
* 组消息
* @author Zhenwei.Zhang (2013-9-25)
* @param charsetName
* @return
* @throws Exception
*/
public byte[] pack(String charsetName) throws Exception {
StringBuffer result = new StringBuffer();
byte[] b = null;
try {
for (MsgField item : itemList) {
PropertyDescriptor pd = new PropertyDescriptor(item.getName(), this.getClass());
Method readMethod = pd.getReadMethod();
Object gotVal = readMethod.invoke(this);
String strFill = gotVal == null ? "" : gotVal.toString();
result.append(this.autoFill(strFill, item.getFillChar(), item.getFillSide(), item.getLength(), charsetName));
}
b = result.toString().getBytes(charsetName);
} catch (Exception e) {
throw new Exception(" 组消息出错:" + e.getMessage(), e);
}

return b;
}

/**
* 解消息
* @author Zhenwei.Zhang (2013-9-25)
* @param msg
* @param charsetName
* @throws Exception
*/
public void unPack(byte[] msg, String charsetName) throws Exception {
if (msg.length != this.getLength()) {
throw new Exception(" 解消息出错,消息长度不合法!");
}

int index = 0;
try {
for (MsgField item : itemList) {
String value = new String(msg, index, item.getLength(), charsetName);
value = this.getRealVal(value, item.getFillSide(), item.getFillChar());

PropertyDescriptor pd = new PropertyDescriptor(item.getName(), this.getClass());
Method setMethod = pd.getWriteMethod();
if (pd.getPropertyType().equals(byte.class) || pd.getPropertyType().equals(Byte.class)) {
setMethod.invoke(this, Byte.parseByte(value));
}else if (pd.getPropertyType().equals(short.class) || pd.getPropertyType().equals(Short.class)) {
setMethod.invoke(this, Short.parseShort(value));
}else if (pd.getPropertyType().equals(int.class) || pd.getPropertyType().equals(Integer.class)) {
setMethod.invoke(this, Integer.parseInt(value));
}else if (pd.getPropertyType().equals(long.class) || pd.getPropertyType().equals(Long.class)) {
setMethod.invoke(this, Long.parseLong(value));
}else if (pd.getPropertyType().equals(float.class) || pd.getPropertyType().equals(Float.class)) {
setMethod.invoke(this, Float.parseFloat(value));
}else if (pd.getPropertyType().equals(double.class) || pd.getPropertyType().equals(Double.class)) {
setMethod.invoke(this, Double.parseDouble(value));
}else if (pd.getPropertyType().equals(char.class) || pd.getPropertyType().equals(Character.class)) {
setMethod.invoke(this, value.toCharArray()[0]);
}else if (pd.getPropertyType().equals(boolean.class) || pd.getPropertyType().equals(Boolean.class)) {
setMethod.invoke(this, Boolean.valueOf(value));
}else {
setMethod.invoke(this, value);
}
index += item.getLength();
}
} catch (Exception e) {
throw new Exception(" 解消息出错:" + e.getMessage(), e);
}
}

/**
* 获得消息片长
* @author Zhenwei.Zhang (2013-9-25)
* @return
*/
public int getLength() {
int length = 0;
for (MsgField item : itemList) {
length += item.getLength();
}
return length;
}

/**
* 格式化真实值为指定长度字符串,超长自动截取
* @author Zhenwei.Zhang (2013-9-26)
* @param value
* @param fillChar 填充字符
* @param side 填充位置
* @param totalLen 域定义的长度
* @param charsetName 编码
* @return
*/
private String autoFill(String value, char fillChar, FillSide side, int totalLen, String charsetName) {
try {
if (value == null) {
value = "";
}
StringBuffer sbuffBuffer = new StringBuffer();
// 长度超过指定长度,截取
if (value.getBytes(charsetName).length > totalLen) {
byte[] data = new byte[totalLen];
System.arraycopy(value.getBytes(charsetName), 0, data, 0, totalLen);
sbuffBuffer.append(new String(data, charsetName));
return sbuffBuffer.toString();
}
if (side == socket.msg.MsgField.FillSide.RIGHT) {
sbuffBuffer.append(value);
}
for (int i = value.getBytes(charsetName).length; i < totalLen; i++) {
sbuffBuffer.append(fillChar);
}
if (side == socket.msg.MsgField.FillSide.LEFT) {
sbuffBuffer.append(value);
}
return sbuffBuffer.toString();
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("系统不支持" + charsetName + "编码");
}
}

/**
* 获得消息域的真实值
* @author Zhenwei.Zhang (2013-9-26)
* @param value
* @param side 填充位置
* @param fillChar 填充字符
* @return
* @throws Exception
*/
private String getRealVal(String value, FillSide side, char fillChar) throws Exception {
char[] chars = value.toCharArray();
if (FillSide.LEFT == side) {
int index = 0;
for (int i = 0; i < chars.length; i++) {
if (fillChar == chars[i]) {
index ++;
} else {
continue;
}
}
return value.substring(index);
} else if (FillSide.RIGHT == side) {
int index = chars.length - 1;
for (int i = index; i >= 0; i--) {
if (fillChar == chars[i]) {
index --;
} else {
continue;
}
}
return value.substring(0, index + 1);
} else {
throw new Exception("无效的填充位置");
}
}

}

消息包类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
package socket.msg;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;

/**
* 消息包,由多个消息片组成
* @author Zhenwei.Zhang (2013-10-12)
*/
public abstract class MsgPackage {

/** 消息包的消息片数组 */
String[] pieces = null;

public MsgPackage(String... pieName) {
pieces = pieName;
}

/**
* 组包
* @author Zhenwei.Zhang (2013-9-26)
* @param charsetName
* @return
* @throws Exception
*/
public byte[] pack(String charsetName) throws Exception {
int index = 0;
byte[] result = new byte[this.getLength()];
for (String p : pieces) {
PropertyDescriptor pd = new PropertyDescriptor(p, this.getClass());
Method getterMethod = pd.getReadMethod();
MsgPiece piece = (MsgPiece)getterMethod.invoke(this);
if (piece == null) { // 属性为空则该属性组空串
piece = (MsgPiece) pd.getPropertyType().newInstance();
}
byte[] t = piece.pack(charsetName);
System.arraycopy(t, 0, result, index, t.length);
index += t.length;
}
return result;
}

/**
* 解包
* @author Zhenwei.Zhang (2013-9-26)
* @param msg
* @param charsetName
* @throws Exception
*/
public void unPack(byte[] msg, String charsetName) throws Exception {
if (msg.length != this.getLength()) {
throw new Exception("解消息出错,消息包长度不合法!");
}

int index = 0;
for (String p : pieces) { // 创建一个新的属性对象,解包,赋值
PropertyDescriptor pd = new PropertyDescriptor(p, this.getClass());
Method writeMethod = pd.getWriteMethod();
MsgPiece piece = (MsgPiece) pd.getPropertyType().newInstance();
byte[] t = new byte[piece.getLength()];
System.arraycopy(msg, index, t, 0, t.length);
piece.unPack(t, charsetName);
writeMethod.invoke(this, piece);
index += t.length;
}
}

/**
* 获得消息包长度
* @author Zhenwei.Zhang (2013-9-26)
* @return
* @throws Exception
*/
public int getLength() throws Exception {
int length = 0;
try {
for (String p : pieces) {
PropertyDescriptor pd = new PropertyDescriptor(p, this.getClass());
Method getterMethod = pd.getReadMethod();
MsgPiece piece = (MsgPiece)getterMethod.invoke(this);
if (piece == null) {
piece = (MsgPiece) pd.getPropertyType().newInstance();
}
length += piece.getLength();
}
} catch (Exception e) {
throw new Exception("获得消息包长度错误:" + e.getMessage(), e);
}
return length;
}
}

实例

以下是使用上述封装做的简单实现例子:
定义一个消息由消息头和消息体组成,消息头由 name,sex,UAge,amt 等属性组成,消息体由 content 属性组成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
package socket.msg.test;

import socket.msg.MsgField;
import socket.msg.MsgPiece;
import socket.msg.MsgField.FillSide;

public class TestHead extends MsgPiece {

private String name;
private boolean sex;
private int UAge;
private String URL;
private double amt;

public int getUAge() {
return UAge;
}

public void setUAge(int uAge) {
UAge = uAge;
}

public double getAmt() {
return amt;
}

public void setAmt(double amt) {
this.amt = amt;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public boolean isSex() {
return sex;
}

public void setSex(boolean sex) {
this.sex = sex;
}

public String getURL() {
return URL;
}

public void setURL(String uRL) {
URL = uRL;
}

private static final MsgField[] items = new MsgField[]{
new MsgField("name", 10, '', FillSide.RIGHT),
new MsgField("sex", 10, '0', FillSide.LEFT),
new MsgField("UAge", 10, '0', FillSide.LEFT),
new MsgField("URL", 10, ' ', FillSide.RIGHT),
new MsgField("amt", 10, '0', FillSide.LEFT)
};

public TestHead() {
super(items);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package socket.msg.test;

import socket.msg.MsgField;
import socket.msg.MsgPiece;
import socket.msg.MsgField.FillSide;

public class TestBody extends MsgPiece {

private static final MsgField[] items = new MsgField[]{
new MsgField("content", 20, '',FillSide.RIGHT),
};

public TestBody() {
super(items);
}

private String content;

public String getContent() {
return content;
}

public void setContent(String content) {
this.content = content;
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package socket.msg.test;

import socket.msg.MsgPackage;

public class TestPackage extends MsgPackage {

public TestPackage() {
super("t1", "t2");
}

private TestHead t1;

private TestBody t2;

public TestHead getT1() {
return t1;
}

public void setT1(TestHead t1) {
this.t1 = t1;
}

public TestBody getT2() {
return t2;
}

public void setT2(TestBody t2) {
this.t2 = t2;
}

}

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package socket.msg.test;

import java.io.UnsupportedEncodingException;

public class Test {

/**
* @author Zhenwei.Zhang (2013-11-12)
* @param args
* @throws Exception
* @throws UnsupportedEncodingException
*/
public static void main(String[] args) throws UnsupportedEncodingException, Exception {
TestHead head = new TestHead();
head.setName("name");
head.setAmt(12.121);
head.setSex(false);
head.setURL("http://asdsaaaaaaaaaaaaaaaaaaaaaaa");
head.setUAge(111);

TestBody body = new TestBody();
body.setContent("content");

TestPackage packagee = new TestPackage();
packagee.setT1(head);
packagee.setT2(body);

System.out.println(new String(packagee.pack("GBK"), "GBK"));

TestPackage packagee2 = new TestPackage();
String str = "name 000000true0000000111http://asd000012.121content";
packagee2.unPack(str.getBytes("GBK"), "GBK");
System.out.println(packagee2.getT1().isSex());
}

}