调用链条:\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;
}