先扯一扯 上一篇博文FIX协议介绍 中对FIX协议背景,语法格式等做了介绍。文中也提到我是由于工作中某个模块需要实现fix报文和xml报文之间的转换对FIX协议进行的接触和了解。文末也给出了一个实现fix协议的开源或闭源的一些代码库的列表。而我采用的是Quickfix引擎。目前国内开发用的多的还是Quickfix,它是一个C++实现的fix引擎。 这篇博文我打算对Quickfix源代码进行一些分析!
Quickfix下载(安装) 两个途径: Quickfix官网:http://www.quickfixengine.org/ Github上:https://github.com/quickfix/quickfix 下载好Quickfix的源代码后(或许不需要编译安装?)。我自己是直接把quickfix的源代码刨去部分后直接拉进我自己的项目中。如果选择编译安装或许也可以,直接在makefile里链fix的库。
代码目录介绍
下载好quickfix的源代码并解压后,得到诸如下图的目录结构:include/*:一些头文件 *doc/ :一些关于quickfix说明和使用的简要html文件example/ :实现了简要的交易客户端tradeclien程序spec/ :数据字典src/ :源代码,其中c++实现的代码在子目录”c++/“下面
下面着重介绍下C++这个子目录下的一些源代码文件:
DataDictionary.cpp:解析诸如FIX42.xml的数据字典 Field .cpp:数据字典中解析预定义的field Message.cpp:数据字典中解析处理message节点 Http .cpp: 实现http引擎的部分(我没用到) Socket.cpp:会话层的通信(当然我没用到) Sessian .cpp: 会话层的东西(没用到) 还有一些其他的文件,略去不说。这里还要注意还有几个子文件夹:fix40/,fix41/,fix42/,fix43/,fix44/,fix50/,fix50sp1。这几个文件夹下是具体实现了该版本的一些头文件。
数据字典载入、处理 Quickfix中进行数据字典的载入,解析本质是对几个xml文件的解析,所以需要一个xml引擎,早期的quickfix好像是采用libxml作为xml引擎的(不太确定),现在是采用pugixml parser,官方网站:http://pugixml.org/ 。正如官网介绍的那样:
Light-weight, simple and fast XML parser for C++ with XPath support
然后Quickfix中在之上进行了一层自己的封装,形成PUGIXML_DOMAttributes类,PUGIXML_DOMNode类,PUGIXML_DOMDocument类。在头文件”PUGIXML_DOMDocument.h”中进行了定义,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 class PUGIXML_DOMAttributes : public DOMAttributes { public : PUGIXML_DOMAttributes( pugi::xml_node pNode ) : m_pNode(pNode) {} bool get ( const std ::string &, std ::string & ) ; DOMAttributes::map toMap () ; private : pugi::xml_node m_pNode; }; class PUGIXML_DOMNode : public DOMNode { public : PUGIXML_DOMNode( pugi::xml_node pNode ) : m_pNode(pNode) {} ~PUGIXML_DOMNode() {} DOMNodePtr getFirstChildNode () ; DOMNodePtr getNextSiblingNode () ; DOMAttributesPtr getAttributes () ; std ::string getName () ; std ::string getText () ; private : pugi::xml_node m_pNode; }; class PUGIXML_DOMDocument : public DOMDocument { public : PUGIXML_DOMDocument() throw ( ConfigError ); ~PUGIXML_DOMDocument(); bool load ( std ::istream& ) ; bool load ( const std ::string & ) ; bool xml ( std ::ostream& ) ; DOMNodePtr getNode ( const std ::string & ) ; private : pugi::xml_document m_pDoc; }; }
其中大多数函数不需要特别关心,我们只需要重点关心PUGIXML_DOMDocument类中的load()函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 bool PUGIXML_DOMDocument::load ( std ::istream& stream ) { try { return m_pDoc.load(stream); } catch ( ... ) { return false ; } } bool PUGIXML_DOMDocument::load ( const std ::string & url ) { try { return m_pDoc.load_file(url.c_str()); } catch ( ... ) { return false ; } }
这个函数就是对给定一个xml路径然后装载后返回一个pugi::xml_document的对象。
数据字典解析 上面的类实现了诸如FIX44.xml的载入处理,通过上一篇博文FIX协议介绍 中的介绍,数据字典中定义了很多结构节点,比如fields,messages,groups等,DataDictionary*.cpp是真正对这些xml文件进行解析的源文件。DataDictionary.h中部分源代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 class DataDictionary { typedef std ::set < int > MsgFields; typedef std ::map < std ::string , MsgFields > MsgTypeToField; typedef std ::set < std ::string > MsgTypes; typedef std ::set < int > Fields; typedef std ::map < int , bool > NonBodyFields; typedef std ::vector < int > OrderedFields; typedef message_order OrderedFieldsArray; typedef std ::map < int , TYPE::Type > FieldTypes; typedef std ::set < std ::string > Values; typedef std ::map < int , Values > FieldToValue; typedef std ::map < int , std ::string > FieldToName; typedef std ::map < std ::string , int > NameToField; typedef std ::map < std ::pair < int , std ::string > , std ::string > ValueToName; typedef std ::map < std ::string , std ::pair < int , DataDictionary* > > FieldPresenceMap; typedef std ::map < int , FieldPresenceMap > FieldToGroup; public : DataDictionary(); DataDictionary( const DataDictionary& copy ); DataDictionary( std ::istream& stream ) throw ( ConfigError ); DataDictionary( const std ::string & url ) throw ( ConfigError ); virtual ~DataDictionary(); void readFromURL ( const std ::string & url ) throw ( ConfigError ) ; void readFromDocument ( DOMDocumentPtr pDoc ) throw ( ConfigError ) ; void readFromStream ( std ::istream& stream ) throw ( ConfigError ) ; ...... }; ....
可以看到DataDictionary类中定义了很多的std::map和std::vector,这些容器都是用来存储从FIX4X.xml文件中解析来的内容,一些映射。比如:
1 typedef std ::map < int , std ::string > FieldToName;
表示存储field和实际的字段名的映射,比如8对应BeginString ;
1 typedef std ::map < int , Values > FieldToValue;
表示枚举当中的int值跟实际的字段名的映射,比如下面的:
1 2 3 4 5 6 7 8 <field number='13' name='CommType' type='CHAR' > <value enum ='1' description='PER_UNIT' /> <value enum ='2' description='PERCENTAGE' /> <value enum ='3' description='ABSOLUTE' /> <value enum ='4' description='4' /> <value enum ='5' description='5' /> <value enum ='6' description='POINTS_PER_BOND_OR_CONTRACT_SUPPLY_CONTRACTMULTIPLIER' /> </field>
3代表ABSOLUTE ;1代表PER_UNIT 。
另外需要注意的成员函数readFrom*()系列,底层就是上一节中的类,进行xml的载入。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 void DataDictionary ::readFromURL ( const std ::string & url ) throw ( ConfigError ) { DOMDocumentPtr pDoc = DOMDocumentPtr (new PUGIXML_DOMDocument ()); if (!pDoc ->load (url )) ¦ throw ConfigError (url + ": Could not parse data dictionary file" ); try { ¦ readFromDocument ( pDoc ); } catch ( ConfigError & e ) { ¦ throw ConfigError ( url + ": " + e.what () ); } } void DataDictionary ::readFromStream ( std ::istream & stream ) throw ( ConfigError ) { >* DOMDocumentPtr pDoc = DOMDocumentPtr (new PUGIXML_DOMDocument ()); if (!pDoc ->load (stream )) ¦ throw ConfigError ("Could not parse data dictionary stream" ); readFromDocument ( pDoc ); } >*void DataDictionary ::readFromDocument ( DOMDocumentPtr pDoc ) throw ( ConfigError ) { DOMNodePtr pFixNode = pDoc ->getNode ("/fix" ); if (!pFixNode.get ()) ... }
到这里,数据字典的解析就完成了。简单的理解就是,读入xml文件,然后针对xml文件里的内容,把内容做成映射用map和vector存储。
FIX报文处理 上面的类只是对数据字典的解析和处理,还没有涉及到真正的fix报文的解析,现在开始!
Message类 针对fix报文的处理类是Message。在Message.h文件中部分代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 class Message : public FieldMap{ friend class DataDictionary ; enum field_type { header, body, trailer }; public : Message(); Message( const std ::string & string , bool validate = true ) throw ( InvalidMessage ); Message( const std ::string & string , const FIX::DataDictionary& dataDictionary, bool validate = true ) throw ( InvalidMessage ); Message( const std ::string & string , const FIX::DataDictionary& sessionDataDictionary, const FIX::DataDictionary& applicationDataDictionary, bool validate = true ) throw ( InvalidMessage ); Message( const Message& copy ) : FieldMap( copy ) { m_header = copy.m_header; m_trailer = copy.m_trailer; m_validStructure = copy.m_validStructure; m_tag = copy.m_tag; } static bool InitializeXML ( const std ::string & string ) ; ... ... void setString ( const std ::string & string ) throw ( InvalidMessage ) { setString(string , true ); } void setString ( const std ::string & string , bool validate ) throw ( InvalidMessage ) { setString(string , validate, 0 ); } void setString ( const std ::string & string , bool validate, const FIX::DataDictionary* pDataDictionary ) throw ( InvalidMessage ) { setString(string , validate, pDataDictionary, pDataDictionary); } void setString ( const std ::string & string , bool validate, const FIX::DataDictionary* pSessionDataDictionary, const FIX::DataDictionary* pApplicationDataDictionary ) throw ( InvalidMessage ) ; void setGroup ( const std ::string & msg, const FieldBase& field, const std ::string & string , std ::string ::size_type& pos, FieldMap& map , const DataDictionary& dataDictionary ) ;... }
正如Message类的注释那样,Message类是各种FIX messages的基类,并且包含3个field maps,分别为header,body,trailer。Message类继承自类FieldMap,关于FiledMap待会再谈。先来看看Message类的构造函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 Message::Message() : m_validStructure( true ) {} Message::Message( const std ::string & string , bool validate ) throw ( InvalidMessage ) : m_validStructure( true ) { setString( string , validate ); } Message::Message( const std ::string & string , const DataDictionary& dataDictionary, bool validate ) throw ( InvalidMessage ) : m_validStructure( true ) { setString( string , validate, &dataDictionary, &dataDictionary ); } Message::Message( const std ::string & string , const DataDictionary& sessionDataDictionary, const DataDictionary& applicationDataDictionary, bool validate ) throw ( InvalidMessage ) : m_validStructure( true ) { setStringHeader( string ); if ( isAdmin() ) setString( string , validate, &sessionDataDictionary, &sessionDataDictionary ); else setString( string , validate, &sessionDataDictionary, &applicationDataDictionary ); }
根据不同的初始化参数调用不同的构造函数,不过最终调用到成员函数setString()。那么setString()函数究竟做了什么呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 void Message::setString( const std::string& string, bool doValidation, const DataDictionary* pSessionDataDictionary, const DataDictionary* pApplicationDataDictionary ) throw( InvalidMessage ) { clear(); std::string::size_type pos = 0; int count = 0; std::string msg; static int const headerOrder[] = { FIELD::BeginString, FIELD::BodyLength, FIELD::MsgType }; field_type type = header; while ( pos < string.size() ) { FieldBase field = extractField( string, pos, pSessionDataDictionary, pApplicationDataDictionary ); if ( count < 3 && headerOrder[ count++ ] != field.getTag() ) if ( doValidation ) throw InvalidMessage("Header fields out of order" ); if ( isHeaderField( field, pSessionDataDictionary ) ) { if ( type != header ) { if (m_tag == 0) m_tag = field.getTag(); m_validStructure = false ; } if ( field.getTag() == FIELD::MsgType ) msg = field.getString(); m_header.setField( field, false ); if ( pSessionDataDictionary ) setGroup( "_header_" , field, string, pos, getHeader(), *pSessionDataDictionary ); } else if ( isTrailerField( field, pSessionDataDictionary ) ) { type = trailer; m_trailer.setField( field, false ); if ( pSessionDataDictionary ) setGroup( "_trailer_" , field, string, pos, getTrailer(), *pSessionDataDictionary ); } else { if ( type == trailer ) { if (m_tag == 0) m_tag = field.getTag(); m_validStructure = false ; } type = body; setField( field, false ); if ( pApplicationDataDictionary ) setGroup( msg, field, string, pos, *this, *pApplicationDataDictionary ); } } if ( doValidation ) validate(); }
函数应该比较容易读懂,第一个参数string保存了fix的真实报文,比如:
1 8 =FIX.4 .2 \0019 =272 \00135 =E\00134 =126 \00166666 =1095350459 \00150 =00303 \00149 =BUYSIDE\00152 =20040916 -16 :19 :18.328 \00168 =2 \00156 =SELLSIDE\00173 =2 \00111 =1095350459 \00155 =fred\00140 =1 \00167 =1 \0011 =00303 \00178 =3 \00179 =string \00179 =string \00179 =string \00154 =1 \001 59 =3 \00111 =1095350460 \00167 =2 \00140 =1 \00159 =3 \0011 =00303 \00178 =3 \00179 =string \00179 =string \00179 =string \00155 =feed\0 0154 =5 \001394 =3 \00110 =120 \001
然后从这个字符串开始一直往后解析,参照一个DataDictionary类的指针pSessianDataDictionary,进行解析,比如从fix报文中读到了8这个tag,那么需要从这个指针中找到对应的字段名值为BeginString 。最终还原成实际的报文。 那么读到的这些tag和value存到哪里呢?这就是刚才卖的关子,可以直接跳到FieldMap类那小节来了解FieldMap类的实现。 setString()函数负责解包,那么组包呢? 组包实现的成员函数是toString()函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 std ::string Message::toString ( int beginStringField, int bodyLengthField, int checkSumField ) const { std ::string str; return toString( str, beginStringField, bodyLengthField, checkSumField ); } std ::string & Message::toString ( std ::string & str, int beginStringField, int bodyLengthField, int checkSumField ) const { int length = bodyLength( beginStringField, bodyLengthField, checkSumField ); m_header.setField( IntField(bodyLengthField, length) ); m_trailer.setField( CheckSumField(checkSumField, checkSum(checkSumField)) ); #if defined(_MSC_VER) && _MSC_VER < 1300 str = "" ; #else >> str.clear (); #endif str.reserve( length + 64 ); m_header.calculateString( str ); FieldMap::calculateString( str ); m_trailer.calculateString( str ); return str; }
函数也比较容易读懂,一个fix报文包含三个部分,header,body,trailer。
FieldMap类 FieldMap类是Message类的基类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 class FieldMap { public : #if defined(_MSC_VER) && _MSC_VER < 1300 typedef std ::multimap < int , FieldBase, message_order > Fields; typedef std ::map < int , std ::vector < FieldMap* >, std ::less<int > > Groups; #else typedef std ::multimap < int , FieldBase, message_order, ALLOCATOR<std ::pair<const int ,FieldBase> > > Fields; typedef std ::map < int , std ::vector < FieldMap* >, std ::less<int >, ALLOCATOR<std ::pair<const int , std ::vector < FieldMap* > > > > Groups; #endif typedef Fields::const_iterator iterator; typedef iterator const_iterator; typedef Groups::const_iterator g_iterator; typedef Groups::const_iterator g_const_iterator; FieldMap( const message_order& order = message_order( message_order::normal ) ) : m_fields( order ) {} FieldMap( const int order[] ) : m_fields( message_order(order) ) {} FieldMap( const FieldMap& copy ) { *this = copy; } ... }
这个类好像没什么好解释的。
好像介绍完了 其实回过头来看这些代码,也比较容易,不过当时还是花了点时间琢磨的。如果想要实现自己的Message类可以从Message类进行派生,然后实现自己的特定组包解包函数。 这还是算一个比较大的开源库了,阅读这样的开源代码确实收获不少,可以学习到别的优秀的C++者的书写习惯,可以学习到别人运用这些库的熟练程度等,收获不少。 由于是回过头来记录当时的项目,可能会遗漏部分或者忽略了部分,因为现在看是比较简单的。多看,多学,多写!
(小插曲:原来markdown换行是两个空格,以前都是直接一个空行。另外行首缩进是 )分别表示一个空格和两个空格。这篇博文就是采用了新的方式,不知道效果如何,先试试看)
Blog:
2017-02-10 于杭州By 史矛革