Java教程

Java用Socket解析字节流数据

本文主要是介绍Java用Socket解析字节流数据,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

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数据,然后按照大小端顺序来拼成数组。

  1. 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)};
        }
    }
  1. 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;
    }
  1. 字符串类型的数据格式与转换
    在这个协议中,字符串的表示字节数组是 <LEN><TEXT>,首先根据字节的前1位(两位,由协议本身约定)的字节,经过转成short类型得到TEXT部分的字节长度len,进而去转成String类型。
String stockCode = new String(bytes, idx, len, StandardCharsets.UTF_8);
  1. 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;
    }
  1. 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解析字节流数据的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!