return(tmpBuf);
}
消息确认
·FIX协议不支持单个消息的确认。
采用的是监控消息时隙的方法来进行消息恢复和验证。
·普通的数据传送(无单个消息确认)通过消息序列间隙进行错误识别。
每个消息由一个唯一的序列号进行标示。
接收端应用程序负责监控接收消息序列号以识别消息间隙并产生重传请求。
·每个FIX参与方必须为FIX会话维护两个序列号,一个是接收序列号,一个是发送序列号,两者都在建立FIX会话开始时初始化为1。
每个消息被赋予一个唯一的序列号值,并在消息发送后递增。
此外,每个收到的消息都有一个唯一的序列号,接收序列号计数器在收到每个消息后将会被递增。
·当接收序列号与所希望得到的的正确序列号不必配时,必须采取纠错处理。
加密
·加密算法由连接双方共同协商。
·一个消息的任何一个域可以被加密并放在SecureData域中。
然而,一些显示的标志域必须采用明文进行传输。
为确保完整性,明文域可以在SecureData域中重复。
·当使用加密时,建议但不是必须,所有的消息体都进行加密。
如果一个消息中的重复组数据中的部分数据要加密,这个重复组必须全部进行加密。
·预先协商好的加密算法在Logon消息中进行声明。
自定义域
·FIX为给用户提供最大的灵活性,FIX协议允许用户自定义域。
这些域在认同的参与者之间实现、应用,并且应注意避免冲突。
·Tag数在5000到9999保留用于用户自定义域。
这些tag值用于企业联盟的信息交换。
可以通过FIX网站进行注册。
·10000以上保留用于单一企业内部使用。
不用注册。
三、消息格式
3.1数据类型
整数int,浮点数float,单个字符char,布尔Boolean,字符串String,数据data
3.2域
常见域
域语法
·开始部分应是消息头,随后是正文,最后是消息尾;
·消息头的前3个域的次序不能改变:
起始串(Tag=8)、消息体长度(Tag=9)、消息类型(Tag=35);
·消息尾的最后一个域应是校验和域(Tag=10);
·重复组中,域出现的顺序应遵循该重复组在消息或组件中定义时的次序;
·在一条消息中,除重复组域外任何其他域不能重复出现。
安全与加密
·由于消息有可能在公网或不安全的网络上传输交换,因此需要对相关的敏感数据加密处理。
·具体加密的方法由连接双方达成的协议而定。
·消息内除某些需要公开识别的域以明文传输外其他任何域都可以加密放置密文数据域(SecureData)内。
当然,这些被加密的域也可以同时保留明文的表示方式。
·当决定使用加密方案时,可以对消息正文内所有的域加密。
如果消息的重复组内有部分需要加密的,那么要求对整个重复组加密。
·本协议还提供的一些域用以支持数字签名、密钥交换和正文加密等安全技术。
3.3消息
消息头
每一个会话或应用消息有一个消息头,该消息头指明消息类型、消息体长度、发送目的地、消息序号、发送起始点和发送时间。
消息尾
每一个消息(会话或应用消息)有一个消息尾,并以此终止。
消息尾可用于分隔多个消息,包含有3位数的校验和值。
新订单消息(MsgType=D)
对于在消息头中设置了PossResend标志的订单消息,应当使用交易客户方订单编号(ClOrdID)核实是否已收到该订单,具体实现时还应检查订单参数(买卖方向、证券代码、数量等)进行核实。
如果之前收到该订单,应以执行报告消息回应订单状态。
如果之前未收到,则以执行报告消息回应订单确认。
执行报告消息(MsgType=8)
·订单确认
·订单状态变化确认(如撤单确认)
·发送订单的成交回报
·订单拒绝
订单状态请求消息(MsgType=H)
订单状态请求用于向交易服务方请求某订单的状态,交易服务方通过执行报告消息返回订单状态。
撤单消息(MsgType=F)
撤单消息用以撤消订单的全部订单剩余数量。
撤单消息也被赋予一个ClOrdID,可视作另外一个订单。
如果被拒绝,撤单拒绝消息的ClOrdID放置撤单消息的ClOrdID,而原始订单的ClOrdID则放入OrigClOrdID域。
ClOrdID要保证唯一。
撤单拒绝消息(MsgType=9)
本消息用于撤单消息的拒绝。
交易服务方接收到撤单发现无法执行(已成交订单不可更改等),将发送撤单拒绝。
拒绝撤单时,撤单拒绝消息应用ClOrdID指示撤单的ClOrdID,用OrigClOrdID指示之前最后接受的订单(除非拒绝原因是“未知订单”)。
四、FIX配置
4.1 会话配置(SESSION)
4.2验证配置
4.3Initiator
4.4Acceptor
4.5Storage
4.6FileStorage
4.7Logging
五、FIX开发
5.1FIX引擎
官网:
FIX引擎(http:
//www.quickfixengine.org/)
github:
QFJGitHubRepository(
5.2DEMO
Acceptor配置文件
#定义会话的默认配置(default节点)
[DEFAULT]
FileStorePath=store
FileLogPath=log
ConnectionType=acceptor
ReconnectInterval=60
SenderCompID=SERVER
ResetOnDisconnect=Y
ResetOnLogout=Y
ResetOnLogon=Y
[SESSION]
BeginString=FIX.4.2
TargetCompID=CLIENT
StartTime=00:
00:
00
EndTime=23:
59:
59
HeartBtInt=30
SocketAcceptHost=127.0.0.1
SocketAcceptPort=6666
DataDictionary=FIX42.xml
Initiator配置文件
[DEFAULT]
ConnectionType=initiator
ReconnectInterval=60
FileLogPath=log
FileStorePath=store
StartTime=00:
00:
00
EndTime=23:
59:
59
HeartBtInt=30
ResetOnDisconnect=Y
ResetOnLogout=Y
ResetOnLogon=Y
[SESSION]
BeginString=FIX.4.2
SenderCompID=CLIENT
TargetCompID=SERVER
SocketConnectPort=6666
SocketConnectHost=127.0.0.1
DataDictionary=FIX42.xml
FixServer
packagecom.app.fix;
importquickfix.*;
/**
*服务启动主类(线程)
*/
publicclassFixServer{
privatestaticThreadedSocketAcceptoracceptor=null;
/**
*指定配置文件启动
*
*@parampropFile
*@throwsConfigError
*@throwsFieldConvertError
*/
publicFixServer(StringpropFile)throwsConfigError,FieldConvertError{
//设置配置文件
SessionSettingssettings=newSessionSettings(propFile);
//设置一个APPlication
Applicationapplication=newFixServerApplication();
/**
*
*quickfix.MessageStore有2种实现。
quickfix.JdbcStore,quickfix.FileStore.
*JdbcStoreFactory负责创建JdbcStore,FileStoreFactory负责创建FileStorequickfix
*默认用文件存储,因为文件存储效率高。
*/
MessageStoreFactorystoreFactory=newFileStoreFactory(settings);
LogFactorylogFactory=newFileLogFactory(settings);
MessageFactorymessageFactory=newDefaultMessageFactory();
acceptor=newThreadedSocketAcceptor(application,storeFactory,settings,logFactory,messageFactory);
}
privatevoidstartServer()throwsRuntimeError,ConfigError{
acceptor.start();
}
/**
*测试本地使用的main方法
*
*@paramargs
*@throwsFieldConvertError
*@throwsConfigError
*/
publicstaticvoidmain(String[]args)throwsConfigError,FieldConvertError{
FixServerfixServer=newFixServer("res/acceptor.config");
fixServer.startServer();
}
}
FixServerApplication
packagecom.app.fix;
importquickfix.Application;
importquickfix.DoNotSend;
importquickfix.FieldNotFound;
importquickfix.IncorrectDataFormat;
importquickfix.IncorrectTagValue;
importquickfix.Message;
importquickfix.MessageCracker;
importquickfix.RejectLogon;
importquickfix.Session;
importquickfix.SessionID;
importquickfix.UnsupportedMessageType;
importquickfix.field.MsgType;
/**
*
*/
publicclassFixServerApplicationextendsMessageCrackerimplementsApplication{
@Override
protectedvoidonMessage(Messagemessage,SessionIDsessionID){
try{
StringmsgType=message.getHeader().getString(35);
Sessionsession=Session.lookupSession(sessionID);
switch(msgType){
caseMsgType.LOGON:
//登陆
session.logon();
session.sentLogon();
break;
caseMsgType.HEARTBEAT:
//心跳
session.generateHeartbeat();
break;
}
}catch(FieldNotFounde){
e.printStackTrace();
}
}
@Override
publicvoidonCreate(SessionIDsessionId){
System.out.println("服务器启动时候调用此方法创建");
}
@Override
publicvoidonLogon(SessionIDsessionId){
System.out.println("客户端登陆成功时候调用此方法");
}
@Override
publicvoidonLogout(SessionIDsessionId){
System.out.println("客户端断开连接时候调用此方法");
}
@Override
publicvoidtoAdmin(Messagemessage,SessionIDsessionId){
System.out.println("发送会话消息时候调用此方法");
}
@Override
publicvoidtoApp(Messagemessage,SessionIDsessionId)throwsDoNotSend{
System.out.println("发送业务消息时候调用此方法");
}
@Override
publicvoidfromAdmin(Messagemessage,SessionIDsessionId)
throwsFieldNotFound,IncorrectDataFormat,IncorrectTagValue,RejectLogon{
System.out.println("接收会话类型消息时调用此方法");
try{
crack(message,sessionId);
}catch(UnsupportedMessageType|FieldNotFound|IncorrectTagValuee){
e.printStackTrace();
}
}
@Override
publicvoidfromApp(Messagemessage,SessionIDsessionId)
throwsFieldNotFound,IncorrectDataFormat,IncorrectTagValue,UnsupportedMessageType{
System.out.println("接收业务消息时调用此方法");
crack(message,sessionId);
}
}
FixClient
packagecom.app.fix;
importquickfix.*;
importquickfix.field.*;
importquickfix.fix42.NewOrderSingle;
importjava.io.FileNotFoundException;
importjava.util.Date;
publicclassFixClientimplementsApplication{
privatestaticvolatileSessionIDsessionID;
@Override
publicvoidonCreate(SessionIDsessionID){
System.out.println("OnCreate");
}
@Override
publicvoidonLogon(SessionIDsessionID){
System.out.println("OnLogon");
FixClient.sessionID=sessionID;
}
@Override
publicvoidonLogout(SessionIDsessionID){
System.out.println("OnLogout");
FixClient.sessionID=null;
}
@Override
publicvoidtoAdmin(Messagemessage,SessionIDsessionID){
System.out.println("ToAdmin");
}
@Override
publicvoidfromAdmin(Messagemessage,SessionIDsessionID)throwsFieldNotFound,IncorrectDataFormat,IncorrectTagValue,RejectLogon{
System.out.println("FromAdmin");
}
@Override
publicvoidtoApp(Messagemessage,SessionIDsessionID)throwsDoNotSend{
System.out.println("ToApp:
"+message);
}
@Override
publicvoidfromApp(Messagemessage,SessionIDsessionID)throwsFieldNotFound,IncorrectDataFormat,IncorrectTagValue,UnsupportedMessageType{
System.out.println("FromApp");
}
publicstaticvoidmain(String[]args)throwsConfigError,FileNotFoundException,InterruptedException,SessionNotFound{
SessionSettingssettings=newSessionSettings("res/initiator.config");
Applicationapplication=newFixClient();
MessageStoreFactorymessageStoreFactory=newFileStoreFactory(settings);
LogFactorylogFactory=newScreenLogFactory(true,true,true);
MessageFactorymessageFactory=newDefaultMessageFactory();
Initiatorinitiator=ne