策略为王源代码分析-客户端连接行情服务器

Published

调用链条:\StkUI\MainFrm.cpp->CMainFrame::OnSysConnectserver()

         ->\StkNet\Src\StartupDlg.cpp->CStartupDlg::OnOK()

       

1.点击“连接行情服务器”按钮,执行的是

G:\stock\TskingVS2019\src\Client\StkUI\MainFrm.cpp

“连接行情服务器”按钮ID为ID_SYS_CONNECTSERVER,点击时发送消息OnSysConnectserver

BEGIN_MESSAGE_MAP(CMainFrame, CTskMainFrame)
	//{{AFX_MSG_MAP(CMainFrame)
	ON_WM_CREATE()
	ON_WM_TIMER()
	ON_WM_SIZE()
	ON_WM_CLOSE()
	

	ON_COMMAND(ID_SYS_CONNECTSERVER, OnSysConnectserver)
	
	END_MESSAGE_MAP()

2.弹出“连接行情服务器”登陆框

G:\stock\TskingVS2019\src\Client\StkUI\MainFrm.cpp

////////////////////////////////////////////////////////////////////////////////
// “系统”菜单
// //主界面菜单->点击"连接行情服务器"菜单,执行这里 2019/06/08 by freeman
void CMainFrame::OnSysConnectserver()
{
	// AfxGetStkReceiver().NetEngineBeginWorking();
	extern BOOL CALLBACK LoadProgram(HWND hWnd, int nMinProgress, int nMaxProgress);

	CStartupDlg	startup;  //在StkNet中定义的“连接行情服务器”登陆框,G:\stock\TskingVS2019\src\Client\StkNet\Include\StartupDlg.h
	CBitmap	bmp;
	bmp.LoadBitmap(IDB_DLGLEFTLOGO);
	startup.SetBitmap((HBITMAP)bmp.GetSafeHandle());
	//加载数据
	startup.SetLoadProgramFunc(LoadProgram);
	startup.SetDisableOffline(TRUE);
	//显示行情服务器选择和连接对话框
	startup.DoModal();
}

“连接行情服务器”登陆框在StkNet工程中定义,类名为CStartupDlg,由:\stock\TskingVS2019\src\Client\StkNet\Src\StartupDlg.cpp实现

3.行情连接登陆框点击“连接”按钮解析

G:\stock\TskingVS2019\src\Client\StkNet\Src\StartupDlg.cpp

void CStartupDlg::OnOK() 
{
   ...读取一些配置信息...

   //首先启动线程RefreshServers,功能:登陆数据服务器,从数据服务器主下载代码表、财务数据、除权数据到本地 。2019/05/20 by freeman
	AfxBeginThread( RefreshServers, NULL, THREAD_PRIORITY_NORMAL);

     //通过AfxGetStkReceiver().NetEngineBeginWorking尝试与行情服务器进行连接。
	if( !AfxGetStkReceiver().NetEngineBeginWorking( m_strAddress, m_nPort, m_strUser, m_strPasswd ) )
	{
		//显示 "连接服务器失败"
		m_staticInfo.SetWindowText( AfxModuleLoadString(IDS_STARTUP_CONNECTFAILED) );
		m_btnOK.EnableWindow( TRUE );
		if( !m_bDisableOffline )
			m_btnOffline.EnableWindow( TRUE );
		m_ctrlProgress.ShowWindow( SW_HIDE );
		return;
	}

}

UINT RefreshServers( LPVOID pParam )

 

接收数据过程:

1.事件机制接收数据

2.

(CTWSocket)CTWSocket::OnReceive(int nErrorCode)负责采用事件机制接收行情服务器发送过来的收据

然后调用CTSCache::GetInstance().OnReceive(m_rbuffer, nReceive);对收到的数据解码

//数据接收事件
void CTWSocket::OnReceive(int nErrorCode) 
{
	m_timeReceiveLast = time(NULL);

	//读取数据事件
	int nReceive = Receive(m_rbuffer, sizeof(m_rbuffer));
	if (nReceive > 0)
	{
		//调用程序对收到的数据解码
		CTSCache::GetInstance().OnReceive(m_rbuffer, nReceive);

		if (nReceive < 256 && TryGetLength(m_rbuffer, nReceive) < 256)	// 收到小块包,说明大包接收完毕
			m_bIsReceiving = FALSE;

		if (nReceive == sizeof(m_rbuffer))
			OnReceive(nErrorCode);
	}

	CSocket::OnReceive(nErrorCode);
}

 

CTSCache::GetInstance().OnReceive(m_rbuffer, nReceive);

//对收到的数据进行解码
int CTSCache::OnReceive( BYTE * buf, size_t len )
{
	CSingleLock lock(&m_mutexBuffer,TRUE);

	if( NULL == buf || len <= 0 )
		return 0;
	if( len > sizeof(m_buffer) )
		return 0;
	if( m_nBufLen + len > sizeof(m_buffer) )
		m_nBufLen = 0;	// discard old
	memcpy( m_buffer+m_nBufLen, buf, len );
	m_nBufLen += len;
	int packets = DecodePacket();
	while( packets > 0 )
		packets = DecodePacket();
	return len;
}

解码代码

int CTSCache::DecodePacket( )
{
	if( m_nBufLen <= 0 )
		return 0;

	int nPacketLen = FindFirstPacketLength();

	if( nPacketLen > 0 && nPacketLen <= (int)m_nBufLen )
	{
		TryGetPacket( nPacketLen );
		DiscardPacket( nPacketLen );
		return 1;
	}

	if( m_nBufLen > sizeof(m_buffer)/2 )
		m_nBufLen = 0;	// truncate if too big and no packets found.
	return 0;
}

 

int CTSCache::TryGetPacket( int nPacketLen ) 函数按下面的循序逐个尝试解码,看属于哪种类型的数据包

nLen = TryGetInit( m_buffer, nPacketLen, prcvdata );

nLen = TryGetReport( m_buffer, nPacketLen, prcvdata );

nLen = TryGetMinute( m_buffer, nPacketLen, prcvdata, prcvdata2 );

nLen = TryGetHistory( m_buffer, nPacketLen, prcvdata );

int CTSCache::TryGetPacket( int nPacketLen )
{
	if( nPacketLen <= 0 || nPacketLen > (int)m_nBufLen )
		return 0;

	PRCV_DATA	prcvdata = NULL;
	PRCV_DATA	prcvdata2 = NULL;
	int nLen = 0;

	prcvdata = new RCV_DATA();
	memset( prcvdata, 0, sizeof(RCV_DATA) );
 	nLen = TryGetInit( m_buffer, nPacketLen, prcvdata );
	if( nLen > 0 )
	{
		{ // clear
			CSingleLock lock(&m_mutexReports,TRUE);
			m_aReports.RemoveAll();
			m_mapReports.RemoveAll();
		}
		PushReport( prcvdata->m_pReport, prcvdata->m_nPacketNum );
		PushPacket( RCV_REPORT, prcvdata );
		prcvdata = NULL;
		StoreReports( );
		return 1;
	}
	if( prcvdata )	FreePacket(prcvdata);

	prcvdata = new RCV_DATA();
	memset( prcvdata, 0, sizeof(RCV_DATA) );
	nLen = TryGetReport( m_buffer, nPacketLen, prcvdata );
	if( nLen > 0 )
	{
		PushReport( prcvdata->m_pReport, prcvdata->m_nPacketNum );
		PushPacket( RCV_REPORT, prcvdata );
		prcvdata = NULL;
		return 1;
	}
	if( prcvdata )	FreePacket(prcvdata);

	prcvdata = new RCV_DATA();
	prcvdata2 = new RCV_DATA();
	memset( prcvdata, 0, sizeof(RCV_DATA) );
	memset( prcvdata2, 0, sizeof(RCV_DATA) );
	nLen = TryGetMinute( m_buffer, nPacketLen, prcvdata, prcvdata2 );
	if( nLen > 0 )
	{
		PushReport( prcvdata2->m_pReport, prcvdata2->m_nPacketNum );
		PushPacket( RCV_FILEDATA, prcvdata );
		PushPacket( RCV_REPORT, prcvdata2 );
		prcvdata = NULL;
		prcvdata2 = NULL;
		return 1;
	}
	if( prcvdata )	FreePacket(prcvdata);
	if( prcvdata2 )	FreePacket(prcvdata2);

	prcvdata = new RCV_DATA();
	memset( prcvdata, 0, sizeof(RCV_DATA) );
	nLen = TryGetHistory( m_buffer, nPacketLen, prcvdata );
	if( nLen > 0 )
	{
		PushPacket( RCV_FILEDATA, prcvdata );
		prcvdata = NULL;
		return 1;
	}
	if( prcvdata )	FreePacket(prcvdata);

	prcvdata = new RCV_DATA();
	memset( prcvdata, 0, sizeof(RCV_DATA) );
	nLen = TryGetMultisort( m_buffer, nPacketLen, prcvdata );
	if( nLen > 0 )
	{
		PushPacket( RCV_FILEDATA, prcvdata );
		prcvdata = NULL;
		return 1;
	}
	if( prcvdata )	FreePacket(prcvdata);

	prcvdata = new RCV_DATA();
	memset( prcvdata, 0, sizeof(RCV_DATA) );
	nLen = TryGetDetail( m_buffer, nPacketLen, prcvdata );
	if( nLen > 0 )
	{
		PushPacket( RCV_REPORT, prcvdata );
		prcvdata = NULL;
		return 1;
	}
	if( prcvdata )	FreePacket(prcvdata);

	prcvdata = new RCV_DATA();
	memset( prcvdata, 0, sizeof(RCV_DATA) );
	nLen = TryGetBase( m_buffer, nPacketLen, prcvdata );
	if( nLen > 0 )
	{
		PushPacket( RCV_FILEDATA, prcvdata );
		prcvdata = NULL;
		return 1;
	}
	if( prcvdata )	FreePacket(prcvdata);

	return 0;
}

解码成功则得到PRCV_DATA

 

客户端将网络服务器来的数据先转换成PRCV_DATA

,然后再
转换成COMMPACKET。

 

 

G:\stock\TskingVS2019\src\Client\StkLib\Include\Stock.h

COMMPACKET是各窗口能识别通用的数据结构

// 行情通用数据包
#define	STKLIB_COMMPACKET_TAG	'KPMC'
typedef struct commpacket_t {
	DWORD	m_dwTag;			// = STKLIB_COMMPACKET_TAG
	DWORD	m_dwDataType;		// see CStock::DataType
	DWORD	m_dwCount;

	union
	{
		REPORT *	m_pReport;		// 行情刷新数据
		MINUTE *	m_pMinute;		// 分时成交数据
		MULTISORT *	m_pMultisort;	// 综合排名数据
		OUTLINE *	m_pOutline;		// 附加数据
		KDATA *		m_pKdata;		// 补充历史日线数据
		DRDATA *	m_pDrdata;		// 补充权息资料
		STOCKCODE *	m_pStockcode;	// 股票代码
		void *		m_pData;
	};
} COMMPACKET, *PCOMMPACKET;

G:\stock\TskingVS2019\src\Client\NetTS\Stockdrv.h

PRCV_DATA是将行情服务器发送过来的数据构造成这么一个结构,然后要转换成COMMPACKET结构,再用消息的形式发往各窗口,以便各窗口接收。

typedef struct tagRCV_DATA
{
	int					m_wDataType;			// 文件类型
	int					m_nPacketNum;			// 记录数,参见注一
	RCV_FILE_HEADEx		m_File;					// 文件接口
	BOOL				m_bDISK;				// 文件是否已存盘的文件
	union
	{
		RCV_REPORT_STRUCTEx  *	m_pReport;
		RCV_HISTORY_STRUCTEx *	m_pDay;
		RCV_MINUTE_STRUCTEx  *	m_pMinute;
		RCV_POWER_STRUCTEx	 *	m_pPower;
		RCV_MULTISORT_STRUCTEx	*	m_pMultisort;
		void				 *	m_pData;		// 参见注二
	};
} RCV_DATA,*PRCV_DATA;
/* 消息处理程序 DEMO*/
LONG OnStkDataOK(UINT wParam,LONG lParam)
{
union tagSrcStock	DOS_StkBuf;
RCV_REPORT_STRUCTEx NEW_StkBuf;
PBYTE				pDataBuf;
RCV_DATA			Header;
RCV_DATA	*		pHeader;
DWORD				dwFileLen;
int					ok;
	pHeader = (RCV_DATA *)lParam;
	switch( wParam )
	{
	case RCV_REPORT:						// 共享数据引用方式,股票行情
		for(i=0; i<pHeader->m_nPacketNum; i++)
		{
			pHeader->m_pReport[i] ...
			// 数据处理
		}
		break;

	case RCV_FILEDATA:						// 共享数据引用方式,文件
		switch(pHeader->m_wDataType)
		{
		case FILE_HISTORY_EX:				// 补日线数据
			break;
		case FILE_MINUTE_EX:				// 补分钟线数据
			break;
		case FILE_POWER_EX:					// 补充除权数据
			break;
		case FILE_BASE_EX:					// 钱龙兼容基本资料文件,m_szFileName仅包含文件名
			break;
		case FILE_NEWS_EX:					// 新闻类,其类型由m_szFileName中子目录名来定
			break;
		case FILE_HTML_EX:					// HTML文件,m_szFileName为URL
			break;
		case FILE_SOFTWARE_EX:				// 升级软件
			break;
		}
		break;
	default:
		return 0;							// unknown data
	}
	return 1;
}