QuickFIX/J自定义消息解码器
一、什么是QuickFIX/J
下面我们来看一下QuickFIX/J官网的介绍:
The Financial Information eXchange (FIX) protocol is a messaging standard developed specifically for the real-time electronic exchange of securities transactions. FIX is a public-domain specification owned and maintained by FIX Protocol, Ltd (FPL).
QuickFIX/J is a full featured messaging engine for the FIX protocol. It is a 100% Java open source implementation of the popular C++ QuickFIX engine.
简单来说,FIX协议就是金融信息交换协议,是专门为证券交易的实时电子交换而开发的消息传递标准,这是目前金融行业普遍采用的金融信息交换协议。
而QuickFIX/J就是一个100%由Java开发的开源FIX消息传递引擎,通过使用这个引擎就可以很方便地搭建出标准的FIX协议消息传递的服务。
二、在实际使用中遇到的问题
在最近的项目开发中,需要搭建一个FIX协议客户端接收金融公司服务端推送来的行情信息。通过使用QuickFIX/J,很快就搭建出来了服务的雏形,但在进行联调测试的时候就傻眼了,服务运行起来就不停地报错:
"did not find checksum field, bad length?"。后来经过排查,发现是由于客户服务端发送来的FIX消息不标准导致的,具体来说就是FIX消息里的消息体长度字段的数值是错误的,因此无法通过QuickFIX引擎反序列化出完整的消息,导致在消息末尾的校验和字段丢失,就会一直报错。
下面就是在quickfix.mina.message.FIXMessageDecoder中进行校验的地方:
if (position + CHECKSUM_PATTERN.getMinLength() <= in.limit()) {
// FEATURE allow configurable recovery position
// int recoveryPosition = in.position() + 1;
// Following recovery position is compatible with QuickFIX C++
// but drops messages unnecessarily in corruption scenarios.
int recoveryPosition = position + 1;
handleError(in, recoveryPosition,
"did not find checksum field, bad length?", isLogon(in));
continue;
} else {
break;
}
三、解决方案
既然知道了是长度字段出现的问题,那我们只要修改Decoder,让他无视长度字段,解析出完整的消息就可以了。
但是想修改Decoder可不是那么容易,我们知道QuickFIX/J是基于Mina通信框架进行开发的,Mina和Netty的处理逻辑类似,都是采用了链式处理,那么我们就需要在与服务端的连接建立起来的时候就把之前的处理链给替换成我们自己的。
首先,我们要创建一个IoFilter,并把它加入QuickFIX/J的启动器中。
public static void main(String[] args) throws ConfigError {
SessionSettings settings = new SessionSettings(ClientReceiver.class.getResourceAsStream("/quickfix-client.properties"));
FileStoreFactory storeFactory = new FileStoreFactory(settings);
FileLogFactory logFactory = new FileLogFactory(settings);
DefaultMessageFactory messageFactory = new DefaultMessageFactory();
ThreadedSocketInitiator initiator = new ThreadedSocketInitiator(new ClientReceiver(), storeFactory, settings, logFactory, messageFactory);
// 创建自定义过滤器,并添加到 initiator
DefaultIoFilterChainBuilder chainBuilder = new DefaultIoFilterChainBuilder();
chainBuilder.addLast("checksum", new CheckFilter());
initiator.setIoFilterChainBuilder(chainBuilder);
initiator.start();
}
CheckFilter的代码如下,这里只把用到的方法写了出来,其他方法一定要记得调用nextFilter的方法,否则可能会出现问题。
import org.apache.mina.core.filterchain.IoFilter;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
public class CheckFilter implements IoFilter {
... ...
@Override
public void sessionCreated(NextFilter nextFilter, IoSession ioSession) throws Exception {
nextFilter.sessionCreated(ioSession);
// 通过名称替换掉原有ProtocolCodecFilter
ioSession.getFilterChain().replace(FIXProtocolCodecFactory.FILTER_NAME, new ProtocolCodecFilter(new FIXExactProtocolCodecFactory()));
}
... ...
}
我们用于替换的ProtocolCodecFilter需要传入一个ProtocolCodecFactory,这时我们就需要自定义一个ProtocolCodecFactory,将我们的Decoder添加进去。
import org.apache.mina.filter.codec.demux.DemuxingProtocolCodecFactory;
import quickfix.mina.message.FIXMessageEncoder;
public class FIXExactProtocolCodecFactory extends DemuxingProtocolCodecFactory {
public FIXExactProtocolCodecFactory() {
addMessageDecoder(FIXExactMessageDecoder.class); // 自定义的Decoder
addMessageEncoder(FIXMessageEncoder.getMessageTypes(), FIXMessageEncoder.class); // Encoder使用quickfix的就可以
}
}
接下来只要自己写一个Decoder实现就可以了。
import org.apache.mina.filter.codec.demux.MessageDecoder;
public class FIXExactMessageDecoder implements MessageDecoder {
... ...
}
Decoder可以仿照 quickfix.mina.message.FIXMessageDecoder 并根据实际情况进行改写,我这里就不在赘述了。