在线五子棋对决
- 一. 项目介绍及链接
- 二. 项目结构设计
- 项目模块划分
- 业务处理模块的子模块划分
- 项目流程图
- 玩家流程图
- 服务器流程图
- 三. 数据管理模块
- 数据库设计
- 创建 user_table 类
- 四. 在线用户管理模块
- 五. 游戏房间管理模块
- 游戏房间类实现
- 游戏房间管理类实现
- 六. Session 管理模块
- Session 类实现
- Session 管理类实现
- 七. 五子棋对战玩家匹配管理模块
- 匹配队列类实现
- 匹配队列管理类实现
- 八. 整合封装服务器模块
- 通信接口设计 (Restful风格)
- 静态资源请求
- 注册用户请求
- 用户登录请求
- 获取用户信息请求
- 切换 WebSocket 通信协议请求 (进入游戏大厅)
- 对战匹配请求
- 停止对战匹配请求
- 切换 WebSocket 通信协议请求 (进入游戏房间)
- 下棋请求
- 聊天请求
- 服务器类实现
- 九. 客户端开发
- 注册页面:register.html
- 登入页面:login.html
- 游戏大厅页面:game_hall.html
- 游戏房间页面:game_room.html
一. 项目介绍及链接
本项目主要实现一个网页版的五子棋对战游戏,其主要支持以下核心功能:
- 用户管理:实现用户注册,用户登录、获取用户信息、用户天梯分数记录、用户比赛场次记录等。
- 匹配对战:实现两个玩家在网页端根据天梯分数匹配游戏对手,并进行五子棋游戏对战的功能。
- 聊天功能:实现两个玩家在下棋的同时可以进行实时聊天的功能。
二. 项目结构设计
项目模块划分
项目模块划分三个大模块来进行:
- 数据管理模块:基于 MySQL 数据库进行用户数据的管理。
- 前端界面模块:基于 JS 实现前端页面 (注册,登录,游戏大厅,游戏房间) 的动态控制以及与服务器的通信。
- 业务处理模块:搭建 WebSocket 服务器与客户端进行通信,接收请求并进行业务处理。
项目要实现的是一个在线五子棋对战服务器,提供用户通过浏览器进行用户注册,登录,以及实时匹配,对战,聊天等功能。而如果要实现这些功能,那么就需要对业务处理模块再次进行细分为多个模块来实现各个功能。
业务处理模块的子模块划分
- 网络通信模块:基于 websocketpp 库实现 HTTP 和 WebSocket 服务器的搭建,提供网络通信功能。
- 会话管理模块:对客户端的连接进行 Cookie 和 Session管理,实现 HTTP 短连接时客户端身份识别功能。
- 在线管理模块:对进入游戏大厅与游戏房间中用户进行管理,提供用户是否在线以及获取用户连接的功能。
- 房间管理模块:为匹配成功的用户创建对战房间,提供实时的五子棋对战与聊天业务功能。
- 用户匹配模块:根据天梯分数不同进行不同层次的玩家匹配,为匹配成功的玩家创建房间并加入房间。
项目流程图
玩家流程图
服务器流程图
三. 数据管理模块
数据管理模块主要负责对于数据库中数据进行统一的增删改查管理,其他模块要对数据操作都必须通过数据管理模块完成。
数据库设计
创建 user 表,用来表示用户信息及积分信息。
- 用户信息:实现登录、注册、游戏对战数据管理等功能 (用户 id,用户名,密码,总场数,胜场数)
- 积分信息:实现匹配功能 (游戏分数)
drop database if exists gobang;
create database if not exists gobang;
use gobang;
create table if not exists user(id int primary key auto_increment,username varchar(32) unique key not null,password varchar(128) not null,score int,total_count int,win_count int
);
验证数据库是否创建成功:
创建 user_table 类
数据库中有可能存在很多张表,每张表中管理的数据又有不同,要进行的数据操作也各不相同,因此我们可以为每一张表中的数据操作都设计一个类,通过类实例化的对象来访问这张数据库表中的数据,这样的话当我们要访问哪张表的时候,使用哪个类实例化的对象即可。
创建 user_table 类,该类的作用是负责通过 MySQL 接口管理用户数据,主要提供了四个方法:
- select_by_name:根据用户名查找用户信息,用于实现登录功能。
- insert:新增用户,用户实现注册功能。
- login:登录验证,并获取完整的用户信息。
- win:用于给获胜玩家修改分数。
- lose:用户给失败玩家修改分数。
位于 db.hpp 文件
存在密码不满足要求,也就是密码太简单了,需要设置密码等级,如下:
四. 在线用户管理模块
- 管理的两类用户:是对于 “进入游戏大厅” 和 “进入游戏房间” 的用户。
- 原因:进入游戏大厅和进入游戏房间的用户才会建立 WebSocket 长连接。
- 管理:将用户 id 和对应的客户端 WebSocket 长链接关联起来。
- 作用:当一个用户发送了消息 (实时聊天/下棋消息),可以找房间中的其它用户,在在线用户管理模块中,找到这个用户对应的 WebSocket 长连接,然后将消息发送给指定的用户,模块的两个功能如下:
- 通过用户 id 找到用户的 WebSocket 连接进而实现向指定用户的客户端推送消息,WebSocket 连接关闭时,会自动在在线用户管理模块中删除自己的信息。
- 可以通过在线用户管理模块判断一个用户是否在线,或者判断用户是否已经掉线。
位于 online.hpp 文件
五. 游戏房间管理模块
游戏房间类实现
- 对匹配成功的玩家创建房间,建立起一个小范围的玩家之间的关联关系,房间里的一个玩家产生的动作会广播给房间里的其它用户。
- 因为房间有可能会有很多,因此需要将这些房间管理起来以便于对于房间生命周期的控制。
- 房间中产生的动作:下棋和聊天都要广播给房间里的其它用户。
- 实现的两个部分:房间的设计、房间管理的设计。
- 房间的设计:房间的ID、房间的状态 (正在游戏/游戏结束,决定玩家退出时谁胜利)、房间中用户的数量 (决定了房间什么时候销毁)、白棋玩家ID、黑棋玩家ID、用户信息表的句柄 (当玩家胜利/失败时更新用户数据)、棋盘信息 (二维数组)、在线用户的管理句柄 (广播数据时需要)
位于 room.hpp 文件
游戏房间管理类实现
需要管理的数据:
- 数据管理模块句柄。
- 在线用户管理模块句柄。
- 房间 ID 分配计数器。
- 用户 ID 映射房间 ID 的哈希表。
- 房间 ID 映射房间信息的哈希表。
- 互斥锁。
功能:
- 创建房间:两个玩家对战匹配完成了,为他们创建一个房间,需要传入两个玩家的用户 ID
- 查找房间:通过房间 ID 查找房间信息,通过用户 ID 查找所在房间信息。
- 销毁房间:通过房间 ID 销毁,房间中所有用户都退出了,销毁房间。
位于 room.hpp 文件
六. Session 管理模块
在 Web 开发中,HTTP 协议是一种无状态短链接的协议,这就导致一个客户端连接到服务器上之后,服务器不知道当前的连接对应的是哪个用户,也不知道客户端是否登录成功,这时候为客户端提供所有服务是不合理的。第一次HTTP请求登入使用的是密码,接下来的HTTP请求,由于无状态短链接不知道用户是谁,需要重新登入,这是不合理的。
因此,服务器为每个用户浏览器创建一个会话对象 (Session 对象),注意:一个浏览器独占一个 Session 对象 (默认情况下)。因此,在需要保存用户数据时,服务器程序可以把用户数据写到用户浏览器独占的 Session 中,当用户使用浏览器访问其它程序时,其它程序可以从用户的 Session 中取出该用户的数据,识别该连接对应的用户,并为用户提供服务。
Session 工作原理:
使用 Cookie 和 Session 进行 HTTP 在短连接通信的情况下进行用户状态管理,但是这个服务器上管理的 Session 都会有过期时间,超过时间就会将对应的 Session 删除,每次客户端与服务器的通信都需要延长 Session 的过期时间。
Session 类实现
简单设计 session 类,但是 session 对象不能一直存在,这是一种资源泄漏,因此需要使用定时器对每个创建的 session 对象进行定时销毁 (一个客户端连接断开后,一段时间内都没有重新连接则销毁 session),成员对象如下:
- ssid:会话id
- uid:用户id
- status:用户状态 (登入/未登入)
- timer_ptr:保存当前 session 对应的定时销毁任务。
位于 session.hpp 文件
Session 管理类实现
session 管理:
- 创建一个新的 session
- 通过 ssid 获取 session
- 通过 ssid 判断 session 是否存在。
- 销毁 session
- 为 session 设置过期时间,过期后 session 被销毁。
位于 session.hpp 文件
七. 五子棋对战玩家匹配管理模块
匹配队列类实现
五子棋对战的玩家匹配是根据自己的天梯分数进行匹配,而服务器中将玩家天梯分数分为三个档次:
- 青铜:天梯分数小于 2000 分。
- 白银:天梯分数介于 2000~3000 分之间。
- 黄金:天梯分数大于 3000 分。
实现玩家匹配的思想非常简单,为不同的档次设计各自的匹配队列,当一个队列中的玩家数量大于等于2的时候,则意味着同一档次中,有2个及以上的人要进行实战匹配,则出队队列中的前两个用户,相当于队首2个玩家匹配成功,这时候为其创建房间,并将两个用户信息加入房间中。
位于 matcher.hpp 文件
匹配队列管理类实现
匹配管理:
- 三个不同玩家水平的队列。
- 三个线程分别对三个队列中的玩家进行匹配。
- 房间管理模块的句柄:为匹配成功的玩家创建房间。
- 在线用户管理模块句柄:为匹配成功的玩家添加到在线用户管理中。
- 数据库管理模块用户表句柄:通过玩家 id 查看玩家的天梯分数,进行玩家匹配。
功能接口:
- 添加用户到匹配队列。
- 从匹配队列移除玩家。
- 线程入口函数:判断指定队列人数是否大于2,出队两个玩家,创建房间,将两个玩家添加到房间中,向两个玩家发送对战匹配成功的消息。
位于 matcher.hpp 文件
八. 整合封装服务器模块
通信接口设计 (Restful风格)
静态资源请求
- 静态资源页面:在后台服务器上就是个 html/css/js 文件。
- 注册页面请求。
- 登入页面请求。
- 游戏大厅页面请求。
- 游戏房间页面请求。
- 静态资源请求的处理:就是将文件中的内容发送给客户端。
# 注册页面请求
请求:GET /register.html HTTP/1.1
响应:
HTTP/1.1 200 OK
Content-Length: xxx
Content-Type: text/htmlregister.html文件内容数据# 登录页面请求
请求:GET /login.html HTTP/1.1
# 大厅页面请求
请求:GET /game_hall.html HTTP/1.1
# 房间页面请求
请求:GET /game_room.html HTTP/1.1
注册用户请求
// 请求
POST /reg HTTP/1.1
Content-Type: application/json{"username":"zhangsan", "password":"123456"}// 成功时的响应
HTTP/1.1 200 OK
Content-Type: application/json{"result":true, "reason": "注册用户成功"}// 失败时的响应
HTTP/1.1 400 Bad Request
Content-Type: application/json{"result":false, "reason": "用户名已经被占用"}
用户登录请求
// 请求
POST /login HTTP/1.1
Content-Type: application/json{"username":"zhangsan", "password":"123456"}// 成功时的响应
HTTP/1.1 200 OK
Content-Type: application/json
Set-Cookie: 123456{"result":true, "reason": "登入成功"}// 失败时的响应
HTTP/1.1 400 Bad Request
Content-Type: application/json{"result":false, "reason": "用户名/密码错误"}
获取用户信息请求
// 请求
POST /info HTTP/1.1
Content-Type: application/json// 成功时的响应
HTTP/1.1 200 OK
Content-Type: application/json{"username":"xzy", "score":1000, "total_count":4, "win_count":2}// 失败时的响应
HTTP/1.1 400 Bad Request
Content-Type: application/json{"result":false, "reason": "找不到用户信息信息, 请重新登入"}
切换 WebSocket 通信协议请求 (进入游戏大厅)
// 成功响应
{"optype": "hall_ready","result": true;
}
// 失败响应
{"optype": "hall_ready","reason": "玩家重复登入""result": false;
}
对战匹配请求
// 客户端:开始匹配玩家的信息
{"optype": "match_start","uid": 1
}// 服务器:开始匹配玩家正确的信息
{"optype": "match_start","result": true
}
// 服务器:开始匹配玩家错误的信息
{"optype": "match_start","result": false,"reason": "具体原因...."
}// 服务器:匹配玩家成功的信息
{"optype": "match_success","result": true
}
停止对战匹配请求
// 客户端:停止匹配玩家的信息
{"optype": "match_stop""uid": 1
}// 服务器:停止匹配玩家正确的信息
{"optype": "match_stop","result": true
}
// 服务器:停止匹配玩家错误的信息
{"optype": "match_stop","result": false,"reason": "具体原因...."
}
切换 WebSocket 通信协议请求 (进入游戏房间)
// 成功响应
{"optype": "room_ready","room_id": 222,"uid": 1,"white_id": 1,"black_id": 2,"result": true;
}
// 失败响应
{"optype": "room_ready","reason": "玩家重复登入""result": false;
}
下棋请求
// 请求
{"optype": "put_chess","room_id": 222,"uid": 1, // 下棋用户的id"row": 3,"col": 2
}// 失败响应
{"optype": "put_chess","result": false"reason": "走棋失败具体原因...."
}
// 成功响应
{"optype": "put_chess","room_id": 222,"uid": 1, // 下棋用户的id"row": 3,"col": 2,"result": true,"reason": "五星连珠,战无敌","winner": 0 // 0 代表没有分出胜负,具体的数字代表获胜用户的id
}
聊天请求
// 请求
{"optype": "chat","room_id": 222,"uid": 1,"message": "快点吧,等的花都谢了"
}// 失败响应
{"optype": "chat","result": false"reason": "消息中包含敏感词汇,无法发送"
}
// 成功响应
{"optype": "chat","result": true,"room_id": 222,"uid": 1,"message": "快点吧,等的花都谢了"
}
服务器类实现
服务器模块:对当前所实现的所有模块的一个整合,并进行服务器搭建的一个模块,最终封装实现出一个 gobang_server 的服务器模块类,向外提供搭建五子棋对战服务器的接口,通过实例化的对象可以简便的完成服务器的搭建。
服务器的整合实现:
- 网络通信接口的设计:
- 收到一个什么格式的数据,代表了什么样的请求,应该给与什么样的业务处理以及响应。
- 开始搭建服务器:
- 搭建 websocket 服务器,实现网络通信。
- 针对各种不同的请求进行不同的业务处理。
客户端所存在的请求:
- 客户端从服务器获取一个 “注册” 页面。
- 客户端给服务器发送一个 “注册” 请求 (提交用户名和密码)
- 客户端从服务器获取一个 “登入” 页面。
- 客户端给服务器发送一个 “登入” 请求 (提交用户名和密码)
- 客户端从服务器获取一个 “游戏大厅” 页面。
- 客户端给服务器发送一个 “获取个人信息” 请求 (展示个人信息)
- 客户端给服务器发送一个 “切换 WebSocket 通信协议” 的请求 (建立游戏大厅长连接)
- 客户端给服务器发送一个 “对战匹配” 的请求 (匹配玩家)
- 客户端给服务器发送一个 “停止对战匹配” 的请求 (停止匹配玩家)
- 客户端从服务器获取一个 “游戏房间” 页面。
- 客户端给服务器发送一个 “切换 WebSocket 通信协议” 的请求 (建立游戏房间长连接)
- 客户端给服务器发送一个 “下棋” 的请求 (下棋)
- 客户端给服务器发送一个 “聊天” 的请求 (聊天)
- 客户端从服务器获取一个 “游戏大厅” 页面 (游戏结束返回游戏大厅)
九. 客户端开发
注册页面:register.html
通过 43.143.27.66:8080/register.html 访问该网页时,返回给客户端一个注册界面,用户输入用户名密码后点击按钮,自动通过 ajax 向后台发送用户注册请求,并将用户名和密码存储到数据库中,最后直接跳转到登入页面。
登入页面:login.html
通过 43.143.27.66:8080/login.html 访问该网页时,返回给客户端一个登入界面,用户输入用户名密码后点击按钮,自动通过 ajax 向后台发送用户登入请求,并直接跳转到游戏大厅页面。
游戏大厅页面:game_hall.html
进入游戏大厅 websocket 长连接页面后,通过点击开始匹配按钮,网页向服务器发送对战匹配请求,再次点击按钮,网页向服务器发送取消对战匹配请求,当存在两个人匹配对战的话,则玩家匹配成功,直接跳转到游戏房间页面。
游戏房间页面:game_room.html
进入游戏房间 websocket 长连接页面后,就可以开始实时下棋和聊天了。