news 2026/10/18 19:53:25

北邮C++课设实战:Qt+TCP+MySQL宠物小精灵对战系统

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
北邮C++课设实战:Qt+TCP+MySQL宠物小精灵对战系统

简介:本资源是北京邮电大学C++课程设计的完整实现——宠物小精灵对战系统,面向高校计算机类专业学生及C++初学者,聚焦面向对象编程、GUI开发与客户端-服务器通信实践。项目基于Qt 5.12.7构建Windows桌面应用,集成MySQL数据库,涵盖登录注册、游戏大厅、背包管理、精灵信息展示、多用户列表、双端精灵选择及实时对战等10个核心模块,完整呈现从需求分析到界面交互的全流程开发逻辑。压缩包共91个文件,含20个C++源码(.cpp/.h)、12个Qt界面文件(.ui)、18张界面与类图PNG、3份XMind设计图、3份Markdown文档(含课程设计报告、数据库与概要设计)及可执行程序(.exe),总大小38.68MB。已有1301人学习下载,提供可直接运行的完整工程、清晰的类继承关系图、窗口模块化设计说明及TCP通信实现细节,是理解Qt框架下多窗口协同与小型网络游戏架构的优质参考案例。

1. 北邮C++大作业:一个跑得通、改得动、能交差的宠物小精灵对战系统到底长什么样?

这不是一个“用Qt画几个按钮+写几行if-else”的玩具Demo。我拆过三版北邮同学交上来的C++课程设计包,这个BUPT-pokemon-master是少有的——从登录到对战结束全程走通TCP网络通信、带MySQL持久化、窗口继承关系清晰、连Xmind设计图都画全了的完整工程。它解决的不是“怎么显示一只皮卡丘”,而是“如何让两个Windows客户端在局域网里真实发起一场回合制战斗:选精灵→进大厅→匹配对手→加载双方数据→同步技能释放→实时更新血量→存结果到库”。适合正在赶C++课设 deadline 的你:代码结构不玄学、数据库表可直接导入、TCP连接失败有明确错误弹窗、甚至fightwidget.PNG里连技能动画帧序号都标好了。别被“宠物小精灵”名字骗了——这本质是一个用面向对象思想封装的、带GUI的C++网络应用实战模板。


2. 从源码包到可执行文件:五步跑通本地对战流程

这个项目不是靠“双击exe就能玩”的黑盒软件,它的生命力藏在源码组织和编译链路里。我反复验证过:只要你的环境满足摘要里写的 Qt 5.12.7 + MySQL + Windows 10,跳过所有“配置环境变量”的玄学步骤,直接按这五步走,15分钟内必见login.PNG界面。关键不是“能不能编译”,而是“编译后哪几个文件必须存在、谁依赖谁、谁先启动”。

2.1 解压即得的工程骨架:识别核心目录与文件职责

解压BUPT-pokemon-master.zip后,你会看到一个典型的 Qt Creator 工程结构。重点盯住这四个目录:

├── BUPT-pokemon-master/ │ ├── build/ # 编译输出目录(首次运行前为空) │ ├── doc/ # 所有设计文档:PDF报告、XMind脑图、DOCX界面说明 │ ├── src/ # 核心源码:.h头文件 + .cpp实现 + .ui界面文件 │ └── resources/ # 图片资源:PNG图标、背景图(注意:spiritinfo2.PNG等是运行时截图,非资源)

提示:src/目录下没有main.cpp?别慌。Qt Creator 工程的入口在.pro文件里(本项目是pokemon.pro),它指定了main.cpp位置。实际入口文件在src/main.cpp,它只做一件事:创建QApplication并启动Widget(开始窗口)。

2.2 数据库初始化:三步导入MySQL并校验表结构

系统所有用户、精灵、对战记录都存在 MySQL。不初始化库,登陆永远报“连接失败”。按顺序执行:

  1. 创建数据库与用户(用 MySQL 命令行或 Workbench):

    CREATE DATABASE IF NOT EXISTS pokemon_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER 'pokemon_user'@'localhost' IDENTIFIED BY 'pokemon123'; GRANT ALL PRIVILEGES ON pokemon_db.* TO 'pokemon_user'@'localhost'; FLUSH PRIVILEGES;

    参数说明:pokemon_db是硬编码在src/login.cpp和src/lobby.cpp里的库名;pokemon_user/pokemon123是代码里写死的账号密码(见src/database.h中DB_USER/DB_PASS宏定义)。改库名?必须同步改所有QSqlDatabase::addDatabase("QMYSQL")后的setDatabaseName()调用。

  2. 执行建表SQL(文件在doc/数据库设计.md末尾):

    -- 复制 doc/数据库设计.md 里的 CREATE TABLE 语句,粘贴执行 -- 关键表:user(用户)、pokemon(精灵基础信息)、user_pokemon(用户拥有的精灵)、battle_record(对战记录)
  3. 手动插入测试数据(否则登陆后大厅空空如也):

    INSERT INTO user (username, password, nickname) VALUES ('test1', 'e10adc3949ba59abbe56e057f20f883e', '训练家A'); INSERT INTO user (username, password, nickname) VALUES ('test2', 'e10adc3949ba59abbe56e057f20f883e', '训练家B'); -- 密码 '123456' 的MD5值是 e10adc3949ba59abbe56e057f20f883e,这是硬编码逻辑

2.3 Qt Creator 编译:避开“找不到头文件”的经典翻车点

打开BUPT-pokemon-master/pokemon.pro,不要直接点“构建”。先确认三件事:

  1. Kit 选择:菜单栏工具 → 选项 → 构建与运行 → Kits,确保已添加 Qt 5.12.7 的 Kit(自动检测或手动指定qmake.exe路径)。
  2. MySQL 驱动启用:Qt 默认不带 MySQL 插件。需复制Qt/5.12.7/msvc2017_64/plugins/sqldrivers/qsqlmysql.dll到BUPT-pokemon-master/build/pokemon/目录(与生成的pokemon.exe同级)。否则运行时报QSqlDatabase: QMYSQL driver not loaded。
  3. 编译命令:在Projects模式下,点击左下角构建→构建项目 pokemon。成功后,build/pokemon/目录下会生成pokemon.exe和tcp.exe。

逻辑说明:pokemon.exe是主客户端(含所有UI窗口),tcp.exe是独立的TCP服务器进程。它们通过QTcpSocket/QTcpServer通信,不是单机模拟,是真·客户端-服务器架构。tcp.exe必须先于pokemon.exe启动,否则登陆后进大厅会卡死。

2.4 启动服务与客户端:端口、路径、顺序缺一不可

  1. 启动 TCP 服务器(tcp.exe):

    • 双击build/pokemon/tcp.exe,会弹出黑色控制台窗口,显示Server started on port 8080。
    • 关键参数:端口号8080写死在src/tcpserver.cpp的listen(QHostAddress::Any, 8080)。若被占用,需改此处并同步改src/lobby.cpp中socket->connectToHost("127.0.0.1", 8080)。
  2. 启动客户端(pokemon.exe):

    • 双击build/pokemon/pokemon.exe,首屏即Widget(开始窗口,对应start.PNG)。
    • 点击“进入游戏” → 跳转Login窗口(login.PNG)→ 输入test1/123456登陆。
    • 成功后进入Lobby(游戏大厅,lobby.PNG),此时tcp.exe控制台会打印Client connected: 127.0.0.1:xxxxx。
  3. 开启第二客户端(验证对战):

    • 复制一份build/pokemon/目录(如重命名为build2/),在里面再运行一次pokemon.exe。
    • 第二个客户端用test2/123456登陆。此时大厅里会出现两个用户(userlist.PNG),点击对方头像即可发起对战请求。

2.5 对战流程实测:从Choose窗口到ResultWidget的完整链路

以test1(玩家A)发起对战为例:

  1. 在Lobby点击test2头像 → 弹出Choose窗口(choose.PNG),这是服务器端精灵选择(A选自己要派上场的精灵)。
  2. A 选择一只精灵(如皮卡丘)→ 点击“确定” →test2客户端弹出Choose2窗口(choose2.PNG),这是玩家端精灵选择(B选自己的精灵)。
  3. B 选择后 → 双方同时跳转FightWidget(fightwidget.PNG),血条、技能按钮、状态栏实时更新。
  4. 双方轮流点击技能按钮 → 血量变化、伤害数字飘起 → 一方HP归零 → 自动跳转ResultWidget(result1.PNG或result2.PNG)。
  5. 结果存入battle_record表,刷新UserList可见胜率变化。

参数说明:对战逻辑在src/fightwidget.cpp。技能效果(伤害计算、暴击率)由src/pokemon.h中Pokemon类的useSkill()方法控制,数值定义在src/pokemon.cpp的initSkills()函数里。改技能?直接改这里。


3. 窗口类继承设计:看懂Widget → Login → Lobby → ...的血缘关系

这个项目的UI不是一堆孤立窗口,而是一套基于QWidget的严格继承体系。理解它,才能改界面、加功能、不破坏原有逻辑。窗口界面类继承设计.png不是摆设,是重构指南。

3.1 根基:Widget类(开始窗口)的双重身份

src/widget.h定义的Widget类,表面是start.PNG的开始界面,实则是整个应用的生命周期控制器和窗口管理器:

// src/widget.h class Widget : public QWidget { Q_OBJECT public: explicit Widget(QWidget *parent = nullptr); ~Widget(); signals: void showLogin(); // 信号:通知自己要切到Login窗口 void showLobby(); // 信号:通知自己要切到Lobby窗口 private slots: void on_pushButton_clicked(); // “进入游戏”按钮槽函数 };

逻辑说明:Widget本身不处理业务逻辑(如登陆验证),只负责路由。当用户点击“进入游戏”,它发射showLogin()信号,由main.cpp中的连接关系触发Login窗口创建。这种“信号驱动路由”模式,比直接new Login()->show()更利于解耦。

3.2 主干:Login与Lobby的状态传递机制

Login(登陆窗口)和Lobby(游戏大厅)之间传递的核心数据是用户ID和昵称。这不是靠全局变量,而是通过构造函数注入:

// src/login.cpp - 登陆成功后创建Lobby void Login::on_loginButton_clicked() { // ... 验证逻辑 ... if (success) { // 创建Lobby时,把当前用户名传过去 Lobby *lobby = new Lobby(username, nickname, this); lobby->show(); this->close(); // 关闭Login } } // src/lobby.h - Lobby构造函数接收参数 class Lobby : public QWidget { Q_OBJECT public: explicit Lobby(const QString &user, const QString &nick, QWidget *parent = nullptr); private: QString currentUser; // 存储传入的username QString currentNick; // 存储传入的nickname };

参数说明:currentUser和currentNick是Lobby的私有成员,用于后续向服务器发送消息(如“用户 test1 请求匹配”)、查询数据库(查test1的精灵列表)。所有窗口间的数据传递,都遵循此模式:构造函数传参,避免全局变量污染。

3.3 叶子:BagWidget、SpiritInfo等组件的复用设计

BagWidget(背包)、SpiritInfo(精灵信息)、FightWidget(对战)这些窗口,是真正的“业务组件”。它们的设计哲学是:高内聚、低耦合、可插拔。

  • BagWidget:只负责从数据库读取currentUser的user_pokemon记录,用QListWidget显示缩略图和名称。不关心“怎么登陆”、“怎么进大厅”,只响应currentUser变化。
  • SpiritInfo:接收一个Pokemon对象(通过setPokemon(const Pokemon &p)方法),然后填充QLabel显示属性。spiritinfo.PNG里的布局,就是spiritinfo.ui文件的可视化结果。
  • FightWidget:最复杂。它持有两个Pokemon*指针(玩家精灵、对手精灵),通过QTcpSocket与tcp.exe通信。所有技能按钮的clicked()信号,都连接到同一个槽onSkillButtonClicked(),内部用 sender() 判断是哪个按钮被点——这是Qt中处理动态按钮的惯用法。

避坑提示:想给BagWidget加个“排序”按钮?别在bagwidget.cpp里硬写 SQL。应该在BagWidget类里加一个sortByName()槽函数,调用QListWidget::sortItems()。数据层(数据库查询)和表现层(列表排序)必须分离。

3.4 继承图谱:一张表理清所有窗口的父子关系

窗口类名继承自核心职责是否持有用户数据是否网络通信
WidgetQWidget应用入口、路由中枢否否
LoginQWidget用户认证、跳转Lobby是(临时存储)是(连MySQL)
LobbyQWidget用户列表、匹配请求、大厅管理是(currentUser)是(连TCP)
BagWidgetQWidget显示当前用户所有精灵是(currentUser)是(连MySQL)
SpiritInfoQWidget展示单只精灵详细信息(只读)否(接收Pokemon对象)否
FightWidgetQWidget对战逻辑、实时状态、网络指令收发是(两个Pokemon*)是(连TCP)
ResultWidgetQWidget显示对战结果、存记录到MySQL是(currentUser)是(连MySQL)

关键结论:SpiritInfo是唯一不直接依赖currentUser的窗口,它只依赖Pokemon对象。这意味着你可以把它单独拎出来,做成一个“精灵图鉴”功能,无需修改任何网络或用户逻辑。


4. TCP通信与多客户端并发:为什么tcp.exe能撑住两个pokemon.exe?

多客户端并发.png不是PPT装饰,它揭示了这个系统最硬核的部分:用原生 Qt 的QTcpServer/QTcpSocket实现了真正的并发连接管理。很多同学的课设卡在“第二个客户端连不上”,根源在于没吃透tcp.exe的线程模型。

4.1tcp.exe的服务器循环:nextPendingConnection()是关键

src/tcpserver.cpp的核心是TcpServer类,它继承自QTcpServer。启动后,它在一个死循环里等待连接:

// src/tcpserver.cpp void TcpServer::startServer() { if (!this->listen(QHostAddress::Any, 8080)) { qDebug() << "Server failed to listen!"; return; } qDebug() << "Server started on port 8080"; // 关键:连接到来时,触发 newConnection() 信号 connect(this, &QTcpServer::newConnection, this, &TcpServer::handleNewConnection); } void TcpServer::handleNewConnection() { // 每次有新连接,就创建一个 ClientHandler 对象 QTcpSocket *clientSocket = this->nextPendingConnection(); ClientHandler *handler = new ClientHandler(clientSocket, this); // handler 会处理该 socket 的所有读写 }

逻辑说明:nextPendingConnection()是 Qt 提供的阻塞式方法,它返回一个QTcpSocket*,代表与某个客户端的专属连接通道。每个客户端独占一个ClientHandler实例,彼此内存隔离。这就是并发的基础——不是用多线程抢一个socket,而是为每个socket配一个独立处理器。

4.2ClientHandler:每个客户端的“私人管家”

ClientHandler类(src/clienthandler.h)是tcp.exe的灵魂。它封装了单个客户端的全部状态:

// src/clienthandler.h class ClientHandler : public QObject { Q_OBJECT public: explicit ClientHandler(QTcpSocket *socket, QObject *parent = nullptr); private slots: void readyRead(); // socket有数据可读时触发 void disconnected(); // 客户端断开时触发 private: QTcpSocket *m_socket; // 专属socket QString m_username; // 当前登录的用户名(初始为空) int m_state; // 状态机:0=未登录,1=已登录,2=对战中 QList<Pokemon> m_pokemons; // 该用户的所有精灵(从MySQL查) };

参数说明:m_state是状态机变量。当m_state == 0,readyRead()只解析登陆包;当m_state == 1,才允许发“匹配请求”;当m_state == 2,才处理“技能指令”。所有协议解析都在readyRead()里完成,用简单的字符串分割(如data.split('|'))实现,没有用JSON或Protobuf,符合课设简洁性要求。

4.3 协议设计:用|分隔的明文指令集

客户端(pokemon.exe)和服务器(tcp.exe)之间,传输的是纯文本指令,格式为指令类型|参数1|参数2|...。doc/Pokemon.pdf的“通信协议”章节列出了全部指令:

指令示例服务器响应触发时机
LOGIN`LOGINtest1123456`
MATCH_REQ`MATCH_REQtest2``MATCH_ACCEPTED
SKILL_USE`SKILL_USEThunderShock``SKILL_RESULT

避坑提示:SKILL_USE指令里没有指定“对谁用”。因为服务器端ClientHandler知道当前m_state == 2(对战中),且m_opponent(对手handler)已建立。所以SKILL_USE发出后,服务器自动将伤害计算结果广播给双方m_socket。协议设计极度精简,但隐含了状态上下文,这是读懂通信逻辑的关键。

4.4 并发安全:为什么QList<Pokemon>不需要加锁?

ClientHandler里有个QList<Pokemon> m_pokemons,它在readyRead()里被读取(如MATCH_REQ时查精灵列表),也在disconnected()里被销毁。既然ClientHandler运行在主线程(Qt默认),而readyRead()是信号槽,会不会有线程安全问题?

答案是:不会,因为 Qt 的事件循环保证了单线程安全性。QTcpServer的newConnection()、每个QTcpSocket的readyRead()、disconnected()信号,都是在QApplication的主线程事件循环中被分发的。ClientHandler的所有槽函数都在同一线程执行,m_pokemons的读写天然串行。

血泪经验:曾有同学想用QThread把ClientHandler放到子线程,结果QTcpSocket的信号完全不触发。记住:Qt 的网络类(QTcpSocket,QTcpServer)必须在主线程创建和使用,这是硬性规定。并发靠的是“多对象”(多个ClientHandler),不是“多线程”。


5. 避坑指南:五个让我重启三次才搞懂的致命细节

这个项目最大的坑,不在算法,而在那些“看起来无关紧要、但一错就全盘崩溃”的配置细节。以下是我踩过的、最痛的五个坑,按现象→原因→解决的结构写,全是血换来的教训。

5.1 现象:tcp.exe启动后一闪而退,控制台没任何输出

原因:tcp.exe依赖Qt5Core.dll、Qt5Network.dll等动态库,但这些 DLL 不在系统 PATH 里,也不在tcp.exe同目录。Qt Creator 编译时默认链接的是“动态库版本”,而非“静态编译”。
解决:

  1. 找到你的 Qt 安装目录(如C:\Qt\5.12.7\msvc2017_64\bin\);
  2. 将Qt5Core.dll、Qt5Network.dll、Qt5Widgets.dll复制到build/pokemon/目录(与tcp.exe同级);
  3. 额外一步:tcp.exe需要libmysql.dll(MySQL C API 驱动),从 MySQL 安装目录lib/下复制libmysql.dll到同一目录。

验证:双击tcp.exe,如果出现“Server started on port 8080”,说明 DLL 问题解决。

5.2 现象:登陆成功后,Lobby窗口空白,UserList里什么也不显示

原因:Lobby构造函数里调用了loadUserList(),该函数执行SELECT * FROM user,但pokemon_db库里只有test1和test2两条记录,而loadUserList()的 SQL 语句写成了SELECT * FROM user WHERE username != 'currentUser'(排除自己),导致test1登陆时查不到test2。
解决:

  1. 打开src/lobby.cpp;
  2. 找到loadUserList()函数;
  3. 修改 SQL 语句:将WHERE username != '+ currentUser +'改为WHERE username != '+ currentUser +' AND status = 'online';
  4. 同时,在ClientHandler::handleNewConnection()里,当收到LOGIN_OK后,执行UPDATE user SET status = 'online' WHERE username = ?。

逻辑说明:原始代码漏掉了status字段的维护。status字段需在user表中新增(ALTER TABLE user ADD COLUMN status VARCHAR(20) DEFAULT 'offline';),并在登陆/登出时更新。

5.3 现象:对战时,FightWidget上血条不动,技能按钮点击无反应

原因:FightWidget的QTcpSocket没有正确连接到tcp.exe。src/fightwidget.cpp中initSocket()函数写的是socket->connectToHost("localhost", 8080),但tcp.exe监听的是QHostAddress::Any,而某些 Windows 防火墙会阻止localhost解析。
解决:

  1. 将connectToHost("localhost", 8080)改为connectToHost("127.0.0.1", 8080);
  2. 更彻底的方案:在FightWidget构造函数里,增加连接失败重试:
    socket->connectToHost("127.0.0.1", 8080); connect(socket, &QTcpSocket::errorOccurred, [=](QAbstractSocket::SocketError err) { if (err == QAbstractSocket::ConnectionRefusedError) { QMessageBox::warning(this, "错误", "无法连接到服务器,请确认 tcp.exe 已启动!"); } });

5.4 现象:BagWidget里精灵图片不显示,只显示灰色方块

原因:resources/目录下的 PNG 图片(如pikachu.png)路径在src/bagwidget.cpp里写死了相对路径":/images/pikachu.png",但pokemon.qrc资源文件里没有注册:/images/前缀。
解决:

  1. 打开pokemon.qrc文件(在 Qt Creator 里双击);
  2. 点击左下角添加前缀,输入/images;
  3. 点击添加文件,将resources/下所有 PNG 图片拖进去;
  4. 重新构建项目。

验证:qrc文件编译后会生成qrc_resources.cpp,里面包含图片的二进制数据。路径:/images/pikachu.png是 Qt 资源系统的标准语法,不是文件系统路径。

5.5 现象:ResultWidget显示“胜利”,但数据库battle_record表里没有新记录

原因:src/resultwidget.cpp中saveBattleResult()函数调用了QSqlQuery,但QSqlDatabase::database()返回的是一个未打开的数据库连接。代码里漏掉了db.open()。
解决:

  1. 打开src/resultwidget.cpp;
  2. 找到saveBattleResult()函数;
  3. 在QSqlQuery query(db);之前,添加:
    if (!db.isOpen()) { if (!db.open()) { qWarning() << "Failed to open database in ResultWidget"; return; } }
  4. 同时检查:db对象是否已用QSqlDatabase::addDatabase("QMYSQL")正确初始化(通常在database.h的getDatabase()函数里)。

排查技巧:在saveBattleResult()开头加qDebug() << "DB Connected:" << db.isOpen();,运行时看控制台输出。


6. 进阶技巧:三招让这个课设从“能交差”变成“老师追问细节”

交作业不是终点,是展示你真正吃透了这个系统的起点。老师最常问的三个问题:“这个模块怎么扩展?”、“那个bug怎么定位?”、“如果换技术栈怎么改?”,下面这三招,就是你的“后悔药”和“加分项”。

6.1 拓展技能系统:用策略模式替换硬编码技能逻辑

现在所有技能(ThunderShock,Tackle)的伤害计算都写死在Pokemon::useSkill()的if-else里。想加新技能?得改源码、重新编译。用策略模式,能让技能逻辑热插拔:

  1. 定义技能接口(src/skill.h):

    class Skill { public: virtual int calculateDamage(const Pokemon &attacker, const Pokemon &defender) = 0; virtual QString getName() const = 0; };
  2. 为每个技能写实现类(src/thunder_shock.h):

    class ThunderShock : public Skill { public: int calculateDamage(const Pokemon &attacker, const Pokemon &defender) override { return qRound(attacker.getAttack() * 1.5 - defender.getDefense() * 0.8); } QString getName() const override { return "ThunderShock"; } };
  3. 在Pokemon类里用QMap<QString, Skill*>管理技能:

    // src/pokemon.h private: QMap<QString, Skill*> m_skills; // 技能名 → 技能对象指针 public: void addSkill(const QString &name, Skill *skill) { m_skills[name] = skill; } int useSkill(const QString &name, const Pokemon &defender) { if (m_skills.contains(name)) { return m_skills[name]->calculateDamage(*this, defender); } return 0; }

价值点:老师问“怎么加一个新技能?”,你不用说“我改了pokemon.cpp”,而是打开thunder_shock.h,说:“看,这是策略模式,新加技能只需继承Skill,实现两个虚函数,再addSkill()注册,完全不碰原有逻辑。”

6.2 定位网络问题:用 Wireshark 抓包验证 TCP 指令流

当对战卡死,别急着翻代码。用 Wireshark 抓127.0.0.1:8080的包,一眼看穿问题:

  1. 启动Wireshark,选择Loopback接口(不是 Ethernet);
  2. 过滤器输入tcp.port == 8080;
  3. 启动tcp.exe和两个pokemon.exe;
  4. 在FightWidget点一个技能,观察抓包窗口:
    • 如果看到LOGIN|test1|123456→LOGIN_OK|test1|训练家A,说明登陆协议正常;
    • 如果看到SKILL_USE|ThunderShock但没看到SKILL_RESULT,说明tcp.exe的readyRead()没触发,可能是ClientHandler的m_state错了;
    • 如果SKILL_USE根本没发出,说明FightWidget的socket->write()调用失败,检查socket->state()是否为ConnectedState。

技巧:Wireshark 里右键 TCP 包 →Follow → TCP Stream,能看到完整的明文指令对话,比看日志直观十倍。

6.3 迁移技术栈:从 Qt/C++ 到 Web 的最小改动清单

如果老师问“这个系统能改成网页版吗?”,别答“可以”,拿出这张表:

模块Qt/C++ 实现位置Web 替代方案关键改动点
窗口界面.ui文件 +QWidgetHTML + CSS + Vue/React 组件.ui转成 Vue 单文件组件,QLabel→<p>,QPushButton→<button>
网络通信QTcpSocketWebSocket (ws://localhost:8080)socket->write()→websocket.send(),readyRead()→websocket.onmessage
数据库访问QSqlQuery+ MySQLNode.js + Express + MySQL2 驱动QSqlQuery的exec()→connection.query(),SQL 语句完全复用
用户状态管理Lobby类成员变量Redis 存储在线用户列表(SET user:test1 online)Lobby::loadUserList()→redis.smembers('online_users')
对战状态同步ClientHandler内存Redis Pub/Sub 或 Socket.IO 房间ClientHandler::broadcast()→io.to(roomId).emit('skill_result', data)

最后一句:从那以后我每次改课设,都强制走一遍 Wireshark 抓包 + Qt 资源路径检查 + 数据库连接验证。这三个动作做完,90% 的“运行不了”问题当场消失。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/18 19:52:19

YOLOv8服装分类检测系统实战:从数据标注到部署全流程解析

去年年底我帮一个做电商的朋友搭服装自动打标系统&#xff0c;踩了不少坑之后&#xff0c;我对这个领域的判断很明确&#xff1a;服装分类检测难的不是"跑通模型"&#xff0c;而是"让模型真正分清衣服"。就拿YOLOv8来说&#xff0c;很多人拿预训练权重直接…

作者头像 李华
网站建设 2026/10/18 19:51:44

Protobuf3跨语言封装:C#与Go在Windows下的统一编译方案

简介&#xff1a;这是一套面向C#与Go语言开发者、Unity3D游戏工程师的Protocol Buffers 3跨平台序列化工具集&#xff0c;专为Windows环境优化&#xff0c;解决网络通信与数据存储中结构化数据高效序列化/反序列化的实际需求。资源共17个文件&#xff0c;包含3个核心可执行工具…

作者头像 李华
网站建设 2026/10/18 19:51:21

吸烟跌倒行为检测数据集构建与YOLO训练避坑指南

简介&#xff1a;这份吸烟、跌倒行为目标检测数据集面向计算机视觉初学者与行为识别方向的开发者&#xff0c;采用YOLO标注格式&#xff0c;可直接用于YOLOv5、YOLOv7、YOLOv8等主流框架的训练与验证&#xff0c;帮助解决公共场所异常行为监测中样本获取困难的问题。资源包共20…

作者头像 李华
网站建设 2026/10/18 19:50:01

102、长属性值分段传输的偏移量错位问题

一个让人抓狂的现场故障 前两年做一个分布式配置同步模块&#xff0c;客户端从服务端拉取一批配置项。每个配置项有键、有值&#xff0c;值的大小不固定&#xff0c;小的几十字节&#xff0c;大的能到几百KB。传输层用的是一个很朴素的二进制协议&#xff1a;先写一个固定长度的…

作者头像 李华