如何解析/编码二进制消息格式



我需要在Java中解析并编码为遗留的二进制消息格式。我开始使用DataOutputStream来读/写基本类型,但我遇到的问题是消息格式不能很好地与字节偏移对齐,并且包含位标志。

例如,我必须处理这样的消息:

+----------+---+---+----------+---------+--------------+
+uint32    +b   +b + uint32   +4bit enum+32 byte string+
+----------+---+---+----------+---------+--------------+

其中(b)是一个位标志。问题是java基本类型不与字节边界对齐,所以我不能使用DataOutputStream对其进行编码,因为我可以编写的最低级别类型是字节。

是否有标准的或第三方的库来处理任意位级消息格式?

编辑:感谢@Software Monkey迫使我更仔细地查看我的规格说明。我使用的规范实际上与字节边界对齐,因此DataOutputStream是合适的。考虑到我最初的问题,我会选择@emboss提出的解决方案。

编辑:虽然发现这个问题的消息格式是在字节边界上的,但我遇到了另一种适用于原始问题的消息格式。该格式定义了一个6位字符映射,其中每个字符实际上只占用6位,而不是整个字节,因此字符串不会在字节边界上对齐。我已经发现了几个解决这个问题的二进制输出流。比如这个:http://introcs.cs.princeton.edu/java/stdlib/BinaryOut.java.html

在Java中有一个内置的byte类型,您可以使用InputStream#read(byte[])读取byte[]缓冲区,并使用OutputStream#write(byte[], int, int)写入OutputStream,所以这没有问题。

关于你的消息-正如你正确指出的,你一次得到的最小的信息是一个字节,所以你必须首先将你的消息格式分解成8位块:

让我们假设您的消息是一个名为data的字节[]。我也假设小端序

一个uint32是32位长->也就是四个字节。(在Java中解析时要小心,Java的整数和长整数是有符号的,你需要处理这个问题。避免麻烦的一个简单方法就是多花点时间。数据[0]填充31 ~ 24位,数据[1]填充23 ~ 16位,数据[2]填充15 ~ 8位,数据[3]填充7 ~ 0位。因此,您需要将它们适当地向左移动,并使用逻辑OR将它们粘合在一起:

long uint32 = ((data[0]&0xFF) << 24) | 
              ((data[1]&0xFF) << 16) | 
              ((data[2]&0xFF) << 8)  | 
               (data[3]&0xFF);

接下来,有两个单比特。我想你必须检查它们是"开"(1)还是"关"(0)。要做到这一点,你使用位掩码并将你的字节与逻辑与比较。

第一位:(二进制掩码| 1 0 0 0 0 0 0 0 | = 128 = 0x80)

if ( (data[4] & 0x80 ) == 0x80 ) // on

第二比特:(二进制掩码| 0 1 0 0 0 0 0 0 | = 64 = 0x40)

if ( (data[4] & 0x40 ) == 0x40 ) // on

要组合下一个uint32,必须在底层数据的字节边界上组合字节。例如,对于第一个字节,取剩余的6位数据[4],向左移动两位(它们将是uint32的8位到2位),并通过向右移动6位(它们将占用uint32剩余的1和0位)来"添加"前两个(最高)数据[5]。"添加"是指逻辑上的或:

byte uint32Byte1 = (byte)( (data[4]&0xFF) << 2 | (data[5]&&0xFF) >> 6);

构建uint32的过程与第一个示例相同。等等。

使用Java二进制块解析器解析消息的脚本将为

  class Parsed {
    @Bin int field1;
    @Bin (type = BinType.BIT) boolean field2;
    @Bin(type = BinType.BIT) boolean field3;
    @Bin int field4;
    @Bin(type = BinType.BIT) int enums;
    @Bin(type = BinType.UBYTE_ARRAY) String str;
  }
  Parsed parsed = JBBPParser.prepare("int field1; bit field2; bit field3; int field4; bit:4 enums; ubyte [32] str;").parse(STREAM).mapTo(Parsed.class);

我听说Preon很不错。

为了补充pholser的答案,我认为Preon的版本应该是这样的:

class DataStructure {
  @BoundNumber(size="32")  long       first; // uint32
  @Bound                   boolean    second; // boolean
  @Bound                   boolean    third; // boolean
  @BoundNumber(size="32")  long       fourth; // uint32
  @BoundNumber(size="4")   int        fifth; // enum
  @BoundString(size="32")  String     sixth; // string
}

…但实际上,通过使用Preon对直接处理枚举的支持,您可以使您的工作更轻松。

为它创建一个Codec并使用它来解码一些数据将是这样的:

Codec<DataStructure> codec = Codecs.create(DataStructure.class)
DataStructure data = Codecs.decode(codec, ....)

在Java中,您需要应用位算术(AND, OR, AND NOT操作符)来更改或读取字节中的单个位。算术运算符为&、|和~

最新更新