在正式开始的时候,我们应该介绍点背景。技术人员最缺的是什么?是什么?什么?么》!表达。
FIX的英文全称为Financial Information eXchange(金融信息交换协议),1992年多个致力于提升其相互间交易流程效率的金融机构和经纪商共同发起制定。这些企业把他们及他们的行业视为一个**整体**,认为能够从对交易指示,交易指令及交易执行的高效电子数据交换的驱动中获利。FIX由此诞生,一个不受单一实体控制的开放消息标 准,一个能够被调整组建适用于任何一个企业的商务需求的协议。
FIX业务消息有一系列预定义类型与格式。目前标准的 消息类型(类比于证券柜台的功能号)有5、60个,函盖了交易、行情、结算等投资管理的各个环节。FIX应用基本上就是对FIX业务消息的编程。
**说白了就是,不要你一套我一套,我们做一个协议1,2.3,4,5多简洁! 就像 404:网页没了 囧**
FIX协议规范由FIX组织维护([FIX组织官网](www.fixprotocol.org)
QuickFix 是Fix开源引擎,目前很多Fix解决方案都是根据或参考QuickFix实现的,尤其在中国市场,基本全部或大部分都是QuickFix的包装产品,所以QuickFix是作为学习Fix 的一个非常好的一个工具, 其官方网址为:http://www.quickfixengine.org , 目前有java,.Net,C++,Python和Ruby五种语言实现,可以说基本满足大部分的客户需求。
【FIX有两种应用模式】initiator和acceptor。
initiator是TCP连接的发起方,acceptor是侦听方。initiator 和acceptor类似但不等同于客户端与服务端的概念。标准的FIX应用(如CTS FIX网关)可以同时支持两种模式,也就是说既可以发起连接,也可以接受连接请求。
我们通常意义上的服务端一般是部署在经纪商(卖方)一侧的,仅接受投资 者(买方)发起连接。但在FIX应用中,这个经纪商服务端应用可以反过来向投资者端发起连接,也就是采用initiator模式。这点要特别注意。
【FIX接收的消息格式】“标记1=值<分隔符>标记2=值 ”
Eg:8=FIX.4.49=22735=834=849=OKSERVER52=20160225-02:46:12.26056=LeiZhiGangCool6=428.4311=de656ed05a004a5b82cb723ff3204ab112=014=115=btc_usd17=156038218337=156038218338=139=240=144=428.4354=155=next_weel150=2151=0898=10911=110=126
【FIX通讯建立流程】1. 通信双方,一方叫Initiator(客户端),另一方叫Acceptor(服务器),各自维护2个递增的序列号(发送消息序列号–每发送一个消息加1,接受消息序列号–每收到一个消息加1)。
2. 通信首先由Initiator开始发起建立一个网络连接 , Acceptor(服务器),接受网络连接建立。
3. Initiator 发起登录消息请求。
4. Acceptor 收到登录请求后,经过一系列消息检查,合格后,返回登录确认。Initiator 收到登录确认后,经过一系列消息检查,合格后,双方Fix 会话层连接成功。
5. 双方交换消息
6. 退出(任意一方均可发退出消息)
本系类文章主要介绍QuickFIX/J 的实现。Eclipse, Maven(也可不用,楼主的DEMO是MAVEN版本源码)
<dependency>
<groupId>org.apache.servicemix.bundlesgroupId>
<artifactId>org.apache.servicemix.bundles.quickfixartifactId>
<version>1.5.3_1version>
dependency>
/**
* 服务启动主类(线程)
* @author FLY.ZHANG
*/
public class FIXServer {
private static ThreadedSocketAcceptor acceptor = null;
public static ThreadedSocketAcceptor getAcceptor() {
return acceptor;
}
private final Map> dynamicSessionMappings = new HashMap>();
public Map> getDynamicSessionMappings() {
return dynamicSessionMappings;
}
/**
* 指定配置文件启动
*
* @param propFile
* @throws ConfigError
* @throws FieldConvertError
*/
public FIXServer(String propFile) throws ConfigError, FieldConvertError {
// 设置配置文件
SessionSettings settings = new SessionSettings(
"quickfix/quickfix-server.properties");
// 设置一个APPlication
Application application = new FixServerApplication();
/**
*
* quickfix.MessageStore 有2种实现。
* quickfix.JdbcStore,quickfix.FileStore .
* JdbcStoreFactory 负责创建JdbcStore ,
* FileStoreFactory 负责创建FileStorequickfix 默认用文件存储,因为文件存储效率高。
*/
MessageStoreFactory storeFactory = new FileStoreFactory(settings);
LogFactory logFactory = new FileLogFactory(settings);
MessageFactory messageFactory = new DefaultMessageFactory();
acceptor = new ThreadedSocketAcceptor(application, storeFactory,
settings, logFactory, messageFactory);
configureDynamicSessions(settings, application, storeFactory,
logFactory, messageFactory);
}
private void startServer() throws RuntimeError, ConfigError {
acceptor.start();
}
public void stop() {
acceptor.stop();
}
/**
* 被调用的start方法
*
* @throws ConfigError
* @throws FieldConvertError
*/
public static void start() throws ConfigError, FieldConvertError {
FIXServer servercom = new FIXServer("quickfix-server.properties");
servercom.startServer();
}
/**
* 测试本地使用的main方法
*
* @param args
* @throws FieldConvertError
* @throws ConfigError
*/
public static void main(String[] args) throws ConfigError,
FieldConvertError {
// 配置LOG日记
PropertyConfigurator.configure("log4j.properties");
FIXServer fixServer = new FIXServer(
"quickfix/quickfix-server.properties");
fixServer.startServer();
}
/**
*
* 以下几个方法是配置动态SessionProvider
* FIX 支持同时支持不同的SessionTemple,使用不同的数据处理Provider
* 体现在配置文件中的[session]标签
*
* @param settings
* @param application
* @param messageStoreFactory
* @param logFactory
* @param messageFactory
* @throws ConfigError
* @throws FieldConvertError
*/
private void configureDynamicSessions(SessionSettings settings,
Application application, MessageStoreFactory messageStoreFactory,
LogFactory logFactory, MessageFactory messageFactory)
throws ConfigError, FieldConvertError {
//获取配置文件中的[session]标签集合
Iterator sectionIterator = settings.sectionIterator();
while (sectionIterator.hasNext()) {
SessionID sessionID = sectionIterator.next();
if (isSessionTemplate(settings, sessionID)) {
//判断是否使用了AcceptorTemplate
InetSocketAddress address = getAcceptorSocketAddress(settings,sessionID);
getMappings(address).add(new TemplateMapping(sessionID, sessionID));
}
}
for (Map.Entry> entry : dynamicSessionMappings
.entrySet()) {
acceptor.setSessionProvider(
entry.getKey(),
new DynamicAcceptorSessionProvider(settings, entry
.getValue(), application, messageStoreFactory,
logFactory, messageFactory));
}
}
private boolean isSessionTemplate(SessionSettings settings,
SessionID sessionID) throws ConfigError, FieldConvertError {
return settings.isSetting(sessionID, SETTING_ACCEPTOR_TEMPLATE)&& settings.getBool(sessionID, SETTING_ACCEPTOR_TEMPLATE);
}
private List getMappings(InetSocketAddress address) {
List mappings = dynamicSessionMappings.get(address);
if (mappings == null) {
mappings = new ArrayList();
dynamicSessionMappings.put(address, mappings);
}
return mappings;
}
private InetSocketAddress getAcceptorSocketAddress(
SessionSettings settings, SessionID sessionID) throws ConfigError,
FieldConvertError {
String acceptorHost = "0.0.0.0";
if (settings.isSetting(sessionID, SETTING_SOCKET_ACCEPT_ADDRESS)) {
acceptorHost = settings.getString(sessionID,
SETTING_SOCKET_ACCEPT_ADDRESS);
}
int acceptorPort = (int) settings.getLong(sessionID,
SETTING_SOCKET_ACCEPT_PORT);
InetSocketAddress address = new InetSocketAddress(acceptorHost,
acceptorPort);
return address;
}
}
/**
*
* MessageCracker是一个工具类,通过继承MessageCracker可以覆盖onMessage方法
* 通过调用crack回调onMessage中的业务逻辑。所以所有的业务逻辑可以直接写在onMessage 方法中。
*
* @author FLY.ZHANG
*/
public class FixServerApplication extends MessageCracker implements Application {
/**
*
* onCreate –>当一个Fix Session建立是调用
*
* onLogon –>当一个Fix Session登录成功时候调用
*
* onLogout –>当一个Fix Session退出时候调用
*
* fromAdmin–>当收到一个消息,经过一系列检查,合格后,属于Admin 类型时候调用
*
* fromApp–>当收到一个消息,经过一系列检查,合格后,不属于Admin 类型时候调用
*
* toAdmin–>当发送一个admin类型消息调用toApp—>当发送一个非admin(业务类型)消息调用
*
*/
public void fromAdmin(quickfix.Message message, final SessionID sessionID) {
System.out.println("接收会话类型消息时调用此方法");
try {
crack(message, sessionID);
} catch (UnsupportedMessageType | FieldNotFound | IncorrectTagValue e) {
e.printStackTrace();
}
}
protected void onMessage(Message message, SessionID sessionID) {
try {
System.out.println("业务逻辑实现统一写在此方法中");
String msgType = message.getHeader().getString(35);
Session session = Session.lookupSession(sessionID);
System.out.println("服务器接收到用户信息订阅==" + msgType);
switch (msgType) {
case MsgType.LOGON: // 登陆
session.logon();
session.sentLogon();
break;
case MsgType.HEARTBEAT: // 心跳
session.generateHeartbeat();
break;
}
} catch (FieldNotFound e) {
e.printStackTrace();
}
}
public void fromApp(quickfix.Message message, SessionID sessionID)
throws FieldNotFound, IncorrectDataFormat, IncorrectTagValue,
UnsupportedMessageType {
System.out.println("接收业务消息时调用此方法");
crack(message, sessionID);
}
public void onCreate(SessionID sessionID) {
System.out.println(" 服务器启动时候调用此方法创建");
}
public void onLogon(SessionID sessionID) {
System.out.println("客户端登陆成功时候调用此方法");
}
public void onLogout(SessionID sessionID) {
System.out.println("客户端断开连接时候调用此方法");
}
public void toAdmin(quickfix.Message message, SessionID sessionID) {
System.out.println("发送会话消息时候调用此方法");
}
public void toApp(quickfix.Message message, SessionID sessionID)
throws DoNotSend {
System.out.println("发送业务消息时候调用此方法");
}
}
#quickfix-server.properties
[default]
SocketAcceptPort=9880
FileStorePath=conf/target/data/store
FileLogPath=conf/target/data/log
ConnectionType=acceptor
SenderCompID=FILZHANG
TargetCompID=*
StartTime=00:00:00
EndTime=00:00:00
HeartBtInt=30
ValidOrderTypes=1,2,F
ValidOrderSymbol=CNY,USD
UseDataDictionary=Y
DataDictionary=quickfix/FIX44.xml
TimeZone=Asia/Chongqing
ResetOnLogon=Y
ResetOnLogout=Y
ResetOnDisconnect=Y
ResetOnError=Y
[session]
AcceptorTemplate=Y
BeginString=FIX.4.4