ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

VC6.0开发腾讯股票实时行情工具:接口解析与WinInet实现

VC6.0开发腾讯股票实时行情工具:接口解析与WinInet实现 简介一份面向VC6.0开发者的腾讯股票实时行情数据获取源码工程解决在老旧Windows开发环境下通过HTTP协议调用腾讯股票接口并解析返回数据的需求适合金融数据抓取初学者或维护遗留项目的程序员参考。压缩包共27个文件约1.79MB包含头文件、C源文件、项目工程文件dsw/dsp、可执行exe及调试文件pdb/obj等可直接打开编译运行也可对照源码学习WinInet网络编程流程。已有4442人学习。工程演示了核心步骤初始化WinInet、创建HTTP请求、发送GET请求、循环读取响应并解析涉及开盘价、收盘价、最新价等关键字段的提取同时附有ReadMe说明和对话框资源便于理解程序结构与界面设计。对于想了解VC6.0下网络通信实现或迁移旧代码至现代环境的开发者这套源码提供了完整可运行范例。 说实话我这次接到的任务有点“古早味”——客户要求在一台只装了VC6.0的旧电脑上写一个拉取腾讯股票接口实时行情的小工具。一开始我以为会很痛苦毕竟VC6.0这玩意连自动补全都费劲更别说拿来写网络程序。但真正动手后发现腾讯股票接口自身极其简单配合VC6.0的WinInet网络封装反而能做到零依赖、秒开速跑整个过程踩了几个旧时代特有的坑今天把完整思路和可用的vc6.0源代码分享出来。1. 腾讯行情接口的真实模样一次抓包与协议拆解先说接口本身。腾讯股票接口的核心URL非常简单不支持任何签名鉴权机制直接用HTTP GET请求就能拿到数据http://qt.gtimg.cn/qsh600000,sz000001后面的参数是股票代码规则是“交易所前缀股票数字代码”sh代表上交所sz代表深交所。多个代码用英文逗号拼接一次请求最多能拉几十只股票响应速度快到可以忽略不计。实测下来单次请求从我发出到拿到完整报文基本在100毫秒以内。请求返回的内容长这样v_sh6000001~浦发银行~600000~10.25~10.30~10.20~123456~65432~58024~10.24~100~10.23~200~10.22~300~10.21~400~10.25~500~10.26~600~10.27~700~10.28~800~10.29~900~~20240415155445~-0.05~-0.48~10.31~10.18~10.25/123456/126501120~123456~12650~0.79~7.36~~10.31~10.18~0.13~2543.66~2543.66~0.13~10.25~10.26~-0.05~-0.48~10.25~10.24~1.26~1287890~128.78~0.6900~;这段返回的协议格式有几个关键特征搞清楚了后面解析才不会懵每一行以v_股票代码开头双引号结束分号结尾。双引号内部所有字段用波浪号~分隔。字段顺序是固定的但对新人极其不友好——因为字段非常多数错一个位置后面全乱。我针对常用字段整理了一个索引表因为索引从0开始网上很多人贴的索引数字其实有偏移这里我实测校准过的版本索引含义说明0未知状态值正常情况是11股票名称如“浦发银行”2股票代码如“600000”3当前价格最重要字段4昨收价前收盘价5今开价开盘价6成交量手注意单位是手7外盘外盘成交量8内盘内盘成交量9买一价买盘第一档价格10买一量买盘第一档挂单量19卖一价卖盘第一档价格20卖一量卖盘第一档挂单量30时间戳格式如2024041515544531涨跌额当前价-昨收价32涨跌幅百分比数值33最高价当日最高34最低价当日最低35价格/成交量/成交额组合斜杠分隔这三个数据36成交量手另一个维度的成交量37成交额万注意单位是万元38换手率百分比39市盈率动态市盈率43振幅百分比44流通市值单位是亿45总市值单位是亿46市净率市净率数值这个表格建议直接存下来我每次重新写行情工具都会翻出来对照。不过需要提醒的是腾讯对字段顺序偶尔会做微调写程序时不要硬编码绝对索引去取所有字段取了前面几个核心字段就够用这样即使后面字段有变化也不容易翻车。2. VC6.0工程的全套实现请求、解析、定时刷新环境就不多纠结了我是在Win10上用兼容模式跑VC6.0SP6补丁打好之后编译完全没问题。新建一个基于对话框的MFC工程然后在StdAfx.h里加上网络头文件#include afxinet.h这是VC6时代Windows下网络编程的标配它封装了WinInet底层API用CInternetSession和CHttpFile两个类就能完成HTTP请求不需要额外引入libcurl这种第三方库对老项目和裸奔部署环境来说非常友好。核心请求函数我封装成了这样接受股票代码返回原始字符串CString GetTencentStockData(const CString strCode) { CInternetSession session(_T(StockQuote/1.0)); CHttpFile* pFile NULL; CString strResult; try { CString strUrl _T(http://qt.gtimg.cn/q) strCode; pFile (CHttpFile*)session.OpenURL(strUrl, 1, INTERNET_FLAG_RELOAD | INTERNET_FLAG_NO_CACHE_WRITE, NULL, 0); if (pFile) { CString strLine; while (pFile-ReadString(strLine)) { strResult strLine; strResult _T(\r\n); } pFile-Close(); delete pFile; pFile NULL; } } catch (CInternetException* pEx) { TCHAR szErr[256] {0}; pEx-GetErrorMessage(szErr, 255); OutputDebugString(szErr); pEx-Delete(); if (pFile) { delete pFile; pFile NULL; } session.Close(); return _T(); } session.Close(); return strResult; }几个容易出错的地方我逐个说明OpenURL的第四个参数我传了1INTERNET_SERVICE_HTTP这是告诉WinInet我们要打开的是HTTP资源。不同的服务类型对应的返回对象类型不一样只有HTTP的OpenURL才会返回CHttpFile指针所以必须要强转。INTERNET_FLAG_RELOAD表示强制从服务器重新下载不读本地缓存这对行情数据太关键了——不加这个断线重连后可能拿到陈旧数据。ReadString是逐行读取腾讯接口返回的内容虽然长但就是一行循环两次就完事。解析函数反而是最需要小心的。VC6的CString有一个非常尴尬的缺陷它没有Tokenize方法这个方法是后来VC7才加的。网上流传的很多VC6代码会用AfxExtractSubString按逗号分割但腾讯接口的分隔符是波浪号所以干脆自己写一个拆分函数int SplitStringByChar(const CString strSrc, TCHAR cSep, CStringArray arrOut) { arrOut.RemoveAll(); if (strSrc.IsEmpty()) return 0; int nPos 0; int nLen strSrc.GetLength(); while (nPos nLen) { int nNext strSrc.Find(cSep, nPos); if (nNext 0) { arrOut.Add(strSrc.Mid(nPos)); break; } arrOut.Add(strSrc.Mid(nPos, nNext - nPos)); nPos nNext 1; } return arrOut.GetSize(); }这个函数思路很朴素就是循环找分隔符找到就把中间这段截出来放进数组直到字符串末尾。用Find而不是逐字符比较是因为CString的Find内部有优化速度比手写循环快不少。有了拆分函数解析腾讯返回数据就简单了。需要注意原始字符串开头有v_sh600000这段前缀、结尾有双引号和分号必须先把双引号内的内容抠出来再按波浪号拆分BOOL ParseStockData(const CString strRaw, StockInfo info) { int nQuote1 strRaw.Find(_T()); int nQuote2 strRaw.Find(_T(), nQuote1 1); if (nQuote1 0 || nQuote2 0) return FALSE; CString strBody strRaw.Mid(nQuote1 1, nQuote2 - nQuote1 - 1); CStringArray arr; if (SplitStringByChar(strBody, _T(~), arr) 35) return FALSE; info.strName arr[1]; info.strCode arr[2]; info.strPrice arr[3]; info.strPreClose arr[4]; info.strOpen arr[5]; info.strChange arr[31]; info.strChangePct arr[32]; info.strHigh arr[33]; info.strLow arr[34]; info.strTime arr[30]; return TRUE; }这里判断数组长度小于35就返回失败是因为核心字段的索引至少到34如果腾讯返回的字段数不够说明协议有变或者请求异常直接退出比硬解析安全得多。定时刷新用MFC的定时器就可以实现在初始化函数里SetTimer(1, 5000, NULL);然后响应OnTimer5秒刷新一次。对行情工具来说5秒是股票软件的标准刷新间隔不频繁也不延迟。这里需要提醒千万别在OnTimer里直接调UpdateData(TRUE)之类的对话框刷新最好用PostMessage把数据从函数里发回UI线程否则界面会卡顿。3. 界面显示与编码转换别让中文变乱码VC6.0默认创建的工程是MBCS多字节字符集编码而腾讯接口返回的中文是GBK编码这两者其实是兼容的。也就是说你的CString直接用%s输出或者SetDlgItemText显示中文不会出现乱码坑里说的那种情况。但有个前提必须保证整个工程编译时字符集没有切换成Unicode。VC6.0的工程属性里一旦设置了_UNICODE预处理宏CString底层变成宽字符再把腾讯返回的GBK字符串丢给SetDlgItemText就会显示成乱码。如果你确实要用Unicode字符集那得做一次GBK到Unicode的显式转换CString ConvertGBKToUnicode(const CString strGBK) { if (strGBK.IsEmpty()) return _T(); int nLen MultiByteToWideChar(CP_ACP, 0, strGBK, -1, NULL, 0); if (nLen 0) return _T(); CString strUnicode; MultiByteToWideChar(CP_ACP, 0, strGBK, -1, strUnicode.GetBuffer(nLen), nLen); strUnicode.ReleaseBuffer(); return strUnicode; }这里的CP_ACP代表当前系统的ANSI代码页在中文Windows上就是GBK。如果你在英文系统上跑应改为936强制指定GBK代码页否则中文会变成问号。界面布局上我用了最简单的静态文本框加编辑框组合股票名称显示在左边的GroupBox里价格、涨跌额、涨跌幅各自占用一个编辑框。有人喜欢用ListView滚动显示多只股票但那个在VC6里设置列头比较烦群里面试时经常有人栽在这上面。对单只股票监控来说直接SetDlgItemText更新文本最省事。为了让界面更直观我加了一个红色/绿色显示涨跌的小技巧。在对话框类里加一个OnPaint绘制函数或者用一个Static控件的自绘把涨跌数字颜色区分开。简化的做法是设置Static控件的文字颜色在对话框的OnCtlColor里判断控件IDHBRUSH CStockQuoteDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) { HBRUSH hbr CDialog::OnCtlColor(pDC, pWnd, nCtlColor); if (pWnd-GetDlgCtrlID() IDC_PRICE) { pDC-SetTextColor(RGB(255, 0, 0)); // 涨红 } else if (pWnd-GetDlgCtrlID() IDC_CHANGE) { pDC-SetTextColor(RGB(0, 0, 255)); // 跌绿 } return hbr; }A股习惯是红涨绿跌跟欧美市场相反这个颜色逻辑别搞反了。如果追求更精细的控制可以在OnCtlColor里根据当前涨跌值决定返回红色还是绿色而不是固定一个ID一个颜色。4. 我踩过的坑VC6老环境、WinInet超时、字段错位这个项目看起来简单实际运行起来问题不少。下面列几个我真实踩过的坑每个都花了不少时间去定位。第一个坑是VC6.0在Win10上的安装和编译问题。标题里提到的“win10 vc6.0 setup is searching for installed components”我一开始就遇到了安装程序卡死在搜索组件那一步。解决办法是右键setup.exe属性里选“兼容性”标签勾选“以兼容模式运行”下拉选Windows XP (Service Pack 3)同时勾选“以管理员身份运行”。装完之后还需要装VC6 SP6补丁包否则编译器对Win10环境有很多兼容性问题。另外VC6的编辑器不支持代码自动补全这是硬伤装个Visual Assist X老版本能勉强用但别指望它能像VS2019那样智能提示能把函数签名列出来就已经谢天谢地了。第二个坑是WinInet的请求超时设置。默认的OpenURL在对方服务器无响应时可能要等到天荒地老才返回。行情软件最怕卡死我给会话加上了超时控制。思路是先用InternetSetOption设置全局的连接超时、接收超时再创建会话DWORD dwTimeout 3000; // 3秒 InternetSetOption(NULL, INTERNET_OPTION_CONNECT_TIMEOUT, dwTimeout, sizeof(dwTimeout)); InternetSetOption(NULL, INTERNET_OPTION_RECEIVE_TIMEOUT, dwTimeout, sizeof(dwTimeout)); InternetSetOption(NULL, INTERNET_OPTION_SEND_TIMEOUT, dwTimeout, sizeof(dwTimeout));注意InternetSetOption的第一个参数传NULL表示设置的是全局默认值这样所有新创建的WinInet会话都会继承这个超时时间。实测下来断网时OpenURL会在3秒左右抛出异常程序不会假装死机。不过这个设置会影响整个进程的所有网络请求如果你同时还有其他网络调用要评估影响范围。第三个坑是代码字段和返回字段对不上。我最早按别的文章里的索引去解析数据发现取出来的“现价”其实是“昨收价”差一个字段。原因就是腾讯接口不同时期返回的字段数量略有差异而网上文章大多没标明自己抓包抓的是哪一天的版本。稳妥做法是在自己的代码里先打印整段返回报文数清楚字段位置再写解析。我上面那个索引表是最近实测的记录但如果你在别的项目里看到不同的索引也要以实际抓包为准。第四个坑是OpenURL请求多个股票时的性能问题。如果一只股票请求一次循环50只就发50次HTTP请求速度慢且容易被腾讯临时限制。正确做法是把所有代码拼到一个URL里一次请求拉回所有股票数据返回的报文按行分割每行对应一只股票。我实测拉30只股票单次请求比循环请求快了一倍还多而且对接口的负担小很多。第五个坑是UI线程的阻塞问题。如果不是用定时器而是用循环去刷新数据千万别把Sleep写在界面线程里否则整个对话框会无响应大半秒用户体验极差。更好的做法是开一个工作线程拉数据然后PostMessage给主窗口通知更新。VC6里开线程用AfxBeginThread即可注意线程里不要碰任何控件句柄。5. 多股票轮询与日志记录把这个工具做成能用的状态单只股票的行情工具做出来之后总觉得不够实用我继续扩展了三个功能让这个程序从“能运行”变成“能日常使用”。第一是多股票自选列表。用一个ListBox或CMarkList控件列出所有自选股代码点击某一行右侧就显示对应的个股详情。这样每次启动程序只需加载一次自选列表定时器触发时一次请求拉全部数据刷新所有显示。代码结构上没有大改动只是把原来的单股票解析函数循环调用即可。唯一的注意点是处理完一行数据后要立即更新对应的界面控件不要在全部解析完再统一刷新否则用户看着界面会感觉卡顿。第二是记录日志文件。行情工具跑在无人值守的机器上时客户最担心的就是它哪天悄悄罢工。我在程序里加了一个日志输出每次请求、每次解析失败、每次网络异常都会追加写一行到quote_log.txt。这样可以事后排查问题比如“为什么昨天下午行情没更新”这类问题看日志发现是网络断开了。日志文件记得按天切割不然时间长了文件会变得很大。第三是报价提醒。当现价超过预设的阈值时程序弹出一个消息框或者播放一段提示音。实现上是把解析出的价格转成double类型然后和设置好的上下限比较。这里就体现出前面字段解析做扎实的重要性——之前有个同事的提醒程序一直不触发查了半天发现他把“现价”和“均价”的字段弄反了阈值设得再准也没用。这个工具的扩展空间还很大比如对接MySQL数据库做历史行情存档、用图表控件画分时图、甚至通过邮件发送预警通知。腾讯接口本身不限制调用频率5秒拉一次对个人使用完全够用。如果你有更复杂的回测需求建议不要再底层硬啃VC6,直接把取数的这部分逻辑用C写成命令行工具然后交给Python调用两头都省事。我个人的体会是这类老接口配老编译器看似过时其实是稳定性的代名词。腾讯股票接口的文档虽不公开但用它做个人行情监控比很多带鉴权、带加密的新版数据源省心得多。而VC6.0写出来的程序在XP甚至Win2003的机器上都能零依赖运行这种“老而弥坚”的优势在维护存量系统的场景里还是非常有价值的。本文还有配套的精品资源点击获取
返回列表