ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

C++ MFC 网络对战游戏平台系统设计:Socket 通信与 MySQL 实战

C++ MFC 网络对战游戏平台系统设计:Socket 通信与 MySQL 实战 简介这份资源是面向计算机软件专业本科生的毕业设计文档主题为网络对战游戏平台系统设计适合正在准备毕设、需要参考完整项目方案与论文写作框架的学生也可供学习C网络编程与MFC界面开发的初学者借鉴。压缩包内共1个doc文件约682KB为完整毕业论文正文涵盖摘要、绪论、理论技术基础、系统分析与设计、实现与测试等章节。文档以C、MFC与MySql为技术主线重点讲解用户管理、游戏大厅与五子棋对战三大模块的设计思路并涉及TCP/UDP协议与Socket通信机制等知识点。读者可从中获取一份从需求分析到编码实现、再到测试总结的完整毕设参考理解如何用MFC搭建客户端界面、用MySql完成数据持久化并借鉴论文的目录结构与论述方式。目前已有142人学习适合作为同类选题的写作与技术实现范本。1. 网络对战游戏平台系统设计从一份毕设文档到能跑起来的双人对战很多人看到「网络对战游戏平台系统设计」这个题目第一反应是去搜现成的源码包结果下载下来一堆跑不起来的工程或者只有单机版五子棋根本没有网络对战。我当年做类似题目时也踩过这个坑文档里写得天花乱坠什么 C/S 架构、Socket 通信、多线程并发真到动手时连 MFC 的对话框怎么传数据都没搞明白。这个题目的本质是用 C 和 MFC 搭一个带 MySQL 后端的客户端/服务端系统核心功能是让两个玩家通过网络下五子棋附带用户注册登录、战绩记录、房间匹配。它适合计算机软件专业的毕业生也适合想练手 C 网络编程和 MFC 界面开发的初学者。下面我按实际落地的顺序把选型、通信协议、数据库设计、界面实现和联调排错一层层拆开讲。2. 技术选型与开发环境搭建为什么是 C MFC MySQL2.1 选型理由毕设场景下的务实选择这个题目里出现的 C、MFC、MySQL、五子棋其实是一套非常典型的毕设技术栈。C 负责核心逻辑和网络通信MFC 负责 Windows 桌面界面MySQL 负责持久化用户和战绩数据。为什么不用 Qt 或 C#因为很多学校的指导老师指定了 MFC而且 MFC 和 Windows API 结合紧密做 Socket 通信时可以直接用CSocket或 WinSock省去额外依赖。五子棋作为对战载体规则简单、棋盘状态容易用二维数组表示适合把精力集中在网络同步和数据库操作上。网络对战的核心是 C/S 架构服务端监听端口维护多个客户端连接和房间状态客户端负责界面渲染和用户操作把落子坐标发给服务端服务端广播给对手。MySQL 里至少要有users表存账号密码records表存对局结果rooms表可以放在内存里用std::map管理不一定落库。2.2 环境搭建Visual Studio 与 MySQL 的安装配置先装 Visual Studio安装时勾选「使用 C 的桌面开发」和「MFC 支持」。如果你用的是离线安装包注意在单个组件里搜索 MFC把「适用于最新 v143 生成工具的 C MFC」勾上。MySQL 建议装 8.0 社区版安装时记住 root 密码字符集选utf8mb4。装完后用 MySQL Workbench 或 Navicat 建库。-- 创建数据库和用户表 CREATE DATABASE game_platform DEFAULT CHARACTER SET utf8mb4; USE game_platform; CREATE TABLE users ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) UNIQUE NOT NULL, password VARCHAR(64) NOT NULL, -- 存 MD5 值不存明文 win_count INT DEFAULT 0, lose_count INT DEFAULT 0, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE records ( id INT PRIMARY KEY AUTO_INCREMENT, black_player VARCHAR(50), white_player VARCHAR(50), winner VARCHAR(50), end_time DATETIME DEFAULT CURRENT_TIMESTAMP );建表时注意password字段长度要够存 MD5 的 32 位十六进制字符串用VARCHAR(64)留余量。win_count和lose_count设默认值 0避免插入时漏字段导致 NULL。MySQL 8.0 默认认证插件是caching_sha2_password如果客户端连接报错可以改成mysql_native_password但更推荐在连接字符串里指定charsetutf8mb4并升级连接库。2.3 MFC 工程创建与 MySQL 连接库配置在 Visual Studio 里新建「MFC 应用」选择「基于对话框」或「单文档」都行网络对战平台一般用对话框做登录和主界面。工程建好后需要引入 MySQL 的 C 连接库。把 MySQL 安装目录下的include和lib路径加到项目属性里链接libmysql.lib把libmysql.dll复制到输出目录。// MySQLConn.h 封装连接和查询 #pragma once #include mysql.h #include string class MySQLConn { public: MYSQL* conn; MySQLConn() { conn mysql_init(nullptr); } bool connect(const char* host, const char* user, const char* pwd, const char* db) { if (!mysql_real_connect(conn, host, user, pwd, db, 3306, nullptr, 0)) { return false; } mysql_set_character_set(conn, utf8mb4); return true; } // 执行查询返回结果集 MYSQL_RES* query(const std::string sql) { if (mysql_query(conn, sql.c_str())) return nullptr; return mysql_store_result(conn); } ~MySQLConn() { mysql_close(conn); } };这段代码把连接和查询封装成类mysql_set_character_set解决中文乱码。注意mysql_store_result会把结果拉到本地内存用完要mysql_free_result否则反复查询会内存泄漏。连接失败时用mysql_error(conn)打印具体原因常见的是密码错、端口不通或认证插件不匹配。3. 网络通信协议设计用 Socket 同步五子棋落子3.1 通信模型TCP 长连接与自定义消息头网络对战最怕的是落子不同步。我一般用 TCP 长连接服务端为每个客户端开一个线程收到消息后解析再转发。消息格式用「长度 类型 数据」的自定义协议避免粘包。比如前 4 字节是消息总长度第 5 字节是消息类型1 登录、2 落子、3 聊天、4 认输后面是 JSON 或简单字符串。// 发送消息先发长度再发内容 bool sendMsg(SOCKET s, const std::string body, char type) { int len body.size() 1; // 1 给类型字节 char header[4]; header[0] (len 24) 0xFF; header[1] (len 16) 0xFF; header[2] (len 8) 0xFF; header[3] len 0xFF; if (send(s, header, 4, 0) ! 4) return false; if (send(s, type, 1, 0) ! 1) return false; if (send(s, body.c_str(), body.size(), 0) ! (int)body.size()) return false; return true; }长度字段用 4 字节大端序接收端先recv4 字节解析出长度再循环收满剩余字节。这里容易翻车的地方是send返回值不等于请求长度TCP 发送缓冲区满时会部分发送必须循环发送直到发完。接收端同理不能假设一次recv就能拿到完整消息。3.2 落子同步与棋盘状态管理棋盘用 15x15 的二维数组表示0 空、1 黑、2 白。客户端点击棋盘时先判断该位置是否为空、是否轮到自己然后把坐标发给服务端。服务端校验合法性后更新棋盘再广播给房间内所有玩家。客户端收到广播后更新本地棋盘并重绘。// 服务端处理落子消息 void handleMove(int roomId, const std::string player, int x, int y) { Room room rooms[roomId]; if (room.board[x][y] ! 0) return; // 位置已被占 if (room.currentTurn ! player) return; // 没轮到你 int color (player room.blackPlayer) ? 1 : 2; room.board[x][y] color; room.currentTurn (player room.blackPlayer) ? room.whitePlayer : room.blackPlayer; // 广播给双方 std::string msg std::to_string(x) , std::to_string(y) , std::to_string(color); broadcast(roomId, msg, MSG_MOVE); // 判断胜负 if (checkWin(room.board, x, y, color)) { endGame(roomId, player); } }checkWin只需要检查刚落子位置的四个方向横、竖、左斜、右斜是否连成五子不用全盘扫描。currentTurn字段保证轮流落子防止客户端作弊连下。服务端必须做校验不能信任客户端发来的任何数据这是网络对战的基本安全底线。3.3 心跳机制与断线处理TCP 连接可能因为网络波动或客户端异常退出而断开服务端需要心跳包检测。客户端每隔 10 秒发一个类型为 0 的空消息服务端收到后更新该连接的lastActiveTime。另开一个定时线程扫描所有连接超过 30 秒没心跳的就关闭 socket 并通知对手获胜。// 心跳检测线程 void heartbeatCheck() { while (running) { std::this_thread::sleep_for(std::chrono::seconds(10)); auto now std::time(nullptr); for (auto pair : clients) { if (now - pair.second.lastActive 30) { // 通知对手 notifyOpponentOffline(pair.first); closesocket(pair.second.sock); } } } }心跳间隔和超时时间要配合一般超时设为心跳的 3 倍。断线后如果玩家重连可以用用户名重新绑定房间但棋盘状态要保留在服务端内存里不能因为断线就清空。4. MFC 界面实现登录、大厅与棋盘绘制4.1 登录注册对话框与 MySQL 交互MFC 对话框上放两个编辑框用户名、密码和两个按钮登录、注册。点击登录时从编辑框读字符串拼 SQL 查询users表。密码先做 MD5 再比对不要明文存库。void CLoginDlg::OnBnClickedLogin() { CString user, pwd; GetDlgItemText(IDC_EDIT_USER, user); GetDlgItemText(IDC_EDIT_PWD, pwd); std::string md5pwd md5((LPCSTR)pwd); std::string sql SELECT id FROM users WHERE username std::string((LPCSTR)user) AND password md5pwd ; MYSQL_RES* res conn.query(sql); if (res mysql_num_rows(res) 0) { // 登录成功打开大厅 mysql_free_result(res); CDialogEx::OnOK(); } else { MessageBox(_T(用户名或密码错误)); } }这里有个血泪经验MFC 的CString转std::string在 Unicode 字符集下会丢字符最好统一用CT2A宏或直接项目属性改成多字节字符集。SQL 拼接有注入风险毕设可以接受但生产环境必须用预处理语句。4.2 游戏大厅与房间列表大厅对话框用一个CListCtrl显示在线玩家和房间列表。MFC 的列表控件要设置LVS_REPORT风格插入列和行。刷新列表时先DeleteAllItems再重新插入避免重复。双击某个房间进入对战对话框把房间 ID 和对手信息传过去。// 刷新房间列表 void CLobbyDlg::RefreshRooms() { m_list.DeleteAllItems(); int i 0; for (auto room : roomList) { m_list.InsertItem(i, room.roomName.c_str()); m_list.SetItemText(i, 1, room.blackPlayer.c_str()); m_list.SetItemText(i, 2, room.whitePlayer.c_str()); m_list.SetItemText(i, 3, room.status.c_str()); i; } }CListCtrl在数据量大时会闪烁可以加SetWindowLong(m_list.m_hWnd, GWL_EXSTYLE, ...)开双缓冲。列表列宽用SetColumnWidth调整别让文字被截断。4.3 棋盘绘制与鼠标点击落子棋盘用CDC在对话框的OnPaint里画线15 条横线和 15 条竖线间距 30 像素。棋子用Ellipse画圆黑子填充黑色白子填充白色加黑边。鼠标点击时把屏幕坐标转成棋盘索引判断是否在有效范围内。void CGameDlg::OnLButtonDown(UINT nFlags, CPoint point) { int x (point.x - offsetX 15) / 30; int y (point.y - offsetY 15) / 30; if (x 0 || x 15 || y 0 || y 15) return; if (board[x][y] ! 0) return; if (!isMyTurn) return; // 发送落子消息给服务端 sendMove(x, y); CDialogEx::OnLButtonDown(nFlags, point); }坐标转换的offsetX和offsetY是棋盘左上角在对话框客户区的偏移画线和点击必须用同一套偏移量否则会出现「点这里下那里」的玄学问题。重绘时用Invalidate(FALSE)只重画不擦背景减少闪烁。5. 避坑与排查那些让毕设卡三天的典型问题5.1 现象MySQL 连接报错Cant connect to MySQL server on localhost原因通常是 MySQL 服务没启动或者端口被占用或者连接字符串里用了localhost但实际走的是命名管道。解决先net start mysql启动服务再用telnet 127.0.0.1 3306测端口。如果报caching_sha2_password认证错误在 MySQL 里执行ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 你的密码;然后FLUSH PRIVILEGES;。5.2 现象MFC 程序编译报fatal error C1083: 无法打开包括文件: mysql.h原因是没有把 MySQL 的include目录加到项目属性。解决项目右键 → 属性 → C/C → 常规 → 附加包含目录加上C:\Program Files\MySQL\MySQL Server 8.0\include。链接器 → 常规 → 附加库目录加上lib路径链接器 → 输入 → 附加依赖项加上libmysql.lib。还要把libmysql.dll复制到Debug或Release目录否则运行时报找不到 DLL。5.3 现象客户端落子后对手看不到或者棋盘显示错位原因通常是消息发送时用了send但没检查返回值或者接收端一次recv就以为收完了。解决发送端循环发送直到发完接收端先收 4 字节长度再循环收满len字节。棋盘错位检查坐标转换的偏移量是否一致画线和点击用同一个offsetX/offsetY。5.4 现象MFC 字符串内存泄漏程序运行越久越卡原因是在CString和std::string之间反复转换时用了GetBuffer没ReleaseBuffer或者mysql_store_result后没mysql_free_result。解决每次查询后释放结果集字符串转换用CT2A临时对象不要手动GetBuffer。用 Visual Studio 的_CrtDumpMemoryLeaks在调试模式下检查泄漏点。5.5 现象服务端多线程访问共享棋盘数据崩溃原因是多个线程同时读写rooms里的棋盘数组没有加锁。解决用std::mutex保护房间数据的读写或者每个房间一个独立线程房间之间不共享数据。MFC 的CSocket在子线程里用时要注意AfxSocketInit的调用位置最好在主线程初始化。6. 进阶技巧用前缀和优化胜负判断与战绩统计五子棋的胜负判断如果每次全盘扫描15x15 也就 225 个格子性能不是问题。但如果你想在毕设里体现一点算法功底可以用前缀和把四个方向的连子计数降到 O(1)。具体做法是维护四个二维数组rowSum[x][y]表示第 x 行前 y 列的黑子数colSum、diagSum1、diagSum2同理。落子后更新这四个数组判断胜负时直接查区间和是否等于 5。// 前缀和判断横向五连 bool checkRow(int x, int y, int color) { int left std::max(0, y - 4); int right std::min(14, y 4); int sum rowSum[x][right 1] - rowSum[x][left]; return sum 5; }rowSum的更新规则是rowSum[x][y1] rowSum[x][y] (board[x][y] color ? 1 : 0)但注意黑白要分开维护或者用两个数组。这个技巧在毕设答辩时能加分但别为了炫技把简单问题复杂化普通遍历四个方向也完全够用。另一个实用技巧是战绩统计用 MySQL 的UPDATE直接累加不要先查再改避免并发问题UPDATE users SET win_count win_count 1 WHERE username 赢家; UPDATE users SET lose_count lose_count 1 WHERE username 输家;最后说个我自己的习惯每次改完网络协议或数据库字段先写一个最小测试用例用两个控制台客户端互相发消息确认通了再接 MFC 界面。界面和网络一起调出了问题你根本分不清是 Socket 粘包还是 MFC 重绘的锅。希望帮到你。本文还有配套的精品资源点击获取
返回列表