3. FAST 消息解码过程
解码FAST消息时,我们需要确定模板ID,检索模板,并将消息内容与模板定义和操作符结合起来,重新生成原始消息。
FAST解码器使用以下算法解码每条消息:
FAST解码器使用以下算法解码每个字段:
综上所述,我们遵循以下步骤解码FAST消息:
- 3.1提取Presence Map
- 3.2确定消息模板ID
- 3.3解码每个消息字段
3.1提取Presence Map
PMap通常出现在每条FAST消息的开头,有时还会出现在一条FAST消息中间。它有若干表示消息中的每个字段(不是“必需的”)是否存在于消息体中的bit位组成。如果我们的FAST消息包含一个重复序列(FIX中的Group,一组可重复的字段的集合),若其中任何字段需要PMap位,那么PMap将在序列的每一组之前出现。
完整的FAST消息的一般结构如图
PMap是采用停止位编码的实体。它是一个位序列,每个位按位的顺序表示一个模板的字段(按模板定义的顺序)。PresenceMap指出模板中的哪些字段有数据,哪些字段有隐含的数据。有数据的字段将PMap位设置为“1”。带有隐含数据的字段将PMap位设置为“0”(不存在于流中)。所以如果PMap位设置为“1”,我们应该解码值为适当的位,比如:
if (presenceMap.hasNextPresenceBit()) {
offset = dataTypeDecoder.decode(encodedData, offset, templateField);
...
}
从逻辑上讲,一个PresenceMap有一个无限的0后缀。一个结尾每位都为零的序列的PresenceMap可以被截断。这个概念如下所示。
解码器的实现应该能够确定PMap是否具有特定的值。例如:
public boolean hasNextPresenceBit() {
if (isEmptyPMap) {
return false;
}
if (offset > lastPMapBytePosition) {
//Implies that presence map is empty and has the infinitive suffix of zeros
isEmptyPMap = true;
return false;
}
boolean bitOnPosition = hasBitOnPosition(currentByte, currentBitPosition);
currentBitPosition++;
if (currentBitPosition == NUMBER_BITS_IN_BYTE) {
currentBitPosition = 1; //注意此处
offset++;
currentByte = data[offset];
}
return bitOnPosition;
}
我们的Presence Map类示例不执行停止位解码来存储PMap数据。编码的字节以实际编码的格式存储。PMap不使用停止位值,所以它传递这些值。
检查是否为特定位置设置了位,可以很容易地定义如下:
public static boolean isPositionSet(byte byteToCheck, int bitPosition) {
if (bitPosition > 0) {
byteToCheck = (byte) (byteToCheck << bitPosition);
}
if (byteToCheck < 0) {
return true;
}
return false;
}
在上面的示例中,我们将字节做左位移运算将所需位移动到第一个位,如果移位的字节中的第一个位为1,则返回true
。
例如,我们有一个FAST消息,它只包含templateID字段: 0xC0 0xA9
这里它的比特表示: 11000000 10101001。
使用停止位定义,我们通过以下步骤定义编码FAST消息PMap的字节量:
- 逐个获取字节。
第一个是0xC0,有1100000位表示。 - 检查当前字节的停止位值。如果它等于" 1 ",我们的PMap由这个字节完成。
在我们的例子中,0xC0的第一个位等于“1”,所以我们的PMap是1100000。
如果当前字节的第一个比特不等于“1”,我们将PMap的长度增加1,并使用下一个字节从第1步开始重复我们的过程。
3.2 确定消息模板ID
任何消息中的Presence Map的第一个位(在停止位之后)显示templateID的存在,这是为我们的消息定义模板所必需的。模板ID使用隐式复制操作符。因此,如果第一个位为1,则流中存在Template ID,如果位为0,则Template ID与前一个消息的Template ID相同。在任何情况下,我们必须始终确定templateID,因为它是解码FAST消息所必需的。
在我们的例子中,模板ID出现在流中,如下所示:
模板ID字段跟在PMap后面,因此提取模板ID是FAST解码过程的第二步。解码器将使用提取的模板ID检索正确的FAST消息模板,然后使用该模板解码FAST消息的其余部分。
相同的FAST消息模板在FAST编码器和解码器(通常是消息生产者和消费者)之间共享。这种关系如下图所示。
3.2.1 模板
通常,FAST消息模板存储在单个XML文件中,并定义如何编码和解码消息内容。这些模板对于编码器和解码器都必须是相同的。模板XML文件采用的语法应该是“标准XML”。
DocumentBuilder docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
File xmlFile = new File(templateFileName);
Document doc = null;
try {
doc = docBuilder.parse(xmlFile);
} catch (Exception ex) {
//handle exception ...
}
模板xml文件包含一组描述编码消息格式的FAST消息模板。
3.2.2 消息模板
为了演示消息模板处理,我们将使用FIX市场数据增量更新消息(FIX消息类型X)的模板。
在下面的示例java片段中,我们将从xml中将FAST消息模板导入到java HashMap中,以便在应用程序的其余部分中方便地访问。
Map<integer, messagetemplate=""> messageTemplates = new HashMap<integer, messagetemplate="">();
public void readTemplates() {
int templateId;
for (int i =0; i < msgTemplatesCount; i++) {
templateId = parseTemplateId();
messageTemplates.put(templateId, new MessageTemplate(parseTemplateFields()));
}
}
</integer,></integer,>
第二个解码步骤是识别模板ID。下面的代码剪辑接收一条FAST消息,提取该消息PMap,解码模板ID,获取消息模板,并使用模板解码消息内容:
int templateId;
Message outputMessage;
int offset;
public Object decodeMessage(byte[] fastMessage) {
offset = 0;
// Get a presence map of the whole message
presenceMap = new PresenceMap();
offset = presenceMap.init(data, offset);
// 注意此部分代码
if (presenceMap.hasNextPrecenceBit()) {
templateId = dataTypeDecoder.processUnInt32Decoding(fastMessage, offset);
}
messageTemplate = templateParser.getTemplate(templateId);
outputMessage = new Message();
messageTemplate.decode(fastMessage, offset, presenceMap, outputMessage);
...
}
在FAST中,模板ID被编码为无符号整数,因此我们使用此数据类型的规则解码它并获得匹配的模板。然后,我们使用该模板和其他编码数据,并按照模板中的定义生成原始消息。
让我们解码3.1节中FAST消息0xC0 0xA9的templateID。
- 正如我们在上面定义的那样,我们的PMap看起来像: 1100 0000。第一个位是停止位,第二个位告诉我们消息中有下一个FAST条目。
根据消息模板定义,它是templateID。所以我们取第二个字节并将其解码为templateID。 - 第二个字节是0xA9,它的位表示为1010 1001。
由于该字段类型是“Unsigned Integer
”,并且根据此数据类型规则,我们可以通过删除第一个位获得其值:0101001。 - 我们将这些位转换为十进制格式并接收41。我们的templateID是41。