概述
Java用Socket解析字节流数据
- 背景
- 问题与解决
- 1. Socket连接与数据读取方式的选择
- 2. 内部数据协议的顶层解析过程
- 3. 字节数据解析成Java数据类型的问题
背景
因业务需求,需要完成一个TCP连接数据的解析与转发的插件。
最终,使用Socket进行TCP简单连接,逐步读取字节数据,解析成想要的数据类型,最后转发。
问题与解决
1. Socket连接与数据读取方式的选择
因为只是做一个Tcp连接的客户端,Socket建立使用最简单的方式:
Socket socket = new Socket(ip, port);
数据流读写方式,设计上要求插件一直等待字节数据,这里使用缓冲流能提高效率:
BufferedInputStream bi = new BufferedInputStream(socket.getInputStream());
2. 内部数据协议的顶层解析过程
public static Packet readPacket(BufferedInputStream in) throws IOException {
// 阻塞等待指定长度的字节流, 这里首先等待一个完整包头的字节数据
while (in.available() < LEN_PKT_HEADER) ;
// 1. 读取packet header
byte[] head = new byte[LEN_PKT_HEADER];
in.read(head);
// 2.解析packet header内容, getUShort是将字节数组转为无符号类型的short数据
int pktBodySize = getUShort(head[0], head[1]);
// 3. 读取packet body
if (pktBodySize <= 0) {
// todo: 空数据包
return null;
}
// 阻塞等待指定长度的字节流
while (in.available() < pktBodySize) ;
// 读取
byte[] pktBody = new byte[pktBodySize];
in.read(pktBody);
return new Packet(0, 0, pktBody);
}
3. 字节数据解析成Java数据类型的问题
这个插件中用到的协议字节格式是小端,而Java中的字节存储是大端。
需要注意的是,Java的基础数据类型是有符号的,而传入的字节流数据会包含无符号数据,重点在于最高位的bit处理,一般思路是用范围更大的有符号数据来存储无符号数据。
通过位运算来逐个字节取short类型中的bit数据,然后按照大小端顺序来拼成数组。
- short转成byte数组,int类型的转换类型
private static byte[] getBytes(short num, @Min(1) int len) {
if (len == 1) {
return new byte[]{(byte) (num & 0xFF)};
} else {
return new byte[]{(byte) (num & 0xFF), (byte) ((num >> Byte.SIZE) & 0xFF)};
}
}
- unsigned short 类型转成byte数组
public static int getUShort(byte... bytes) {
int num = bytes[0] & 0xFF;
if (bytes.length > 1) {
num |= (bytes[1] & 0xFF) << Byte.SIZE;
}
return num;
}
- 字符串类型的数据格式与转换
在这个协议中,字符串的表示字节数组是<LEN><TEXT>
,首先根据字节的前1位(两位,由协议本身约定)的字节,经过转成short类型得到TEXT部分的字节长度len,进而去转成String类型。
String stockCode = new String(bytes, idx, len, StandardCharsets.UTF_8);
- byte数组转成Long (有符号)
int LEN_LONG = Long.SIZE / Byte.SIZE;
public static long getLong(byte[] bytes, int idx) {
long res = bytes[idx + LEN_LONG - 1];
for (int i = idx + LEN_LONG - 2; i >= idx; i--) {
res = (res << Byte.SIZE) | ((long) bytes[i] & 0xFFL);
}
return res;
}
- byte数组转成unsigned long类型
因为Java中基础类型最长的字节就8个,因为需要借助更大的数据类型来保存8字节的无符号数据
public static BigDecimal getULong(byte[] bytes, int idx) {
// 7 * 8 = 2的56次方, 将ULong型数的最高位字节,从第一个字节还原所需的移动的位数
final BigDecimal towOf56 = BigDecimal.valueOf((long) 1 << ((LEN_LONG - 1) * Byte.SIZE));
// 1. 保存最高位
byte high = bytes[idx + LEN_LONG - 1];
// 2. 将最高位置0
bytes[idx + LEN_LONG - 1] = 0;
// 3. 将除最高字节以外的byte数组转为有符号long
long tmp = getLong(bytes, idx);
BigDecimal num = BigDecimal.valueOf(tmp);
// 4.将最高位字节转成对应的大小的数
BigDecimal highNum = BigDecimal.valueOf(Byte.toUnsignedLong(high));
// 乘以2的56次方,等同与将这个数表示的最高字节向左移动7个字节
highNum = highNum.multiply(towOf56);
return num.add(highNum);
}
最后
以上就是负责鸭子为你收集整理的Java用Socket解析字节流数据背景问题与解决的全部内容,希望文章能够帮你解决Java用Socket解析字节流数据背景问题与解决所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复