ProxySocket.cpp
作用:只专门负责连接行情服务器,里面的ConnectThroughProxy()被其他类调用,不同的协议对应不同的连接函数(需传入参数),成功返回true,否则返回false
TW.cpp
作用:定义和行情服务器发送和接受的数据结构;
将数据结构转换为buffer二进制流用于发送;
将收到的来自行情服务器二进制流转换为数据结构;
1.客户端
客户端采用c++ Cstocket 技术向服务器发起请求。
CTWSocket 继承自CProxySocket
CProxySocket : public CSocket
CTWSocket<-CProxySocket<-CSocket<-CAsyncSocket
当在行情登陆窗口点击 “连接”按钮后,开始连接行情服务器,执行了以下动作:
step 1 尝试连接行情服务器,判断是否能连上
通过ProxySocket.cpp类判断行情服务器是否能连接上,如能连接上,则执行登陆,如不能连接上,则返回,显示“行情服务器连接失败”
G:\stock\Tskingfromgoogle\src\NetTS\TWSocket.cpp
->BOOL CTWSocket::BeginWorking(LPCTSTR lpszHostAddress, UINT nHostPort, LPCTSTR lpszUser, LPCTSTR lpszPasswd)
->ProxySocket.cpp->ConnectThroughProxy(m_strHostAddress, m_nHostPort))
\src\NetTS\ProxySocket.cpp
BOOL CProxySocket::ConnectThroughProxy( LPCTSTR lpszHostAddress, UINT nHostPort )
{
USES_CONVERSION;
ASSERT(lpszHostAddress != NULL);
//向服务器发起请求
SOCKADDR_IN sockAddr;
//每个字节都用0填充
memset(&sockAddr,0,sizeof(sockAddr));
LPSTR lpszAscii = T2A((LPTSTR)lpszHostAddress);
//IPv4 网络协议的套接字类型,选择 AF_INET 的目的就是使用 IPv4 进行通信。
sockAddr.sin_family = AF_INET;
//目标主机地址,(如135.178.1.11)
sockAddr.sin_addr.s_addr = inet_addr(lpszAscii);
if (sockAddr.sin_addr.s_addr == INADDR_NONE) //INADDR_NONE 是个宏定义,代表IpAddress 无效的IP地址
{
LPHOSTENT lphost;
lphost = gethostbyname(lpszAscii);
if (lphost != NULL)
sockAddr.sin_addr.s_addr = ((LPIN_ADDR)lphost->h_addr)->s_addr;
else
{
WSASetLastError(WSAEINVAL);
return FALSE;
}
}
//目标主机端口
sockAddr.sin_port = htons((u_short)nHostPort);
{
CString strMessage;
// Format
strMessage.Format(_T("没有选泽代理服务器连接,执行这里"));
// Then display.
AfxMessageBox(strMessage);
//没有选择代理时,执行这里。
return Connect((SOCKADDR*)& sockAddr, sizeof(sockAddr));
使用的非阻塞连接
//BOOL CAsyncSocket::Connect(const SOCKADDR* lpSockAddr, int nSockAddrLen)
}
}
step 2 用用户名和密码登录
\src\NetTS\TWSocket.cpp
客户端将登陆信息转换为一个二进制数据buffer,数据包括数据头和数据区。行情服务器需按约定的数据格式返回表明登陆成功对应的数据包给客户端。
BOOL CTWSocket::Login(LPCTSTR lpszUser, LPCTSTR lpszPasswd)
{
BYTE sbuffer[512];
int lenSend = 0;
if (lenSend = ConstructLoginBuffer(sbuffer, sizeof(sbuffer), lpszUser, lpszPasswd))
{
if (Send(sbuffer, lenSend) == lenSend)
{
int lenRcv = Receive(m_rbuffer, sizeof(m_rbuffer));
if (IsLoginOK(m_rbuffer, lenRcv)) //登陆成功。根据返回结果判断是否登陆成功 2019/05/20 by freeman
{
CTSCache::GetInstance().LoadReports();//根据磁盘文件名dat/report.now 读取分笔明细 2019/05/20 by freeman
//请求初始化 2019/05/20 by freeman
RequestInit();
return TRUE;
}
return FALSE;
}
}
return FALSE;
}
step 3:如果登陆成功,执行两个动作
a.加载本地分笔数据
b.执行请求初始化所需要的数据int CTWSocket::RequestInit()
客户端的收到行情服务的CTWSocket::RequestInit(),解析在
调用链:CTWSocket::OnReceive(int nErrorCode) ->CTSCache::OnReceive->CTSCache::DecodePacket( )->CTSCache::TryGetPacket( int nPacketLen )->TW.CPP->TryGetInit(BYTE* buffer, size_t len, PRCV_DATA pRCV_DATA)
G:\stock\Tskingfromgoogle\src\NetTS\TW.cpp
int TryGetInit(BYTE* buffer, size_t len, PRCV_DATA pRCV_DATA)
{
if(len < sizeof(TW_ANS))
return 0;
TW_ANS * pans = (TW_ANS*)buffer;
if(TW_MAGIC != pans->m_header.m_magic)
return 0;
if(0x02 != pans->m_tag1 || 0x01 != pans->m_tag2)
return 0;
// get it, fill pRCV_DATA
size_t dataoffset = 469;
int datalen = len - dataoffset;
CSPTime tLatest(CSPTime::GetLatestTradeTime(time(NULL)));
if(len > 128)
{
DWORD date = (*(DWORD*)(buffer+45));
CSPTime sptime;
if(sptime.FromStockTimeDay(date))
tLatest = CSPTime(sptime.GetYear(),sptime.GetMonth(),sptime.GetDay(),9,15,0).GetTime();
}
if(pRCV_DATA && datalen >= (int)sizeof(TW_ANS_INIT))
{
// 每组28个字节, 共XXXX组,最后以0xfdfdfdfd为结尾
TW_ANS_INIT * pinit = (TW_ANS_INIT*)(buffer+dataoffset);
int packetsize = datalen/(int)sizeof(TW_ANS_INIT);
memset(pRCV_DATA, 0, sizeof(RCV_DATA));
pRCV_DATA->m_wDataType = RCV_REPORT; // no use
pRCV_DATA->m_nPacketNum = packetsize;
pRCV_DATA->m_bDISK = FALSE;
pRCV_DATA->m_pReport = new RCV_REPORT_STRUCTEx[packetsize];
memset(pRCV_DATA->m_pReport, 0, sizeof(RCV_REPORT_STRUCTEx)*packetsize);
for(int i=0;i <packetsize; i++)
{
if(pinit[i].m_tag != TW_MAGIC_BYTE)
continue;
pRCV_DATA->m_pReport[i].m_cbSize = sizeof(RCV_REPORT_STRUCTEx);
pRCV_DATA->m_pReport[i].m_time = tLatest.GetTime();
pRCV_DATA->m_pReport[i].m_wMarket = (pinit[i].m_type & 0x20) ? SZ_MARKET_EX : SH_MARKET_EX;
pRCV_DATA->m_pReport[i].m_fLastClose = (float)(pinit[i].m_lastclose * 0.001);
strncpy(pRCV_DATA->m_pReport[i].m_szLabel, pinit[i].m_code,
min(sizeof(pRCV_DATA->m_pReport[i].m_szLabel),sizeof(pinit[i].m_code)));
strncpy(pRCV_DATA->m_pReport[i].m_szName, pinit[i].m_name,
min(sizeof(pRCV_DATA->m_pReport[i].m_szName),sizeof(pinit[i].m_name)));
strncpy(pRCV_DATA->m_pReport[i].m_szName+sizeof(pinit[i].m_name)+2, pinit[i].m_shortname,
min(sizeof(pRCV_DATA->m_pReport[i].m_szName)-sizeof(pinit[i].m_name)-2,sizeof(pinit[i].m_shortname)));
}
return len;
}
return 0;
}
step 3 加载本地分笔文件
step 4 发送请求初始化包
请求分笔数据解析
step 1:
2.行情服务器端
2.1 客户端发送过来的数据格式
客户端发送的数据包由三部分组成:数据头、请求的数据种类、数据区
TW_HEADER m_header; // tw header 数据头
BYTE m_tag1; // second data type 数据类型2
BYTE m_tag2; // data type 数据类型
... //其他数据结构,各不相同
2.2 登陆请求包
2.2.1 登陆请求包数据结构
typedef struct _tw_login_t
{
TW_HEADER m_header; // tw header
BYTE m_tag; // 0x0A
WORD m_name_len; // name length
char m_data[256]; // (NAME)(WORD m_passwd_len)(PASSWD)
} TW_LOGIN;
2.2.2 :登陆请求回应包
TW_ANS ack;
memset(&ack, 0, sizeof(ack));
ack.m_header.m_magic = TW_MAGIC;
ack.m_tag1 = ack.m_tag2 = 0;
memcpy(sbuffer, &ack, sizeof(ack)
2.3 RequestInit
2.3.1 RequestInit数据结构
客户端通过CTWSocket::RequestInit()的回应包
客户端通过CTWSocket::RequestInit()发送的请求包数据结构
\src\NetTS\TW.h
32只股票代码
typedef struct _tw_ask_t
{
TW_HEADER m_header; // tw header
BYTE m_tag1; // second data type
BYTE m_tag2; // data type
BYTE m_serial; // request serial, max 0x7F
BYTE m_reserved; // always 0x00
SHORT m_size; // request data's size
TW_STOCK m_stocks[32]; // max 32 stocks
} TW_ASK;
TW_STOCK 是下面的结构
typedef struct tagSTOCK_STRUCTEx {
BYTE m_type; // stock's type, see enum StockType
char m_code[6]; // stock code
} STOCK_STRUCTEx,*pSTOCK_STRUCTEx;
\src\NetTS\Stockdrv.h
num StockType {
typeNone = 0x00,
typeshIndex = 0x10, // 上海指数
typeshA = 0x11, // 上海A股
typeshB = 0x12, // 上海B股
typeshBond = 0x13, // 上海债券
typeshRight = 0x14, // 上海权证
typeshBonus = 0x15, // 上海红利
typeshAdmeasure = 0x16, // 上海配股
typeshInfo = 0x1F, // 上海证交所信息
typeszIndex = 0x20, // 深圳指数 0x20
typeszA = 0x21, // 深圳A股 0x21
typeszB = 0x22, // 深圳B股 0x22
typeszBond = 0x23, // 深圳债券 0x23
typeszRight = 0x24, // 深圳权证 0x24
typeszBonus = 0x25, // 深圳红利 0x25
typeszAdmeasure = 0x26, // 深圳配股 0x26
typeszInfo = 0x2F, // 深圳证交所信息 0x2f
typeGeneralInfo = 0x05, // 综合财经信息 0x05
};
2.4 请求分笔数据
实时行情窗口单个股票的分笔数据请求
2.4.1
又NetTs的数据分发线程 发过来的lPara参数数据是一个RCV_DATA数据结构,在StkNet的CStkReceiver::OnStkReceiverTsdata(UINT wFileType,LONG lPara)首先转换为 COMMPACKET结构
//行情服务器NetTS.DLL中由TSDispatchThreadMain线程发送消息及数据
LRESULT CStkReceiver::OnStkReceiverTsdata(UINT wFileType,LONG lPara)
{
int i = 0;
PBYTE pFileBuf = NULL;
PBYTE pBuffx = NULL;
PGZLXBINDATA pGZLX = NULL;
RCV_DATA * pHeader = (RCV_DATA *) lPara;
// 检查参数
ASSERT(pHeader && pHeader->m_pData);
if (!pHeader || !pHeader->m_pData)
return -1L;
// ASSERT(pHeader->m_nPacketNum > 0 || pHeader->m_bDISK);
if (pHeader->m_nPacketNum <= 0 && !pHeader->m_bDISK)
return -1L;
// 对于处理较慢的数据类型,建议将数据备份,另创建一线程处理
switch(wFileType)
{
case RCV_REPORT:
{
// 检查参数
//ASSERT(pHeader->m_nPacketNum > 0 && !pHeader->m_bDISK);
if (pHeader->m_nPacketNum <= 0 || pHeader->m_bDISK)
return -1L;
// 分配备份数据存储空间
COMMPACKET * pCommPacket = AllocCommPacket(CStock::dataReport, pHeader->m_nPacketNum);
if (NULL == pCommPacket)
return -1L;
// 修改AfxGetStockContainer()
int nBufSize = pHeader->m_pReport[0].m_cbSize;
PBYTE pBaseBuf = (PBYTE)&pHeader->m_pReport[0];
for(i=0; i<pHeader->m_nPacketNum; i++)
{
PRCV_REPORT_STRUCTEx pReport = (PRCV_REPORT_STRUCTEx)(pBaseBuf + nBufSize*i);
if (convert_TSREPORT_to_REPORT(pReport, &(pCommPacket->m_pReport[i])))
{
/* 以下代码转移到新建线程StkProcessReceiveReport(LPVOID pParam)中执行 */
AfxGetStkReceiver().OnReceiveReport(&(pCommPacket->m_pReport[i]));//主力大单和盘中预警处理
}
}
{
//CSingleLock lock(&g_mutexCommPacket,TRUE);
//g_aptrCommPacket.Add(pCommPacket);//存入全局变量
}
// 以下代码转移到新建线程StkProcessReceiveReport(LPVOID pParam)中执行,暂时注释掉,
// 保存至本地文件
//AfxGetDB().StoreReport(pCommPacket->m_pReport, pCommPacket->m_dwCount, FALSE);
// 通知各股票信息显示窗口,刷新行情数据
for(i=0; i<m_awndRcvData.GetSize(); i++)
::SendMessage(m_awndRcvData[i], WM_APP_STKRECEIVER_DATA, CStock::dataReport, (LPARAM)pCommPacket);
// 释放备份数据存储空间
FreeCommPacket(pCommPacket);
}
break;
其中convert_TSREPORT_to_REPORT(pReport, &(pCommPacket->m_pReport[i])语句通视RCV_REPORT_STRUCTEx 转换为 标准 REPORT。
所以,要确保行情服务器发送过来这些信息字段。
/通视RCV_REPORT_STRUCTEx 转换为 标准 REPORT
BOOL convert_TSREPORT_to_REPORT( RCV_REPORT_STRUCTEx *pTSReport, REPORT * pReport )
{
SP_ASSERT( pTSReport && pReport );
if( NULL == pTSReport || NULL == pReport )
return FALSE;
memset( pReport, 0, sizeof(REPORT) );
// 股票市场
pReport->m_dwMarket = TSMarket_to_Market( pTSReport->m_wMarket );
strncpy( pReport->m_szCode, pTSReport->m_szLabel, min(sizeof(pReport->m_szCode)-1,sizeof(pTSReport->m_szLabel)) );
strncpy( pReport->m_szName, pTSReport->m_szName, min(sizeof(pReport->m_szName)-1,sizeof(pTSReport->m_szName)) );
pReport->m_time = pTSReport->m_time;
pReport->m_fLast = pTSReport->m_fLastClose;
pReport->m_fOpen = pTSReport->m_fOpen;
pReport->m_fHigh = pTSReport->m_fHigh;
pReport->m_fLow = pTSReport->m_fLow;
pReport->m_fNew = pTSReport->m_fNewPrice;
pReport->m_fVolume = pTSReport->m_fVolume * 100;
pReport->m_fAmount = pTSReport->m_fAmount;
pReport->m_fBuyPrice[0] = pTSReport->m_fBuyPrice[0];
pReport->m_fBuyPrice[1] = pTSReport->m_fBuyPrice[1];
pReport->m_fBuyPrice[2] = pTSReport->m_fBuyPrice[2];
pReport->m_fBuyVolume[0] = pTSReport->m_fBuyVolume[0] * 100;
pReport->m_fBuyVolume[1] = pTSReport->m_fBuyVolume[1] * 100;
pReport->m_fBuyVolume[2] = pTSReport->m_fBuyVolume[2] * 100;
pReport->m_fSellPrice[0] = pTSReport->m_fSellPrice[0];
pReport->m_fSellPrice[1] = pTSReport->m_fSellPrice[1];
pReport->m_fSellPrice[2] = pTSReport->m_fSellPrice[2];
pReport->m_fSellVolume[0] = pTSReport->m_fSellVolume[0] * 100;
pReport->m_fSellVolume[1] = pTSReport->m_fSellVolume[1] * 100;
pReport->m_fSellVolume[2] = pTSReport->m_fSellVolume[2] * 100;
if( pTSReport->m_cbSize >= sizeof(RCV_REPORT_STRUCTEx) )
{
pReport->m_fBuyPrice[3] = pTSReport->m_fBuyPrice4;
pReport->m_fBuyVolume[3] = pTSReport->m_fBuyVolume4 * 100;
pReport->m_fSellPrice[3] = pTSReport->m_fSellPrice4;
pReport->m_fSellVolume[3] = pTSReport->m_fSellVolume4 * 100;
pReport->m_fBuyPrice[4] = pTSReport->m_fBuyPrice5;
pReport->m_fBuyVolume[4] = pTSReport->m_fBuyVolume5 * 100;
pReport->m_fSellPrice[4] = pTSReport->m_fSellPrice5;
pReport->m_fSellVolume[4] = pTSReport->m_fSellVolume5 * 100;
}
return TRUE;
}
客户端接收的行情服务器发送过来的信息字段
int TryGetReport(BYTE* buffer, size_t len, PRCV_DATA pRCV_DATA)
{
//add by freeman 2019/06/08
char szText[256];
sprintf(szText, "←buffer Byte length:%d", len);
g_pWinTrace->Debug()->Send("TW.cpp::TryGetReport(BYTE* buffer, size_t len, PRCV_DATA pRCV_DATA)", szText);
if(len < sizeof(TW_ANS))
return 0;
//返回包的数据包头部 2019/06/08 by freeman
TW_ANS * pans = (TW_ANS*)buffer;
if(TW_MAGIC != pans->m_header.m_magic)
return 0;
if(0x00 != pans->m_tag1 || 0x03 != pans->m_tag2)
return 0;
CTWStockArray astocks;
//根据行情服务器返回的数据包序号,从客户端维护的一张全局g_serial_stocks 股票表中,找到股票名单,拷贝到astocks变量中.
BOOL bStockOK = GetSerialStock(pans->m_serial, astocks);//根据序号找到对应得股票
// get it, fill pRCV_DATA
size_t dataoffset = 43;//数据包头部的长度是43字节
int datalen = len - dataoffset;//除掉头部的分笔数据缓冲区的字节数长度
sprintf(szText, "←buffer Byte length(除掉头部):%d", datalen);
g_pWinTrace->Debug()->Send("TW.cpp::TryGetReport(BYTE* buffer, size_t len, PRCV_DATA pRCV_DATA)", szText);
if(pRCV_DATA && bStockOK && astocks.GetSize() > 0 && datalen >= (int)sizeof(TW_ANS_REPORT))
{
sprintf(szText, "←开始处理TW_ANS_REPORT:%d", datalen);
g_pWinTrace->Debug()->Send("TW.cpp::TryGetReport(BYTE* buffer, size_t len, PRCV_DATA pRCV_DATA)", szText);
TW_ANS_REPORT * preport = (TW_ANS_REPORT*)(buffer+dataoffset);//指向第一个分笔数据结构的实际地址?并且是三档结构?
//设置pRCV_DATA数据结构
memset(pRCV_DATA, 0, sizeof(RCV_DATA));
pRCV_DATA->m_wDataType = RCV_REPORT; // no use
pRCV_DATA->m_nPacketNum = astocks.GetSize();//记录数
pRCV_DATA->m_bDISK = FALSE;
pRCV_DATA->m_pReport = new RCV_REPORT_STRUCTEx[astocks.GetSize()];//准备存储分笔数据的内存区域,五档结构
memset(pRCV_DATA->m_pReport, 0, sizeof(RCV_REPORT_STRUCTEx)*astocks.GetSize());//置0,一只股票只有一个分笔成交结构
CSPTime tLatest = CTSCache::GetInstance().GetLocalLatest();
//多只股票
for(int i=0; i<astocks.GetSize() && i<datalen/(int)sizeof(TW_ANS_REPORT); i++) //循环处理行情服务器发过来的数据
{
pRCV_DATA->m_pReport[i].m_cbSize = sizeof(RCV_REPORT_STRUCTEx);//结构体大小:
pRCV_DATA->m_pReport[i].m_time = tLatest.GetTime(); //时间:怎么会是本地时间?应该是数据包的时间啊?
pRCV_DATA->m_pReport[i].m_wMarket = (astocks[i].m_type & 0x20) ? SZ_MARKET_EX : SH_MARKET_EX;//市场类型:
strncpy(pRCV_DATA->m_pReport[i].m_szLabel, astocks[i].m_code,
min(sizeof(pRCV_DATA->m_pReport[i].m_szLabel),sizeof(astocks[i].m_code))); ////股票代码:股票代码。成交明细应按股票代码顺序。问题:没有成交明细怎么办?
sprintf(szText, "←preport[i].m_open:%d", preport[i].m_open);
g_pWinTrace->Debug()->Send("TW.cpp::TryGetReport(BYTE* buffer, size_t len, PRCV_DATA pRCV_DATA)", szText);
// unknown pRCV_DATA->m_pReport[i].m_fLastClose = (float)(0.001*preport->m_open);
pRCV_DATA->m_pReport[i].m_fOpen = (float)(0.001*preport[i].m_open);
pRCV_DATA->m_pReport[i].m_fHigh = (float)(0.001*preport[i].m_high);
pRCV_DATA->m_pReport[i].m_fLow = (float)(0.001*preport[i].m_low);
pRCV_DATA->m_pReport[i].m_fNewPrice = (float)(0.001*preport[i].m_new);
pRCV_DATA->m_pReport[i].m_fVolume = (float)(0.01*preport[i].m_volume);
pRCV_DATA->m_pReport[i].m_fAmount = (float)(0.01*preport[i].m_amount);
pRCV_DATA->m_pReport[i].m_fBuyPrice[0]= (float)(0.001*preport[i].m_buy1);
pRCV_DATA->m_pReport[i].m_fBuyPrice[1]= (float)(0.001*preport[i].m_buy2);
pRCV_DATA->m_pReport[i].m_fBuyPrice[2]= (float)(0.001*preport[i].m_buy3);
pRCV_DATA->m_pReport[i].m_fBuyVolume[0]= (float)(0.01*preport[i].m_buy1vol);
pRCV_DATA->m_pReport[i].m_fBuyVolume[1]= (float)(0.01*preport[i].m_buy2vol);
pRCV_DATA->m_pReport[i].m_fBuyVolume[2]= (float)(0.01*preport[i].m_buy3vol);
pRCV_DATA->m_pReport[i].m_fSellPrice[0]= (float)(0.001*preport[i].m_sell1);
pRCV_DATA->m_pReport[i].m_fSellPrice[1]= (float)(0.001*preport[i].m_sell2);
pRCV_DATA->m_pReport[i].m_fSellPrice[2]= (float)(0.001*preport[i].m_sell3);
pRCV_DATA->m_pReport[i].m_fSellVolume[0]= (float)(0.01*preport[i].m_sell1vol);
pRCV_DATA->m_pReport[i].m_fSellVolume[1]= (float)(0.01*preport[i].m_sell2vol);
pRCV_DATA->m_pReport[i].m_fSellVolume[2]= (float)(0.01*preport[i].m_sell3vol);
}
return len;
}
return 0;
}
其他主动请求构造的请求数据包
/* INIT
m_tag1 = 0x01
m_tag2 = 0x01
m_serial = 0x00
m_size = 0x0007
the next 7 bytes = 0xdc, 0xcc, 0x31, 0x01, 0x00, 0x00, 0x00
the next is string:
"E013.45D013.04D022.03D043.20xcs1.00.is2.00"
*/
/* REPORT
m_tag1 = 0x00
m_tag2 = 0x03
m_size = 本次请求股票个数
重复m_size次TW_STOCK
*/
/* MINUTE
m_tag1 = 0x01
m_tag2 = 0x04
m_size = 0x0001
m_stocks = 1个stock
*/
/* HISTORY
更换股票后的第一次请求:
m_tag1 = 0x20日 0x33五分 0x43十五分 0x53三十分 00x63六十分 0x83周 0x93月
m_tag2 = 0x09
m_size = 请求数据个数*-1
m_stocks = 1个stock
更换数据类型后的第一次请求:
m_tag1 = 0x11日 0x31五分 0x41十五分 0x51三十分 00x61六十分 0x81周 0x91月
m_tag2 = 0x09
m_size = 请求数据个数*-1
m_stocks = 1个stock
连续请求:
m_tag1 = 0x12日 0x32五分 0x42十五分 0x52三十分 00x62六十分 0x82周 0x92月
m_tag2 = 0x09
m_size = 请求数据个数*-1
m_stocks = 1个stock
*/
/* MULTISORT
m_tag1 = 0x0a
m_tag2 = 0x08
m_size = 请求数据个数*-1,一般情况下为 ((WORD)-9)
m_stocks[0].m_type = enum StockType
m_stocks[0].m_code = 0x06 0x00 0xff 0x01 0x00 0x00
*/
/* DETAIL
m_tag1 = 0x01
m_tag2 = 0x02
m_size = 0x0001
m_stocks = 1个stock
*/
/* BASE
m_tag1 = 0x05
m_tag2 = 0x0b
m_size = 0x0005
m_stocks = 5个stock
*/