一个 Linux C++ 服务端 + Qt5 客户端 的证券撮合交易模拟器,完成于 2022 年 7 月。 服务端框架层改编自 TinyWebServer,业务撮合层为原创。 功能需求见 readme.txt。
TransClientUi/ Qt 5.13.2 客户端
widget.cpp 主界面:连接、登录、四个功能入口
tradewidget 买入/卖出(价格两位小数、数量正整数的前端正则校验)
revwidget 撤单(支持部分撤单)
subwidget 行情订阅(拉取订单簿快照)
network.* 静态单例 QTcpSocket,同步收发
TransServer/ Linux C++ 服务端(端口 1316,MySQL 库 SecTransDb)
server/ epoll Reactor(ET/LT 可切)+ 线程池 + 小根堆定时器 —— TinyWebServer 框架层
pool/ RAII MySQL 连接池
log/ 阻塞队列异步日志
http/connection.cpp ★ 业务核心:撮合、撤单、订单簿查询(416 行)
buffer/ 应用层读写缓冲
通信协议(伪 HTTP):用户名\r\n + 操作符\r\n + body\r\n\r\n;body 参数以 ; 分隔。
操作符五种:buy / sell / revoke / userPublish / getSupportSec。
核心思路:MySQL 表 transOrder 即订单簿。
- 新订单到达 → SQL 查询对手方向、价格可成交的挂单(价格优先排序 + 时间优先,浮点容差 1e-3);
- while 循环逐笔吃单,成交部分
DELETE/UPDATE,吃不完的剩余量INSERT回表; - 成交回报拼接为文本,返回给当前请求方;
- 撤单:回查本人挂单列表,按序号撤(支持部分撤单);
userPublish:把某证券的订单簿整表返回 —— 拉取式快照,非推送。
内置 20 只模拟证券(银行600000600009 / 700000700009),服务端硬编码。
相对原需求的差距:
- 对手方收不到成交回报:成交信息只返回给当前下单方,被吃单的一方无感知;
- 无实时行情推送、无逐笔成交流水表:readme 中的发布/订阅需求实际是请求-响应式拉取;
- 撮合非独立线程:只在新订单到达时触发一次,"订单队列"即 MySQL 表。
实现层面的坑:
SqlConnRAII(&sql, ...)构造临时对象立刻析构,连接在业务开始前已归还池子,全程悬空使用(沿袭自 TinyWebServer 原版写法);- 无事务、无锁、无唯一订单号:撮合"查-改-写"非原子,同用户同价同量挂单会被误伤,撤单序号依赖跨请求的成员变量缓存;
- SQL 直接拼接无参数化(服务端无防注入);价格以
float入库; - 合法性校验仅存在于客户端正则,服务端不复核。
2022 年学习 epoll 网络编程后的应用化练手作,框架层(Reactor/线程池/连接池/定时器/日志)跑通,业务层为简易撮合。 仅作学习记录与简历素材,不建议作为生产参考。