Quickfix源代码分析

先扯一扯

 上一篇博文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;
};

/// XML node as represented by pugixml.
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;
};
/// XML document as represented by pugixml.
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;
// while FieldToGroup structure seems to be overcomplicated
// in reality it yields a lot of performance because:
// 1) avoids memory copying;
// 2) first lookup is done by comparing integers and not string objects
// TODO: use hash_map with good hashing algorithm
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 )
{
// VERSION
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
/**
* Base class for all %FIX messages.
*
* A message consists of three field maps. One for the header, the body,
* and the trailer.
*/

class Message : public FieldMap
{
friend class DataDictionary;
// friend class Session;

enum field_type { header, body, trailer };

public:
Message();

/// Construct a message from a string
Message( const std::string& string, bool validate = true )
throw( InvalidMessage );

/// Construct a message from a string using a data dictionary
Message( const std::string& string, const FIX::DataDictionary& dataDictionary,
bool validate = true )
throw( InvalidMessage );

/// Construct a message from a string using a session and application data dictionary
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;
}

/// Set global data dictionary for encoding messages into XML
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

/*small speculation about the space needed for FIX string*/ 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
/**
* Stores and organizes a collection of Fields.
*
* This is the basis for a message, header, and trailer. This collection
* class uses a sorter to keep the fields in a particular order.
*/
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 史矛革

buy me a cola!

欢迎关注我的其它发布渠道