FIX三天日记-quick fix源码

一、概述

1.1 如何阅读?

对于一般人,没必要像对待常用公共组件一样,搞清楚每一个点,我们从使用的角度出发,把我们用到的功能读到即可。

1.2 如何下载 ?

https://github.com/quickfix/quickfix

1.3 大概都有哪些?

源码就在src\C++下,我们先大致浏览一下。

DataDictionary.cpp:解析诸如FIX42.xml的数据字典
Field
.cpp:数据字典中解析预定义的field
Message.cpp:数据字典中解析处理message节点
Http
.cpp: 实现http引擎的部分
Socket.cpp:会话层的通信
Session
.cpp: 会话层的东西
还有一些其他的文件,略去不说。这里还要注意还有几个子文件夹:fix40/,fix41/,fix42/,fix43/,fix44/,fix50/,fix50sp1。这几个文件夹下是具体实现了该版本的一些头文件。

1.4 我会用到哪些?

上篇文章有使用的例子,我们去掉多余部分,拿过来是这样的:

int main( int argc, char** argv )
{FIX::Initiator * initiator = 0;try{FIX::SessionSettings settings( file );Application application;FIX::FileStoreFactory storeFactory( settings );FIX::ScreenLogFactory logFactory( settings );initiator = new FIX::SocketInitiator( application, storeFactory, settings, logFactory );initiator->start();application.run();initiator->stop();delete initiator;
'''}catch ( std::exception & e ){
'''}
}

请记住每一行代码,接下来,本文基本是每章讲解本代码中的一行。

二、SessionSettings

就是这一行:FIX::SessionSettings settings( file );

2.1 数据字典

Quickfix中进行数据字典的载入,解析本质是对几个xml文件的解析,是采用pugixml parser,官方网站:pugixml.org - Home。正如官网介绍的那样:

Light-weight, simple and fast XML parser for C++ with XPath support

然后Quickfix中在之上进行了一层自己的封装,形成PUGIXML_DOMAttributes类,PUGIXML_DOMNode类,PUGIXML_DOMDocument类。在头文件”PUGIXML_DOMDocument.h”中进行了定义,如下:

 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()函数,这也是最重要+最复杂的函数。

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的对象。

2.2 数据字典解析

 上面的类实现了诸如FIX44.xml的载入处理,数据字典中定义了很多结构节点,比如fields,messages,groups等,DataDictionary.cpp是真正对这些xml文件进行解析的源文件。DataDictionary.h中部分源代码如下:

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 algorithmtypedef 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文件中解析来的内容,一些映射,但是是否过于繁琐,我没有深究。

比如:

typedef std::map < int, std::string > FieldToName;

 

表示存储field和实际的字段名的映射,比如8对应BeginString;

typedef std::map < int, Values > FieldToValue;

表示枚举当中的int值跟实际的字段名的映射,比如:

<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的载入。

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 ){// VERSIONDOMNodePtr pFixNode = pDoc->getNode("/fix");if(!pFixNode.get())
...
}

到这里,数据字典的解析就完成了。简单的理解就是,读入xml文件,然后针对xml文件里的内容,把内容做成映射用map和vector存储。

2.3 数据字典存储

SessionSettings

/// Container for setting dictionaries mapped to sessions.
class SessionSettings
{
public:SessionSettings() { m_resolveEnvVars = false; }SessionSettings( std::istream& stream, bool resolveEnvVars = false ) EXCEPT ( ConfigError );SessionSettings( const std::string& file, bool resolveEnvVars = false ) EXCEPT ( ConfigError );
''''''typedef std::map < SessionID, Dictionary > Dictionaries;std::set < SessionID > getSessions() const;private:Dictionaries m_settings;Dictionary m_defaults;
'''friend std::istream& operator>>( std::istream&, SessionSettings& ) EXCEPT ( ConfigError );friend std::ostream& operator<<( std::ostream&, const SessionSettings& );
};

是通过友元函数 operator >> 从任意的流中读取配置,通过一个sessonid的set和一个sessionid->dictionary的map,管理每个段。

三、Application

3.1 Application

若是须要使用QuickFIX开发FIX应用,则须要实现FIX::Application接口,并重载不一样FIX协议版本的MessageCracker::OnMessage接口,如FIX42::MessageCracker。

class Application
{
public:virtual ~Application() {};/// Notification of a session begin createdvirtual void onCreate( const SessionID& ) = 0;/// Notification of a session successfully logging onvirtual void onLogon( const SessionID& ) = 0;/// Notification of a session logging off or disconnectingvirtual void onLogout( const SessionID& ) = 0;/// Notification of admin message being sent to targetvirtual void toAdmin( Message&, const SessionID& ) = 0;/// Notification of app message being sent to targetvirtual void toApp( Message&, const SessionID& )EXCEPT ( DoNotSend ) = 0;/// Notification of admin message being received from targetvirtual void fromAdmin( const Message&, const SessionID& )EXCEPT ( FieldNotFound, IncorrectDataFormat, IncorrectTagValue, RejectLogon ) = 0;/// Notification of app message being received from targetvirtual void fromApp( const Message&, const SessionID& )EXCEPT ( FieldNotFound, IncorrectDataFormat, IncorrectTagValue, UnsupportedMessageType ) = 0;
};


 onCreate:当Fix Session创建时调用。
onLogon:当Fix Session登陆成功时调用。
onLogout:当Fix Session退出时调用。
fromAdmin:当收到一个Admin类型消息时调用。
fromApp:当收到一个不属于Admin 类型消息时调用。
toAdmin:当发送一个admin类型消息调用。
toApp:当发送一个非admin(业务类型)消息调用。

admin一般是服务提供方,app是客户端


3.2 MessageCracker

除了实现FIX::Application接口,还需要重新实现FIX::MessageCracker从具体的FIX协议版本实现继承而来的onMessage方法,crack接口就可以根据message类型匹配到你实现的具体onMessage接口上。

  void crack( const Message& message,const SessionID& sessionID ){const FIX::BeginString& beginString = FIELD_GET_REF( message.getHeader(), BeginString );crack( message, sessionID, beginString );}void crack( const Message& message,const SessionID& sessionID,const BeginString& beginString ){if ( beginString == BeginString_FIX40 )((FIX40::MessageCracker&)(*this)).crack((const FIX40::Message&) message, sessionID);else if ( beginString == BeginString_FIX41 )((FIX41::MessageCracker&)(*this)).crack((const FIX41::Message&) message, sessionID);else if ( beginString == BeginString_FIX42 )((FIX42::MessageCracker&)(*this)).crack((const FIX42::Message&) message, sessionID);else if ( beginString == BeginString_FIX43 )((FIX43::MessageCracker&)(*this)).crack((const FIX43::Message&) message, sessionID);else if ( beginString == BeginString_FIX44 )((FIX44::MessageCracker&)(*this)).crack((const FIX44::Message&) message, sessionID);else if ( beginString == BeginString_FIXT11 ){if( message.isAdmin() ){((FIXT11::MessageCracker&)(*this)).crack((const FIXT11::Message&) message, sessionID);}else{
'''}}}

四、*Factory 

就是这两行:

FIX::FileStoreFactory storeFactory( settings );

FIX::ScreenLogFactory logFactory( settings );

逻辑比较简单,就是读了上文介绍的settings,然后存下来,存储结构如下:

  std::string m_path;

  SessionSettings m_settings;

五、initiator/Acceptor

也就是这一行 initiator = new FIX::SocketInitiator( application, storeFactory, settings, logFactory );

这俩大概差不多,先看一个。

主要代码如下:

/*** Base for classes which act as an acceptor for incoming connections.** Most users will not need to implement one of these.  The default* SocketAcceptor implementation will be used in most cases.*/
class Acceptor
{
public:
''''''Acceptor( Application&, MessageStoreFactory&,const SessionSettings&, LogFactory& ) EXCEPT ( ConfigError );virtual ~Acceptor();''''''/// Poll the acceptorbool poll( double timeout = 0.0 ) EXCEPT ( ConfigError, RuntimeError );/// Stop acceptor.void stop( bool force = false );/// Check to see if any sessions are currently logged onbool isLoggedOn();Session* getSession( const std::string& msg, Responder& );const std::set<SessionID>& getSessions() const { return m_sessionIDs; }Session* getSession( const SessionID& sessionID ) const;const Dictionary* const getSessionSettings( const SessionID& sessionID ) const;bool has( const SessionID& id ){ return m_sessions.find( id ) != m_sessions.end(); }bool isStopped() { return m_stop; }Application& getApplication() { return m_application; }MessageStoreFactory& getMessageStoreFactory(){ return m_messageStoreFactory; }private:
''''''static THREAD_PROC startThread( void* p );typedef std::set < SessionID > SessionIDs;typedef std::map < SessionID, Session* > Sessions;thread_id m_threadid;Sessions m_sessions;SessionIDs m_sessionIDs;Application& m_application;MessageStoreFactory& m_messageStoreFactory;
protected:SessionSettings m_settings;
private:LogFactory* m_pLogFactory;Log* m_pLog;NullLog m_nullLog;bool m_firstPoll;bool m_stop;
};

基本包含了之前介绍的大部分类,如

Session相关的(SessionSettings/set<SessionID>/map<SessionID, Session*>)、

Application(用于接收并处理消息的)、LogFactory(写日志的对象)

5.1 init

功能就是把配置的每一个session初始化,很简单。

void Acceptor::initialize() EXCEPT ( ConfigError )
{std::set < SessionID > sessions = m_settings.getSessions();std::set < SessionID > ::iterator i;if ( !sessions.size() )throw ConfigError( "No sessions defined" );SessionFactory factory( m_application, m_messageStoreFactory,m_pLogFactory );for ( i = sessions.begin(); i != sessions.end(); ++i ){if ( m_settings.get( *i ).getString( CONNECTION_TYPE ) == "acceptor" ){m_sessionIDs.insert( *i );m_sessions[ *i ] = factory.create( *i, m_settings.get( *i ) );}}if ( !m_sessions.size() )throw ConfigError( "No sessions defined for acceptor" );
}

5.2 start

这一行:Acceptor/initiator->start();

  1. 调用 SocketAcceptor::onInitialize() 创建 socket 句柄,进行监听端口。
  2. 启动线程,调用 SocketAcceptor::onStart(),检测对端的连接
void Acceptor::start() EXCEPT ( ConfigError, RuntimeError )
{m_stop = false;onConfigure( m_settings );onInitialize( m_settings );HttpServer::startGlobal( m_settings );if( !thread_spawn( &startThread, this, m_threadid ) )throw RuntimeError("Unable to spawn thread");
}

其他的操作大同小异,可以自己阅读

5.3 SocketAcceptor::onInitialize

主要功能就是对每个session设置监听

void SocketAcceptor::onInitialize(const SessionSettings& s)EXCEPT ( RuntimeError )
{short port = 0;try{m_pServer = new SocketServer(1);std::set<SessionID> sessions = s.getSessions();std::set<SessionID>::iterator i = sessions.begin();for( ; i != sessions.end(); ++i ){const Dictionary& settings = s.get( *i );port = (short)settings.getInt( SOCKET_ACCEPT_PORT );
''''''// 管理监听端口与 SeesionID 的对应关系m_portToSessions[port].insert(*i);// 为每个监听的端口创建 Socket 句柄: socket_handlem_pServer->add( port, reuseAddress, noDelay, sendBufSize, rcvBufSize );}}catch( SocketException& e ){
''''''}
}

5.4 

5.2中的第二步调用

THREAD_PROC Acceptor::startThread( void* p )
{Acceptor * pAcceptor = static_cast < Acceptor* > ( p );pAcceptor->onStart();return 0;
}

六、session

回顾所有我们浏览的代码,唯独没有介绍session,最后来看一下。

6.1 session创建

用factory(初始化心跳、session)

Session* SessionFactory::create( const SessionID& sessionID,const Dictionary& settings ) EXCEPT ( ConfigError )
{std::string connectionType = settings.getString( CONNECTION_TYPE );if ( connectionType != "acceptor" && connectionType != "initiator" )throw ConfigError( "Invalid ConnectionType" );if( connectionType == "acceptor" && settings.has(SESSION_QUALIFIER) )throw ConfigError( "SessionQualifier cannot be used with acceptor." );// 初始化心跳HeartBtInt heartBtInt( 0 );if ( connectionType == "initiator" ){heartBtInt = HeartBtInt( settings.getInt( HEARTBTINT ) );if ( heartBtInt <= 0 ) throw ConfigError( "Heartbeat must be greater than zero" );}// 创建 Session 对象SmartPtr<Session> pSession;pSession.reset( new Session( m_application, m_messageStoreFactory,sessionID, dataDictionaryProvider, sessionTimeRange,heartBtInt, m_pLogFactory ) );return pSession.release();
}

其中session对象内属性太多,挑一些重要的看:

Application(会话)、

SessionID(标识唯一session)、

m_sessionTime/m_logonTime(主要用于之前讲的24小时重新连接,对应配置)、

m_senderDefaultApplVerID/m_targetDefaultApplVerID(发送端/接收端默 Fix 协议版本号)、

m_state(session状态)、

send()(发送消息函数)、

next()(处理收到的消息,比较重要)

6.2 next()

精简过的代码如下

void Session::next( const Message& message, const UtcTimeStamp& timeStamp, bool queued )
{const Header& header = message.getHeader();try{//检查时间if ( !checkSessionTime(timeStamp) ){ reset(); return; }//获取类型,下面根据类型分处理方法const MsgType& msgType = FIELD_GET_REF( header, MsgType );//校验时间const BeginString& beginString = FIELD_GET_REF( header, BeginString );// make sure these fields are presentFIELD_THROW_IF_NOT_FOUND( header, SenderCompID );FIELD_THROW_IF_NOT_FOUND( header, TargetCompID );if ( beginString != m_sessionID.getBeginString() )throw UnsupportedVersion();const DataDictionary& sessionDataDictionary = m_dataDictionaryProvider.getSessionDataDictionary(m_sessionID.getBeginString());if( m_sessionID.isFIXT() && message.isApp() ){ApplVerID applVerID = m_targetDefaultApplVerID;header.getFieldIfSet(applVerID);const DataDictionary& applicationDataDictionary = m_dataDictionaryProvider.getApplicationDataDictionary(applVerID);DataDictionary::validate( message, &sessionDataDictionary, &applicationDataDictionary );}else{sessionDataDictionary.validate( message );}if ( msgType == MsgType_Logon )nextLogon( message, timeStamp );else if ( msgType == MsgType_Heartbeat )nextHeartbeat( message, timeStamp );else if ( msgType == MsgType_TestRequest )nextTestRequest( message, timeStamp );else if ( msgType == MsgType_SequenceReset )nextSequenceReset( message, timeStamp );else if ( msgType == MsgType_Logout )nextLogout( message, timeStamp );else if ( msgType == MsgType_ResendRequest )nextResendRequest( message, timeStamp );else if ( msgType == MsgType_Reject )nextReject( message, timeStamp );else{if ( !verify( message ) ) return ;//内含Session::doTargetTooLow() 来处理序列号过小的消息//    Session::doTargetTooHigh() 来处理序列号过大的消息m_state.incrNextTargetMsgSeqNum();}}''''''if( !queued )nextQueued( timeStamp );if( isLoggedOn() )next();
}

经过各种检查后,根据type调用不同的处理方法,然后操作queue进行下次操作。

这里调用的函数太多了,挑一个复杂的看一下。

6.3 nextResendRequest()

当收到 type是ResendRequest 消息时,回调用nextResendRequest() 处理:

void Session::nextResendRequest(const Message& resendRequest, const UtcTimeStamp& timeStamp)
{// ...// 从缓存拿出需要重传的消息片段(从MessageStore中的消息,如果是FileStore,那么就会从文件中取出)std::vector < std::string > messages;m_state.get( beginSeqNo, endSeqNo, messages );// ...for ( i = messages.begin(); i != messages.end(); ++i ){// 重新计算消息的校验和// ...if ( Message::isAdminMsgType( msgType ) ){// 跳过管理消息if ( !begin ) begin = msgSeqNum;}else{// 在 resend 里会回调 Application::toAppif ( resend( msg ) ){// 有需要跳过的管理消息,则用一条 SeqReset-GapFill 消息替代if ( begin ) generateSequenceReset( begin, msgSeqNum );// 发送应用消息send( msg.toString(messageString) );m_state.onEvent( "Resending Message: "+ IntConvertor::convert( msgSeqNum ) );begin = 0;appMessageJustSent = true;}else{ if ( !begin ) begin = msgSeqNum; }}current = msgSeqNum + 1;}// 结尾还有需要跳过的管理消息,需要用一条 SeqReset-GapFill 消息替代if ( begin ){generateSequenceReset( begin, msgSeqNum + 1 );}// 序列号同步。为什么在重传借宿后还需要再发送一个 SeqReset-GapFill 消息?if ( endSeqNo > msgSeqNum ){endSeqNo = EndSeqNo(endSeqNo + 1);int next = m_state.getNextSenderMsgSeqNum();if( endSeqNo > next )endSeqNo = EndSeqNo(next);if ( appMessageJustSent )beginSeqNo = msgSeqNum + 1;generateSequenceReset( beginSeqNo, endSeqNo );}resendRequest.getHeader().getField( msgSeqNum );if( !isTargetTooHigh(msgSeqNum) && !isTargetTooLow(msgSeqNum) )m_state.incrNextTargetMsgSeqNum();
}

作者修行尚浅,这里只是浅读一下源码,由于使用经验不足,肯定对一些知识的认识不足,以后多加改正。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/443923.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何写出高性能SQL语句

优化SQL查询&#xff1a;如何写出高性能SQL语句 1、首先要搞明白什么叫执行计划&#xff1f;执行计划是数据库根据SQL语句和相关表的统计信息作出的一个查询方案&#xff0c;这个方案是由查询优化器自动分析产生欀如一条SQL语句如果用来从一个10万条记录的表中查1条记录&#…

兔老大的系统设计(一)健康度系统

本系列用大白话&#xff0c;手把手带你实现上百个BAT公司内部真实的常用中型系统。评论抽奖送书 与培训班/营销号/忽悠人的低水平作者&#xff0c;不同的是&#xff1a; 保证听懂&#xff08;小白也可以&#xff0c;这是我的一贯风格&#xff0c;字典式小白式的输出&#xff0…

小白入门Haskell 语言

Haskell 语言 安装 因为我是 Windows 系统&#xff0c;在这里下载一个 GHC for Win 后解压缩&#xff0c;将解压缩后的目录中的 bin 添加到环境变量的 Path 中&#xff0c;在命令行中输入 ghci 就可用交互式的了。 其中 bin 目录下有一个 runhaskell.exe 文件&#xff0c;我…

兔老大的系统设计(二)定时系统(延时队列)

之前文章&#xff1a; 兔老大的系统设计&#xff08;一&#xff09;健康度系统 一、背景 延迟队列的应用场景非常广泛&#xff0c;如客户主动操作&#xff1a; 股票定投顾客预约场景会员定时续费/缴费CSDN定时发布或系统内部操作&#xff1a; 订单成功后&#xff0c;在30分…

作为软件工程师,你必须知道的20个常识

作为一名优秀是软件开发工程师&#xff0c;以下的这些常识你知道吗&#xff1f;在实际工作中有没有总结过呢&#xff1f;小编就带大家一起分享这20个软件开发常识。 1、针对面向对象的设计与分析&#xff1a;为了让软件有更好的可维护性&#xff0c;重用性以及快速开发&#xf…

新鲜美团测试岗面经(带答案)

1、测试环境搭建过程 2、 验证环境部署是否成功时 跑测试用例 接口是什么样的&#xff1f;&#xff08;服务对外提供的调用接口&#xff09; 3、 数据库 表 有个字段 name字段 nameliuguoge 唯一标识id3 修改nameguogeliu update user set nameguogeliu where id3; 4、写…

如何实现两个数据库之间的同步

两台服务器分别架在两个不同的机房&#xff0c;要实现所有表中数据的同步&#xff0c;延时一两分钟没关系&#xff0c;数据库数据量很大&#xff0c;表大概有不到一百个吧&#xff0c;怎么实现同步&#xff1f;不同服务器数据库之间的数据操作--创建链接服务器 execsp_addlink…

博弈论经典入门

文章目录博弈论常见模型必胜点和必败点的概念&#xff1a;必胜点和必败点的性质&#xff1a;巴什博弈斐波那契博弈威佐夫博弈尼姆博弈SG函数与SG定理博弈论 博弈论 &#xff0c;是经济学的一个分支&#xff0c;主要研究具有竞争或对抗性质的对象&#xff0c;在一定规则下产生的…

百度校园招聘历年经典面试题汇总:Java开发岗

&#xff08;1&#xff09;、Java中的多态 &#xff08;2&#xff09;、Object类下的方法 &#xff08;3&#xff09;、Finalize的作用和使用场景 &#xff08;4&#xff09;、Hashcode和equals &#xff08;5&#xff09;、为什么要同时重写hashcode和equals&#xff0c;不同时…

如何实现Oracle数据库之间的数据同步?

我们都知道&#xff0c;在Oracle数据库的管理与开发工作中&#xff0c;总会存在着一些表数据和基础资料数据&#xff0c;这时需要有效的将这些数据库进行同步合并&#xff0c;有没有什么简单的方法可以实现Oracle数据库之间的数据同步呢&#xff1f;在此诚恺科技重庆服务器频道…

c++面试题总结1

内存结构 堆&#xff1a;由程序员手动分配和释放&#xff0c;完全不同于数据结构中的堆&#xff0c;分配方式类似链表。由malloc&#xff08;c语言&#xff09;或new&#xff08;c&#xff09;来分配&#xff0c;free&#xff08;c语言&#xff09;和delete&#xff08;c&…

JBPM4.4整合SSH2项目

一&#xff1a;导入相应的jar包: *注意事项&#xff1a; (1).与项目中的jar包不能出现冲突 (2).版本应一致 jbpm-bpmn.jar jbpm-console-form-plugin.jar jbpm-console-graphView-plugin.jar jbpm-console-integration.jar jbpm-console-reports.jar jbpm-db.jar jbpm-example…

Linux简单命令收录(who,echo,date)【上】

shell严格区分输入命令的大小写&#xff0c;如who、Who和WHO是不同的&#xff0c;其中只有全小写——who是正确的Linux命令。 命令与选项和参数之间要用空格或制表符隔开。连续空格会被shell解释称单个空格。 选项&#xff1a;对命令的特殊定义&#xff0c;以“-”开始&#…

移动端测试面试题目大全

ADB工作原理 当用户启动一个adb客户端&#xff0c;客户端首先确认是否已有一个adb服务进程在运行。如果没有&#xff0c;则启动服务进程。当服务器运行&#xff0c; adb服务器就会绑定本地的TCP端口5037并监听adb客户端发来的命令&#xff0c;所有的adb客户端都是用端口 5037与…

Linux简单命令收录(cal,passwd,clear)【下】

1、cal NAME cal - display a calendar 显示日历 SYNOPSIS cal [options] [[[day] month] year] cal [options] [timestamp|monthname] 用法&#xff1a; cal [选项] [[[日] 月] 年] cal [选项] <时间戳|月份名> OPTIONS -1…

web知识点大总结

#第一章 Web基础知识 Web开发基本概念 1、万维网是一个由许多相互链接的超文本组成的系统&#xff0c;通过互联网访问。 2、web&#xff1a;worldwideweb&#xff0c;万维网&#xff0c;简称web&#xff0c;www&#xff0c;通常称为网页。 3、web开发&#xff1a;进行网页页…

Linux命令集—— cat AND more

1、cat NAME cat - concatenate files and print on the standard output 连接所有指定文件并将结果写到标准输出。【经常用来显示文件的内容&#xff0c;类似DOS的TYPE 命令】 SYNOPSIS cat [OPTION]... [FILE]... cat [选项]... [文件]... With no FILE, or when FILE…

Linux简单命令集——less

NAME less - opposite of more more的对立面 注意 与more命令类似&#xff0c;less命令也用来分屏显示文件的内容&#xff0c;但是less命令允许用户向前或向后浏览文件。例如&#xff0c;less命令显示文件内容时&#xff0c;可以用⬆键和⬇键分别将屏幕内容下移一行和上移一…

《重构-改善既有代

重要列表 1、如果你发现自己需要为程序添加一个特性&#xff0c;而代码结构使你无法很方便地达成目的&#xff0c;那就先重构哪个程序&#xff0c;使特性的添加比较容易的进行&#xff0c;然后再添加特性 2、重构前&#xff0c;先检查自己是否有一套可靠的测试机制&#xff0…

Myeclipse 6.5 优化

1、取消自动validation validation有一堆&#xff0c;什么xml、jsp、jsf、js等等&#xff0c;我们没有必要全部都去自动校验一下&#xff0c;只是需要的时候才会手工校验一下&#xff01; 取消方法&#xff1a; windows–>perferences–>MyEclipse Enterprise Workbench–…