"你经过我每个灿烂时刻,我才真正学会如你般自由~"
项目介绍:
(1) 项目简介
本项目是一款网页版的五子棋对战游戏,其主要支持以下几种功能:
● ⽤⼾管理: 实现⽤⼾注册, ⽤⼾登录、获取⽤⼾信息、⽤⼾天梯分数记录、⽤⼾比赛场次记录等.
● 匹配对战: 实现两个玩家在⽹⻚端根据天梯分数匹配游戏对⼿,并进⾏五⼦棋游戏对战的功能.
● 聊天功能: 实现两个玩家在下棋的同时可以进⾏实时聊天的功能.
(2) 核心技术
● HTTP/WebSocket/Websocket++ ● JsonCpp ● Mysql ● C++11 ● HTML/CSS/JS/AJAX
(3) 环境搭建(Centos-7.6)
① 安装wget⼯具
# root:
yum install wget
② 更换软件源
# 进入到该目录
cd /etc/yum.repos.d
# 配置 yum源
sudo wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-8.repo
# 安装SCL软件源
sudo yum install centos-release-scl-rh centos-release-scl
# 安装epel软件源
sudo yum -y install epel-release
③ 安装其他工具
# 安装lrzsz传输⼯具
sudo yum install lrzsz -y
# 安装⾼版本gcc/g++编译器
sudo yum install devtoolset-8-gcc devtoolset-8-gcc-c++ -y
// 设置开机 时自动启用
echo "source /opt/rh/devtoolset-7/enable" >> ~/.bashrc
// "~" 表示的是当前用户的主目录
source ~/.bashrc
# 安装gdb调试器
sudo yum install gdb -y
# 安装git
sudo yum install git -y
# 安装cmake
sudo yum install cmake
# 安装boost库
sudo yum install boost-devel.x86_64 -y
# 安装Jsoncpp库
sudo yum install jsoncpp-devel -y
④ 安装Mysql数据库服务及开发包
# 获取mysql官⽅yum源
wget http://repo.mysql.com/mysql57-community-release-el7-10.noarch.rpm
# 安装mysql官⽅yum源
sudo rpm -ivh mysql57-community-release-el7-10.noarch.rpm
# 安装Mysql数据库服务
sudo install -y mysql-community-server
# 安装Mysql开发包
sudo yum install -y mysql-community-devel
# 启动Mysql
systemctl start mysqld.service
# 设置开机项
systemctl enable mysqld.service
# 停止服务
service mysqld stop
过期Key:
rpm --import https://repo.mysql.com/RPM-GPG-KEY-mysql-2022
查看临时密码:
首次登录时,会在MySQL里的日志文件中生成一个临时密码:
# 也可以使用这个命令直接获取
sudo grep 'temporary password' /var/log/mysqld.log
# 查看mysql 密码策略
show variables like 'validate_password%';
# 设置密码强度
set global validate_password_policy = 0 (密码强度)
# 设置密码长度
set global validate_password_length = 1 (长度)
# 修改密码 才能进行后面的操作
alter user 'root'@'%' Identified by 'XXXXXXX';
如果你已经忘记密码了,你可以进入 /etc/my.cnf文件中设置:
[mysqld]: skip-grant-tables
配置Mysql文件:
● 配置 '/etc/my.cnf' 字符集
[client]
default-character-set=utf8
[mysql]
default-character-set=utf8
[mysqld]
character-set-server=utf8
⑤ 安装Websocketpp库
# 远方拉取库
git clone https://github.com/zaphoyd/websocketpp.git
# 进入websocketpp目录
cd /websocketpp mkdir build
# 在build目录下:
cmake -DCMAKE_INSTALL_PREFIEX=/usr ../
# 生成临时文件都会放在 makefile文件
# 安装websocketpp
sudo make install
验证是否安装成功:
进入/websocketpp/examples
关于什么是WebSocket可以看看这篇文章:
我们来谈谈websocket_websocket 线程_RNGWGzZs的博客-CSDN博客
前置工具包:
该项目牵涉到数据库的常用操作,数据序列化反序列化,文件读写等操作,所以我们先对这些操作进行一个封装,方便之后接口的调用。
mysql:
其中包含了创建初始化 mysql连接句柄,以及sql语句的执行和句柄销毁。
class mysql_util
{
public:
// 用创建句柄+初始化+连接
static MYSQL *mysql_create(
const std::string host,
const std::string username,
const std::string password,
const std::string db_name,
uint16_t port = 3306)
{
// 初始化
MYSQL *mysql = mysql_init(nullptr);
if (mysql == nullptr)
{
ERR_LOG("mysql init failed");
return nullptr;
}
// 库连接
if (mysql_real_connect(mysql, host.c_str(), username.c_str(), password.c_str(), db_name.c_str(), port, nullptr, 0) == nullptr)
{
ERR_LOG("mysql connect server failed:%s", mysql_error(mysql));
mysql_close(mysql);
return nullptr;
}
// 设置字符集
if (mysql_set_character_set(mysql, "utf8") != 0)
{
ERR_LOG("mysql charset failed:%s", mysql_error(mysql));
mysql_close(mysql);
return nullptr;
}
return mysql;
}
// 执行sql
static bool mysql_exec(MYSQL *mysql, const std::string &sql)
{
int ret = mysql_query(mysql, sql.c_str());
if (ret != 0)
{
ERR_LOG("SQL:%s ERROR:%s", sql.c_str(), mysql_error(mysql));
return false;
}
return true;
}
// 销毁句柄释放资源
static void mysql_release(MYSQL *mysql)
{
if (mysql == nullptr)
return;
mysql_close(mysql);
}
};
Json工具:
包含传输数据的序列化和反序列化过程。
class json_util
{
public:
static bool serialize(const Json::Value &value, std::string &res)
{
Json::StreamWriterBuilder swb;
std::unique_ptr<Json::StreamWriter> sw(swb.newStreamWriter());
std::stringstream ss;
int ret = sw->write(value, &ss);
if (ret != 0)
{
ERR_LOG("json serialization faild!");
return false;
}
res = ss.str();
return true;
}
static bool unserialize(const std::string &str, Json::Value &value)
{
Json::CharReaderBuilder crb;
std::unique_ptr<Json::CharReader> cb(crb.newCharReader());
bool ret = cb->parse(str.c_str(), str.c_str() + str.size(), &value, nullptr);
if (ret == false)
{
ERR_LOG("json unserialize failed!");
return false;
}
return true;
}
};
字符串分割\读文件:
class string_util
{
public:
static int split(const std::string &in, const std::string &sep, std::vector<std::string> &arry)
{
arry.clear();
// idx:表示切分字符串的位置
size_t idx = 0;
while (idx < in.size())
{
size_t pos = in.find(sep, idx);
if (pos == std::string::npos)
{
// 虽然原字符串没有sep 但 pos-in != 0
arry.push_back(in.substr(idx));
break;
}
// "出现多个相连sep: 33,,,,"
if (pos == idx) {
idx += sep.size();
continue;
}
res.push_back(src.substr(idx, pos - idx));
idx = pos + sep.size();
}
return arry.size();
}
};
class file_util
{
public:
static bool read_file(const std::string filename, std::string &body)
{
std::fstream fs(filename, std::ios::in | std::ios::binary);
if (!fs.is_open())
{
ERR_LOG("file open faild:%s", filename.c_str());
return false;
}
// 使用偏移量的方法 读取整个文件
// seekg:设置读取偏移指针的位置
// tellg:返回当前position
fs.seekg(0, std::ios::end);
body.resize(fs.tellg());
fs.seekg(std::ios::beg); // 回到文件开头
fs.read(&body[0], body.size());
// good:Check whether state of stream is good
if (fs.good() == false)
{
ERR_LOG("read file content faild:%s", filename.c_str());
fs.close();
return false;
}
fs.close();
return true;
}
};
功能模块:
用户管理模块: 
用户管理模块中,我们需要为玩家存储他们的各项身份信息,比如:id,玩家名,天梯分数,总场数,胜场数……这些数据都是存储在磁盘上的,由强大的Mysql进行管理。
另外,为了取得这些数据,我们实现一个类来完成,user_table类就是为了获取数据库中的数据的。
在线⽤⼾管理模块:
在线⽤⼾管理,是对于当前游戏⼤厅和游戏房间中的⽤⼾进⾏管理。它需要承担对在线用户以及正在进行五子棋对战的玩家进行管理。并通过一定的策略对玩家进行 "保活" 查询。
游戏房间管理: 
这是该项目功能的主逻辑。游戏房间模块是在两个玩家成功匹配后生成的。该房间需要支持游戏玩法和实时聊天的功能。
Session管理:
什么是session?
http是一种无状态的短连接协议。所谓无状态,就是不知道这条信息或者这份数据是谁发送的。我们可以试想一下,两个正在进行游戏的玩家,会在一张棋盘上落子,客户端点击发送黑白棋子,但在计算机看无非就是一串01序列,压根不知道这条消息是属于谁的。
由此,服务器为每个⽤⼾浏览器创建⼀个会话对象(session),在需要保存⽤⼾数据时,服务器程序可以把⽤⼾数据写到⽤⼾浏览器独占的session中,当⽤⼾使⽤浏览器访问该程序时,就会携带这个sessio信息,该程序可以从⽤⼾的session中读取该⽤⼾的历史数据,识别该连接对应的⽤⼾,并为⽤⼾提供服务。
在这里我们需要设计一个session类,并且这个session类不能一直存在,当用户结束对局或者用户下线等,该session对象就需要被清理掉。
匹配队列实现:
五⼦棋对战的玩家匹配是根据⾃⼰的天梯分数进⾏匹配的,实现玩家匹配的思想⾮常简单,为不同的档次设计各⾃的匹配队列,当⼀个队列中的玩家数量⼤于 等于2的时候,则意味着同⼀档次中,有2个及以上的⼈要进⾏实战匹配,则出队队列中的前两个⽤⼾,并为之创建游戏房间准备对战。
剩余的模块属于前端知识,诸如登录页面、注册页面、游戏大厅等等,也就不过多赘述了。
项目实现:
日志打印:
#ifndef __MY_LOG__
#define __MY_LOG__
#include <iostream>
#include <stdio.h>
#include <time.h>
#include <pth