一、项目描述:
实现功能
用户的注册、登录,个人答题训练、排位赛(网络匹配对战)、排位系统
1、该项目采用C/S结构基于TCP/IP协议,运用Libevent网络库和线程池进行搭建服务器框架。在传输数据中采用Json进行轻量级的数据格式转换。使用spdlog日志库来记录操作信息、出错信息和提醒消息。
2、使用MySQL数据库来存储注册账号信息,创建一个数据库专门用于存储题库数据
3、单人训练模式:从题库中随机抽取10道题目进行答题,答对继续作答,答错退出答题界面,最后计算的分
4、答题时间设置为10秒,超过答题时间记作答题错误,最终得分与答题时间成正比
5、排位系统:设置用户段位星级登记,只有在上下偏差5颗星的范围内才会匹配成功,如果没有匹配成功,进入到等待队列。有匹配成功的,则将玩家移除等待队列进行答题比赛,答题结束后比较玩家得分情况进行段位改动
二、段位介绍
青铜3 1颗星 到 星耀1
青铜 3个段位 1--3 每个段位3颗星
白银 3个段位 1--3 每个段位3颗星
黄金 4个段位 1--4 每个段位4颗星
铂金 4个段位 1--4 每个段位4颗星
钻石 5个段位 1--5 每个段位5颗星
星耀 5个段位 1--5 每个段位5颗星
三、效果展示
四、代码展示
客户端代码(windows下QT开发)
brainstorm.h
#ifndef BRAINSTORM_H
#define BRAINSTORM_H
#include <QDialog>
#include "communicate.h"
#include <QTimer>
namespace Ui {
class BrainStorm;
}
class BrainStorm : public QDialog
{
Q_OBJECT
public:
explicit BrainStorm( Communicate *com,QJsonObject &json,QWidget *parent = 0);
~BrainStorm();
private:
void SingelgetQuestion(); //个人训练从服务器获取题目
void SingelSetQuestion(); //单人训练设置题目
void SingelAnswerQuestion(int select);//单人训练答题
void setEnemyScore();//设置对手得分
void setSelfScore();//设置自己得分
void setRankQuestion(); //设置排位题目
void rankAnswerQuestion(int select);//排位回答问题
void setRankResult(QJsonObject &json);//rank结果
void showEnquire(QJsonObject &json);
private slots:
void on_singlepushButton_clicked();
void on_singl_backButton_clicked();
void on_single_startButton_clicked();
void receiveSingelQuestion(QJsonObject json);//单人训练,从服务器接收到题目
void singelTimerOut();
void on_singel_selectButton1_clicked();
void on_singel_selectButton2_clicked();
void on_singel_selectButton3_clicked();
void on_singel_selectButton4_clicked();
void on_backpushButton_clicked();
void on_rankpushButton_clicked();
void Rank(QJsonObject json);//接受排位
void rankTimerOut();
void on_rankselect1Button_clicked();
void on_rankselect2Button_clicked();
void on_rankselect3Button_clicked();
void on_rankselect4Button_clicked();
void on_backpushButton_2_clicked();
void on_rankchartpushButton_clicked();
void on_backpushButton_3_clicked();
private:
Ui::BrainStorm *ui;
Communicate *_com; //负责和服务器通信
QJsonObject _singelQuestion;
int _currentSingelQuestion; //当前回答哪一个问题的下标
QTimer _singelTimer; //个人训练定时器
int _singelSec;//个人训练答题时间
int _singelScore;//个人训练得分
//rank部分
QJsonObject _rankQuestion; //rank的题目
QString _enemyName; //对手的名字
QString _enemyRank; //对手的段位
int _enemyScore; //对手的得分
int _rankSec; //rank计时
int _myScore; //自己的得分
QString _userName; //自己的用户名
QString _userRank; //自己的段位
int _currentRankQuestion;//当前回答问题的下标
int _enemyRankQuestion;//对手当前问题的下标
QTimer _rankTimer; //排位赛定时器
};
#endif // BRAINSTORM_H
communicate.h
#ifndef COMMUNICATE_H
#define COMMUNICATE_H
#include <QObject>
#include <QTcpSocket>
#include <QJsonObject>
#include <QJsonDocument>
#include <QHostAddress>
#include "../commom.h"
#include <QJsonArray>
class Communicate : public QObject
{
Q_OBJECT
public:
explicit Communicate(QObject *parent = nullptr);
void writeData(const QJsonObject &json);
signals:
void rstResult(int );
void login(QJsonObject json);
void receiveSingelQuestion(QJsonObject json);
void Rank(QJsonObject json);
void Exit(QJsonObject json);
public slots:
void readData();
private:
QTcpSocket s;
};
#endif // COMMUNICATE_H
login.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include "communicate.h"
#include <QMessageBox>
#include "brainstorm.h"
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
~Widget();
private slots:
void on_pushButton_clicked();
void on_pushButton_2_clicked();
void login( QJsonObject json);
private:
Ui::Widget *ui;
Communicate *_com; //通信类指针,负责和服务器通信
};
#endif // WIDGET_H
register.h
#ifndef REGISTER_H
#define REGISTER_H
#include <QDialog>
#include <QJsonObject>
#include "communicate.h"
#include "../commom.h"
#include <QMessageBox>
#include "../commom.h"
namespace Ui {
class Register;
}
class Register : public QDialog
{
Q_OBJECT
public:
explicit Register(Communicate *com,QWidget *parent = 0);
~Register();
private slots:
void on_registerpushButton_clicked();
void on_returnpushButton_clicked();
void rstResult(int );
private:
Ui::Register *ui;
Communicate *_com;
};
#endif // REGISTER_H
brainstorm.cpp
#include "brainstorm.h"
#include "ui_brainstorm.h"
BrainStorm::BrainStorm(Communicate *com,QJsonObject &json,QWidget *parent) :
QDialog(parent),
ui(new Ui::BrainStorm)
{
ui->setupUi(this);
_com = com;
_userName = json["userName"].toString();
_userRank = json["rank"].toString();
connect(&_singelTimer,SIGNAL(timeout()),this,SLOT(singelTimerOut()));
connect(_com,SIGNAL(receiveSingelQuestion(QJsonObject)),this,SLOT(receiveSingelQuestion(QJsonObject)));
connect(_com,SIGNAL(Rank(QJsonObject)),this,SLOT(Rank(QJsonObject)));
connect(_com,SIGNAL(Exit(QJsonObject)),this,SLOT(Rank(QJsonObject)));
connect(&_rankTimer,SIGNAL(timeout()),this,SLOT(rankTimerOut()));
}
BrainStorm::~BrainStorm()
{
delete ui;
}
//个人训练从服务器获取题目
void BrainStorm::SingelgetQuestion()
{
QJsonObject json;
json["cmd"] = SINGEL_GETQUESTION;
_com->writeData(json);
}
//进入个人训练
void BrainStorm::on_singlepushButton_clicked()
{
ui->stackedWidget->setCurrentWidget(ui->singleMenu);
}
void BrainStorm::on_singl_backButton_clicked()
{
ui->stackedWidget->setCurrentWidget(ui->mianMenu);
}
//单人训练设置问题
void BrainStorm::SingelSetQuestion()
{
ui->singel_questionlabel->setText(_singelQuestion["question"].toArray().at(_currentSingelQuestion).toString());
ui->singel_selectButton1->setText(_singelQuestion["selection1"].toArray().at(_currentSingelQuestion).toString());
ui->singel_selectButton2->setText(_singelQuestion["selection2"].toArray().at(_currentSingelQuestion).toString());
ui->singel_selectButton3->setText(_singelQuestion["selection3"].toArray().at(_currentSingelQuestion).toString());
ui->singel_selectButton4->setText(_singelQuestion["selection4"].toArray().at(_currentSingelQuestion).toString());
}
//接收题目
void BrainStorm::receiveSingelQuestion(QJsonObject json)
{
_singelQuestion = json;
_currentSingelQuestion = 0;
_singelSec = 10;
_singelScore = 0;
ui->lcdNumber->display(_singelSec); //数码管显示时间
SingelSetQuestion();
//开启定时器
_singelTimer.start(1000);
ui->stackedWidget->setCurrentWidget(ui->singel_running); //跳转到答题页面
}
//开始答题
void BrainStorm::on_single_startButton_clicked()
{
SingelgetQuestion();
}
//个人训练计时
void BrainStorm::singelTimerOut()
{
--_singelSec;
if(_singelSec == 0)
{
ui->singel_resultlabel->setText("回答错误");
QString str = QString("本次得分: %1").arg(_singelScore);
ui->singel_scorelabel->setText(str);
_singelTimer.stop();
ui->stackedWidget->setCurrentWidget(ui->singel_score);
}
ui->lcdNumber->display(_singelSec);
}
void BrainStorm::SingelAnswerQuestion(int select)
{
//回答正确
if(select == _singelQuestion["answer"].toArray().at(_currentSingelQuestion).toString().toInt())
{
_singelScore += 20*_singelSec;
_currentSingelQuestion++;
SingelSetQuestion(); //设置下一题
_singelSec = 10;
_singelTimer.stop();
ui->lcdNumber->display(_singelSec);
_singelTimer.start(1000);
}
else //回答错误
{
ui->singel_resultlabel->setText("回答错误");
QString str = QString("本次得分: %1").arg(_singelScore);
ui->singel_scorelabel->setText(str);
ui->stackedWidget->setCurrentWidget(ui->singel_score);
_singelTimer.stop();
}
//题目结束
if(_currentSingelQuestion == QUESTION_NUM)
{
ui->singel_resultlabel->setText("恭喜全部答对");
QString str = QString("本次得分: %1").arg(_singelScore);
ui->singel_scorelabel->setText(str);
ui->stackedWidget->setCurrentWidget(ui->singel_score);
_singelTimer.stop();
}
}
void BrainStorm::on_singel_selectButton1_clicked()
{
SingelAnswerQuestion(1);
}
void BrainStorm::on_singel_selectButton2_clicked()
{
SingelAnswerQuestion(2);
}
void BrainStorm::on_singel_selectButton3_clicked()
{
SingelAnswerQuestion(3);
}
void BrainStorm::on_singel_selectButton4_clicked()
{
SingelAnswerQuestion(4);
}
void BrainStorm::on_backpushButton_clicked()
{
ui->stackedWidget->setCurrentWidget(ui->singleMenu);
}
//rank
void BrainStorm::on_rankpushButton_clicked()
{
QJsonObject json;
json["cmd"] = RANK;
_com->writeData(json);
//跳转到等待页面
ui->stackedWidget->setCurrentWidget(ui->rank_wait);
}
void BrainStorm::setEnemyScore()
{
QString str = QString("%1(%2):%3").arg(_enemyName,-5).arg(_enemyRank).arg(_enemyScore);
ui->enemyStatuslabel->setText(str);
}
void BrainStorm::setSelfScore()
{
QString str = QString("%1(%2):%3").arg(_userName,-5).arg(_userRank).arg(_myScore);
ui->selfStatuslabel->setText(str);
}
void BrainStorm::setRankQuestion()
{
ui->rankQuestion->setText(_rankQuestion["question"].toArray().at(_currentRankQuestion).toString());
ui->rankselect1Button->setText(_rankQuestion["selection1"].toArray().at(_currentRankQuestion).toString());
ui->rankselect2Button->setText(_rankQuestion["selection2"].toArray().at(_currentRankQuestion).toString());
ui->rankselect3Button->setText(_rankQuestion["selection3"].toArray().at(_currentRankQuestion).toString());
ui->rankselect4Button->setText(_rankQuestion["selection4"].toArray().at(_currentRankQuestion).toString());
ui->rankselect1Button->setEnabled(true);
ui->rankselect2Button->setEnabled(true);
ui->rankselect3Button->setEnabled(true);
ui->rankselect4Button->setEnabled(true);//设置完题目将四个按钮设置为可使用状态
ui->rankselect1Button->setStyleSheet("");
ui->rankselect2Button->setStyleSheet("");
ui->rankselect3Button->setStyleSheet("");
ui->rankselect4Button->setStyleSheet("");//设置完题目将按钮清空
if(_currentRankQuestion == QUESTION_NUM)
{
_rankTimer.stop();
//将结果发送给服务器
QJsonObject json;
json["cmd"] = RANKRESULT;
json["score"] = _myScore;
json["enemyScore"] = _enemyScore;
json["enemyName"] = _enemyName;
_com->writeData(json);
}
}
void BrainStorm::rankAnswerQuestion(int select)
{
//答对计算得分
if(select == _rankQuestion["answer"].toArray().at(_currentRankQuestion).toString().toInt())
{
_myScore += 20 * _rankSec;
}
setSelfScore();
_currentRankQuestion++;
//判断是否跳转到下一题
if(_currentRankQuestion == _enemyRankQuestion)
{
_rankSec = 10;
_rankTimer.stop();
ui->lcdNumber_2->display(_rankSec);
_rankTimer.start(1000);
setRankQuestion();
}
QJsonObject json;
json["cmd"] = ANSWER;
json["enemyName"] = _enemyName;
json["score"] = _myScore;
json["questionId"] = _currentRankQuestion;
_com->writeData(json);
}
void BrainStorm::setRankResult(QJsonObject &json)
{
QString newRank = json["newRank"].toString();
if(_myScore == _enemyScore)
{
ui->rank_resultlabel->setText("平局");
}
else if(_myScore > _enemyScore)
{
ui->rank_resultlabel->setText("胜利");
}
else if(_myScore < _enemyScore)
{
ui->rank_resultlabel->setText("失败");
}
QString str = QString("%1-------->%2").arg(_userRank).arg(newRank);//段位变化
ui->newRanklabel->setText(str);
_userRank = newRank;
ui->stackedWidget->setCurrentWidget(ui->rank_result);
}
void BrainStorm::showEnquire(QJsonObject &json)
{
ui->rankchartBrowser->clear();
qDebug() << "1111111111111111\n";
QJsonObject json1 = json["order"].toObject();
auto nameJson = json1["name"].toArray();
auto rankJson = json1["rank"].toArray();
int len = json1["name"].toArray().size();
for(int i = 0;i < len;i++)
{
ui->rankchartBrowser->append(nameJson.at(i).toString() + ": " + rankJson.at(i).toString());
}
}
void BrainStorm::Rank(QJsonObject json)
{
int cmd = json["cmd"].toInt();
if(cmd == RANK)
{
_rankQuestion = json["question"].toObject();
_enemyName = json["enemyName"].toString();
_enemyRank = json["enemyRank"].toString();
_enemyScore = json["enemyScore"].toInt();
_enemyRankQuestion = 0;
_rankSec = 10;
_myScore = 0;
ui->lcdNumber_2->display(_rankSec); //数码管显示时间
_currentRankQuestion = 0;
//设置对手的得分
setEnemyScore();
setSelfScore();
setRankQuestion();
_rankTimer.start(1000);
ui->stackedWidget->setCurrentWidget(ui->rank_running);
}
else if(cmd == ANSWER)
{
_enemyScore = json["enemyScore"].toInt();
_enemyRankQuestion = json["enemyQuestionId"].toInt();
setEnemyScore();
if(_currentRankQuestion == _enemyRankQuestion)
{
_rankSec = 10;
_rankTimer.stop();
ui->lcdNumber_2->display(_rankSec);
_rankTimer.start(1000);
setRankQuestion();
}
}
else if(cmd == EXIT)
{
_rankTimer.stop();
QJsonObject json;
json["cmd"] = RANKRESULT;
json["enemyscore"] = 0;
json["score"] = 1;
_com->writeData(json);
}
else if(cmd == RANKRESULT)
{
setRankResult(json);
}
else if(cmd == ENQUIRE)
{
showEnquire(json);
}
}
void BrainStorm::rankTimerOut()
{
_rankSec--;
if(_rankSec == 0)
{
if(ui->rankselect1Button->isEnabled())
{
_currentRankQuestion++;
}
setRankQuestion();
_rankSec = 10;
}
ui->lcdNumber_2->display(_rankSec);
}
void BrainStorm::on_rankselect1Button_clicked()
{
ui->rankselect1Button->setStyleSheet("background-color: rgb(170, 170, 255)");
ui->rankselect1Button->setEnabled(false);
ui->rankselect2Button->setEnabled(false);
ui->rankselect3Button->setEnabled(false);
ui->rankselect4Button->setEnabled(false);
rankAnswerQuestion(1);
}
void BrainStorm::on_rankselect2Button_clicked()
{
ui->rankselect2Button->setStyleSheet("background-color: rgb(170, 170, 255)");
ui->rankselect1Button->setEnabled(false);
ui->rankselect2Button->setEnabled(false);
ui->rankselect3Button->setEnabled(false);
ui->rankselect4Button->setEnabled(false);
rankAnswerQuestion(2);
}
void BrainStorm::on_rankselect3Button_clicked()
{
ui->rankselect3Button->setStyleSheet("background-color: rgb(170, 170, 255)");
ui->rankselect1Button->setEnabled(false);
ui->rankselect2Button->setEnabled(false);
ui->rankselect3Button->setEnabled(false);
ui->rankselect4Button->setEnabled(false);
rankAnswerQuestion(3);
}
void BrainStorm::on_rankselect4Button_clicked()
{
ui->rankselect4Button->setStyleSheet("background-color: rgb(170, 170, 255)");
ui->rankselect1Button->setEnabled(false);
ui->rankselect2Button->setEnabled(false);
ui->rankselect3Button->setEnabled(false);
ui->rankselect4Button->setEnabled(false);
rankAnswerQuestion(4);
}
void BrainStorm::on_backpushButton_2_clicked()
{
ui->stackedWidget->setCurrentWidget(ui->mianMenu);
}
void BrainStorm::on_rankchartpushButton_clicked()
{
QJsonObject json;
json["cmd"] = ENQUIRE;
_com->writeData(json);
ui->stackedWidget->setCurrentWidget(ui->rankchart);
}
void BrainStorm::on_backpushButton_3_clicked()
{
ui->stackedWidget->setCurrentWidget(ui->mianMenu);
}
communicate.cpp
#include "communicate.h"
Communicate::Communicate(QObject *parent) : QObject(parent)
{
s.connectToHost(QHostAddress("192.168.1.126"),9999);//连接服务器
connect(&s,SIGNAL(readyRead()),this,SLOT(readData()));
}
void Communicate::readData()
{
//读数据
QByteArray data;
while(s.bytesAvailable())
{
data += s.readAll();
}
//数据解析 将读到的数据解析为json字符串
QJsonDocument dt = QJsonDocument::fromJson(data);
if(dt.isNull())
{
return;
}
QJsonObject root = dt.object();
//具体逻辑处理
int cmd = root["cmd"].toInt();
switch (cmd)
{
case REGISTER:
{
emit rstResult(root["result"].toInt());
break;
}
case LOGIN:
{
emit login(root);
break;
}
case SINGEL_GETQUESTION:
{
emit receiveSingelQuestion(root["question"].toObject());
break;
}
case RANK:
{
emit Rank(root);
break;
}
case ANSWER:
{
emit Rank(root);
break;
}
case EXIT:
{
emit Exit(root);
break;
}
case RANKRESULT:
{
emit Rank(root);
break;
}
case ENQUIRE:
{
emit Rank(root);
break;
}
default:
break;
}
}
void Communicate::writeData(const QJsonObject &json)
{
QJsonDocument d(json);
QByteArray sendData = d.toJson();
int len = sendData.size();
//发送数据长度
s.write((char *)&len,sizeof(int));
//发送数据本身
s.write(sendData);
}
login.cpp
#include "login.h"
#include "ui_widget.h"
#include "register.h"
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
_com = new Communicate();
connect(_com,SIGNAL(login(QJsonObject)),this,SLOT(login(QJsonObject)));
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_pushButton_clicked()
{
QString userName = ui->userNamelineEdit->text();
QString passwd = ui->PasswordlineEdit->text();
//登陆操作,将登录信息发送给服务器
QJsonObject json;
json["cmd"] = LOGIN;
json["userName"] = userName;
json["passwd"] = passwd;
_com->writeData(json); //数据发送给服务器
}
void Widget::on_pushButton_2_clicked()
{
this->hide();
Register reg(_com);
reg.exec();
this->show();
}
void Widget::login(QJsonObject json)
{
int result = json["result"].toInt();
switch (result)
{
case OK:
{
this->hide();
BrainStorm *dlg = new BrainStorm(_com,json);
dlg->show();
dlg->setAttribute(Qt::WA_DeleteOnClose);
break;
}
case ERROR:
QMessageBox::critical(this,"登录","登录失败");
break;
case NAMEORPASSWD:
QMessageBox::critical(this,"登录","用户名或密码错误");
break;
case USEREXIST:
QMessageBox::critical(this,"登录","用户已登录,不允许重复登录");
break;
default:
break;
}
}
register.cpp
#include "register.h"
#include "ui_register.h"
Register::Register(Communicate *com,QWidget *parent) :
QDialog(parent),
ui(new Ui::Register)
{
ui->setupUi(this);
_com = com;
connect(_com,SIGNAL(rstResult(int)),this,SLOT(rstResult(int)));
}
Register::~Register()
{
delete ui;
}
void Register::on_registerpushButton_clicked()
{
QString userName = ui->userNamelineEdit->text();
QString passwd = ui->PasswordlineEdit->text();
//发送注册信息
QJsonObject json;
json["cmd"] = REGISTER;
json["userName"] = userName;
json["passwd"] = passwd;
_com->writeData(json); //数据发送给服务器
}
void Register::rstResult(int ret)
{
switch (ret)
{
case OK:
QMessageBox::information(this,"注册","注册成功");
break;
case ERROR:
QMessageBox::critical(this,"注册","未知错误");
break;
case USEREXIST:
QMessageBox::critical(this,"注册","该用户已存在,注册失败");
break;
default:
break;
}
}
void Register::on_returnpushButton_clicked()
{
this->close();
}
main.cpp
#include "login.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
服务器端代码展示(Linux 下QT开发)
common.h
#ifndef COMMOM_H
#define COMMOM_H
//5XXX 用户操作指令
#define REGISTER 5001 //注册
#define OK 5000 //注册成功
#define LOGIN 5002 //登录
#define SINGEL_GETQUESTION 5003 //从服务器获取题目
#define RANK 5004 //进行排位匹配
#define ANSWER 5005 //排位回答一个问题
#define RANKRESULT 5006 //排位结束
#define EXIT 5007 //排位时一个用户退出
#define ENQUIRE 5008 //查询排位赛排名
//8XXX 错误
#define ERROR 8001 //位置错误
#define USEREXIST 8002 //用户存在
#define NAMEORPASSWD 8003 //用户名或者密码错误
#define USERLOGIN 8004 //用户已经登录
//其他
#define QUESTION_NUM 5
#endif // COMMOM_H
database.h
#ifndef DATABASEMENT_H
#define DATABASEMENT_H
#include "mysql/mysql.h"
#include <mutex>
#include "spdlog/spdlog.h"
#include <json/json.h>
#include <string.h>
class DataBasement
{
public:
DataBasement(const char *host,const char *userName,const char *password,const char *dbName);
//执行数据库语句
bool db_exec(const char *sql);
//数据库查询
//sql 查询语句 outJson 查询结果
bool db_select(const char *sql,Json::Value &outJson);
private:
std::mutex _mutex; //数据库句柄互斥锁
MYSQL *_mysql; //数据库句柄
};
#endif // DATABASEMENT_H
myserver.h
#ifndef MYSERVER_H
#define MYSERVER_H
#include "tcpserver.h"
#include <spdlog/spdlog.h>
#include <databasement.h>
#include <json/json.h>
#include <json/reader.h>
#include "commom.h"
#include <string>
#include <map>
#include "user.h"
#define DEBUG
#define QUESTION_NUM 5
class MyServer:public TcpServer
{
public:
MyServer();
void connectEvent(TcpSocket *);
void readEvent(TcpSocket *);
void writeEvent(TcpSocket *);
void closeEvent(TcpSocket *,short);
private:
void writeData(TcpSocket *s,const Json::Value &inJson);//发送数据
void Register(TcpSocket *s,const Json::Value &inJson);//用户注册
void Login(TcpSocket *s,const Json::Value &inJson);//用户登录
void singelGetQuestion(TcpSocket *s); //个人训练获取题目
void initRankMap();//初始化rank积分比对表
void Rank(TcpSocket *s);//进行排位
void startRank(TcpSocket *first,TcpSocket *second);//开始对战
void rankAnswerQuestion(TcpSocket *s,Json::Value &inJson);//rank时回答一道问题
void rankResult(TcpSocket *s, const Json::Value &inJson);
void Enquire(TcpSocket *s);//获取排位赛排名
private:
std::shared_ptr<spdlog::logger> _log; //记录日志的句柄
DataBasement *_db; //数据库句柄
//map容器中健是用户名,值是用户指针
std::mutex _userLock; //用户列表锁
std::map<std::string,User *> _users; //在线用户列表
//key是rank积分,值是段位说明
std::map<int,std::string> _rankMap; //rank积分比对表
//key为rank积分,value为参与rank的用户socket句柄
std::mutex _rankLock;
std::map<int,TcpSocket*> _rankQueue; //排位匹配等待队列
};
tcpserver.h
#ifndef TCPSERVER_H
#define TCPSERVER_H
#include "thread.h"
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <event2/listener.h>
#include <event2/bufferevent.h>
#include "tcpsocket.h"
class TcpSocket;
//Tcp服务器的基类
class TcpServer
{
friend class TcpSocket;
public:
TcpServer(int threadNum = 8);//开启threadNum个线程
int listen(int port,const char* ip = nullptr);//监听
//服务器开始运行
void start();
protected:
static void listenCb(struct evconnlistener *,evutil_socket_t,struct sockaddr *,int socklen,void *);//服务器监听回调函数
//有客户端连接的时候调用这个函数
//监听处理函数
void listenEvent(evutil_socket_t fd,struct sockaddr_in *);//listenCb为静态成员函数,访问成员变量不方便,通过该函数来解决
//-------------------------------虚函数去具体处理客户端的逻辑-------------------------------
virtual void connectEvent(TcpSocket *){} //客户端连接事件
virtual void readEvent(TcpSocket *){} //客户端可读事件
virtual void writeEvent(TcpSocket *){} //客户端可写事件
virtual void closeEvent(TcpSocket *,short ){} //客户端关闭事件
private:
int m_threadNum;//线程个数
Thread *m_threadPool;//线程池
struct event_base *m_base;
struct evconnlistener *m_listener;//监听客户端的连接
int m_nextThread;//记录下一个线程的下标
};
#endif // TCPSERVER_H
tcpsocket.h
#ifndef TCPSOCKET_H
#define TCPSOCKET_H
#include "tcpserver.h"
#include <string>
class TcpServer;
//通信类,负责与客户端的通信
class TcpSocket
{
public:
TcpSocket(TcpServer *tcpServer,struct bufferevent *bev,char *ip,u_int16_t port);
//可读事件回调函数
static void readEventCb(struct bufferevent *bev,void *ctx); //客户端发送的信息由该函数处理
//可写事件回调函数
static void writeEventCb(struct bufferevent *bev,void *ctx); //客户端发送的信息由该函数处理
//异常事件回调函数
static void closeEventCb(struct bufferevent *bev,short what,void *stx);
char *getIp();//获取ip地址
u_int16_t getPort();//获取端口
//从客户端读数据
int readData(void *data,int size);
//往客户端写数据
int writeData(const void *data,int size);
void setUserName(std::string name);
std::string getUserName();
private:
static TcpServer *m_tcpServer; //服务器类对象
struct bufferevent *m_bev;//用于与客户端通信的句柄
char *m_ip; //客户端的ip地址
u_int16_t m_port; //客户端端口
std::string _userName; //用户名
};
#endif // TCPSOCKET_H
thread.h
#ifndef THREAD_H
#define THREAD_H
#include <stdio.h>
#include <stdlib.h>
#include <event2/event.h>
#include <event2/event_struct.h>
#include <event2/event_compat.h>
#include <pthread.h>
#include <unistd.h>
class Thread
{
public:
Thread();
void start();//线程运行函数
struct event_base *getBase(); //获取事件集合
protected:
static void* worker(void*);//线程工作函数
void run();//线程的逻辑处理函数,线程工作函数为静态成员变量,想调用成员很麻烦,用该函数来解决
//静态成员函数只能调用静态成员变量
static void pipeRead(evutil_socket_t,short,void *);//监听到管道有数据可读的回调函数
private:
struct event_base *m_base;//开辟base事件池
pthread_t m_threadId;//线程id
int m_pipeReadFd; //管道的读端
int m_pipeWriteFd; //管道的写端
struct event m_pipeEvent;//管道事件
};
#endif // THREAD_H
user.h
#ifndef USER_H
#define USER_H
#include <string>
#include "tcpsocket.h"
class User
{
public:
User(std::string n,std::string p,int ran,TcpSocket *s);
TcpSocket *getSocket();
const char *getName();
int getRank();
void setRank(int rank);
private:
std::string _userName; //用户名
std::string _passwd; //用户密码
int _rank; //rank分数
TcpSocket *_s; //通信套接字类
};
#endif // USER_H
database.cpp
#include "databasement.h"
#include <json/json.h>
DataBasement::DataBasement(const char *host,const char *userName,const char *password,const char *dbName)
{
//初始化数据库句柄
_mysql = mysql_init(nullptr);
if(_mysql == nullptr)
{
// spdlog::get("myprojecttxt")->error("数据库初始化失败!");
exit(-1);
}
//连接数据库
MYSQL *con = mysql_real_connect(_mysql,host,userName,password,dbName,0,nullptr,0);
if(con == nullptr)
{
spdlog::get("myprojecttxt")->error ("连接数据库失败:{}",mysql_error(_mysql));
exit(-1);
}
_mysql = con;
//设置字符集
int ret = mysql_query(_mysql,"set names utf8");//设置数据库支持中文
if(ret != 0)
{
spdlog::get("myprojecttxt")->error("数据库设置字符集失败:{}",mysql_error(_mysql));
exit(-1);
}
}
bool DataBasement::db_exec(const char *sql)
{
std::unique_lock<std::mutex> loc{_mutex}; //数据库句柄上锁
int ret = mysql_query(_mysql,sql);
if(ret != 0)
{
spdlog::get("myprojecttxt")->error("数据库语句执行失败: {}",mysql_error(_mysql));
return false;
}
return true;
}
bool DataBasement::db_select(const char *sql, Json::Value &outJson)
{
std::unique_lock<std::mutex> lock{_mutex}; //数据库句柄上锁
int ret = mysql_query(_mysql,sql);
if(ret != 0)
{
spdlog::get("myprojecttxt")->error("数据库语句执行失败: {}",mysql_error(_mysql));
return false;
}
//从mysql服务器下载查询结果
MYSQL_RES *sql_res = mysql_store_result(_mysql);
if(sql_res == nullptr)
{
if(mysql_errno(_mysql) == 0)
{
return true;
}
else
{
spdlog::get("myprojecttxt")->error("数据库语句执行失败: {}",mysql_error(_mysql));
}
}
MYSQL_ROW row; //从结果集中一行一行取出数据
unsigned int num_fileds = mysql_num_fields(sql_res);//获取列数
MYSQL_FIELD *fetch_field = mysql_fetch_field(sql_res); //获取表头
//一行一行获取数据
while(row = mysql_fetch_row(sql_res))
{
for(unsigned int i = 0;i < num_fileds;i++)
{
outJson[fetch_field[i].name].append(row[i]);
}
}
mysql_free_result(sql_res);
return true;
}
myserver.cpp
#include "myserver.h"
MyServer::MyServer()
{
#ifdef DEBUG
_log = spdlog::stdout_color_mt("console");
#else
_log = spdlog::rotating_logger_mt("myprojecttxt","myprojecttxt",1024*1024 * 5,3);\
_log->flush_on(spdlog::level::info);
#endif
_log->info("1111111111111");
_db = new DataBasement(nullptr,"xiaowang","xiaowang","BrainStorm");
initRankMap();
}
void MyServer::connectEvent(TcpSocket *s)
{
_log->info("有一个新连接[{}:{}]",s->getIp(),s->getPort());
}
void MyServer::readEvent(TcpSocket *s)
{
char buf[1024] = {0};
while(1)
{
int len = 0;
s->readData(&len,sizeof(len));
if(len <= 0)
{
break;
}
s->readData(buf,len);
//数据解析,读到的数据为json格式数据
Json::Value root;
Json::Reader reader; //json解析器
if(!reader.parse(buf,root))
{
_log->error("数据解析失败");
return;
}
int cmd = root["cmd"].asInt();
switch (cmd)
{
case REGISTER:
Register(s,root);
break;
case LOGIN:
Login(s,root);
break;
case SINGEL_GETQUESTION:
singelGetQuestion(s);
break;
case RANK:
Rank(s);
break;
case ANSWER:
rankAnswerQuestion(s,root);
break;
case RANKRESULT:
rankResult(s,root);
break;
case ENQUIRE:
Enquire(s);
break;
default:
break;
}
}
}
void MyServer::writeEvent(TcpSocket *)
{
}
void MyServer::closeEvent(TcpSocket *s, short)
{
//用户意外从rank等待队列中退出
{
std::unique_lock<std::mutex> lock(_rankLock); //局部锁,要加括号
int rank = _users[s->getUserName()]->getRank();
auto it = _rankQueue.find(rank);
if(it != _rankQueue.end())
{
_rankQueue.erase(it);
}
}
//printf("客户端退出,ip: %s 端口: %d\n",s->getIp(),s->getPort());
std::unique_lock<std::mutex> lock(_userLock);
std::map<std::string,User *>::iterator it = _users.begin();
while(it != _users.end())
{
if(it->second->getSocket() == s)
{
_users.erase(it);
printf("用户{}{}:{} 退出",it->second->getName(),s->getIp(),s->getPort());
//释放user
delete it->second;
return;
}
it++;
}
_log->info("{}{} 退出",s->getIp(),s->getPort());
}
void MyServer::writeData(TcpSocket *s, const Json::Value &inJson)
{
std::string data = inJson.toStyledString();
s->writeData(data.c_str(),data.length());
}
void MyServer::Register(TcpSocket *s,const Json::Value &inJson)
{
std::string userName = inJson["userName"].asString();
std::string passwd = inJson["passwd"].asString();
//检测用户是否已经注册
char sql[100] = {0};
sprintf(sql,"select *from user where name = '%s' and passwd = '%s'",userName.c_str(),passwd.c_str());
int result = OK;
Json::Value outJson;
bool ret = _db->db_select(sql,outJson);
if(!ret)
{
result = ERROR;
_log->error("查询失败!");
}
if(outJson.isMember("name")) //用户存在,已经注册过了
{
result = USEREXIST;
}
else
{
sprintf(sql,"insert into user(name,passwd,rank) values('%s','%s',0)",userName.c_str(),passwd.c_str());
bool ret = _db->db_exec(sql);
if(!ret)
{
result = ERROR;
_log->error("登录失败!");
}
else
{
_log->info("Register user = {} succeed",userName);
}
}
Json::Value json;
json["cmd"] = REGISTER;
json["result"] = result;
writeData(s,json);
}
void MyServer::Login(TcpSocket *s, const Json::Value &inJson)
{
int rank = 0;
std::string userName = inJson["userName"].asString();
std::string passwd = inJson["passwd"].asString();
//检测用户是否存在
char sql[100] = {0};
sprintf(sql,"select *from user where name = '%s' and passwd = '%s'",userName.c_str(),passwd.c_str());
int result = OK;
Json::Value outJson;
bool ret = _db->db_select(sql,outJson);
if(!ret)
{
result = ERROR;
_log->error("查询失败!");
}
if(outJson.isMember("name")) //用户已经登录
{
std::unique_lock<std::mutex> lock(_userLock);
if(_users.find(userName) != _users.end()) //用户已经登录
{
result = USERLOGIN;
}
else
{
int i = 0;
rank = atoi(outJson["rank"][i].asString().c_str());
User* user = new User(userName,passwd,rank,s);
_users.insert(make_pair(userName,user));
_log->info("用户 {}{}:{} 登录成功",userName,s->getIp(),s->getPort());
s->setUserName(userName);
}
}
else
{
result = NAMEORPASSWD;
}
Json::Value json;
json["cmd"] = LOGIN;
json["result"] = result;
json["userName"] = userName;
json["rank"] = _rankMap[rank];
writeData(s,json);
}
void MyServer::singelGetQuestion(TcpSocket *s)
{
int result = OK;
char sql[100] = {0};
sprintf(sql,"select *from question order by rand() limit %d",QUESTION_NUM);
Json::Value outJson;
bool ret = _db->db_select(sql,outJson);
if(!ret || outJson["question"].size() != QUESTION_NUM)
{
result = ERROR;
_log->error("singelGetQuestion select question error!");
}
Json::Value json;
json["cmd"] = SINGEL_GETQUESTION;
json["result"] = result;
json["question"] = outJson;
_log->info("用户{}[{}:{}] 获取题目:{}\n", s->getUserName(),s->getIp(),s->getPort(),json.toStyledString().c_str());
writeData(s,json);
}
void MyServer::startRank(TcpSocket *first, TcpSocket *second)
{
int result = OK;
char sql[100] = {0};
sprintf(sql,"select *from question order by rand() limit %d",QUESTION_NUM);
Json::Value outJson;
bool ret = _db->db_select(sql,outJson);
if(!ret || outJson["question"].size() != QUESTION_NUM)
{
result = ERROR;
_log->error("startRank select question error!");
}
Json::Value json;
json["cmd"] = RANK;
json["result"] = result;
json["question"] = outJson;
//first user
json["enemyName"] = second->getUserName();
json["enemyRank"] = _rankMap[_users[second->getUserName()]->getRank()];
json["enemyScore"] = 0;
writeData(first,json);
//second user
json["enemyName"] = first->getUserName();
json["enemyRank"] = _rankMap[_users[first->getUserName()]->getRank()];
writeData(second,json);
_log->info("获取rank题目 :{}\n",json.toStyledString());
}
void MyServer::initRankMap()
{
char buf[100] = {0};
int rank = 0;//段位(青铜1/2/3)
int num = 0; //有多少颗星
for(int i = 0;i < 100;i++)
{
if(i < 9)
{
rank = i / 3;
num = i % 3;
sprintf(buf,"青铜%d %d颗星",3-rank,num+1);
}
else if(9 <= i && i < 18)
{
rank = (i - 9) / 3;
num = (i - 9) % 3;
sprintf(buf,"白银%d %d颗星",3-rank,num+1);
}
else if(18 <= i && i < 34)
{
rank = (i - 18) / 4;
num = (i - 18) % 4;
sprintf(buf,"黄金%d %d颗星",4-rank,num+1);
}
else if(34 <= i && i < 50)
{
rank = ( i - 34) / 4;
num = (i - 34) % 4;
sprintf(buf,"铂金%d %d颗星",4-rank,num+1);
}
else if(50 <= i && i < 75)
{
rank = (i - 50) / 5;
num = (i - 50) % 5;
sprintf(buf,"钻石%d %d颗星",5-rank,num+1);
}
else if(75 <= i && i < 100)
{
rank = (i - 75) / 5;
num = (i - 75) % 5;
sprintf(buf,"星耀%d %d颗星",5-rank,num+1);
}
_rankMap.insert(std::make_pair(i,buf));
}
// for(int i = 0;i < 100;i++)
//{
// std::cout << i << ":" << _rankMap[i] << std::endl;
//}
}
void MyServer::Rank(TcpSocket *s)
{
TcpSocket *other = nullptr; //对手
User *user = _users[s->getUserName()];//获取当前用户名
int rank = user->getRank(); //获取用户名的rank分
std::unique_lock<std::mutex> lock(_rankLock);
//查找同一段位的对手
std::map<int,TcpSocket*>::iterator it = _rankQueue.find(rank);
if(it != _rankQueue.end())
{
other = it->second;
_rankQueue.erase(it);
}
else //查找其他段位的选手 积分差值绝对值为小于5的都可以进行对决
{
for(int i = 1;i <= 5;i++)
{
it = _rankQueue.find(rank + i);
if(it != _rankQueue.end())
{
other = it->second;
_rankQueue.erase(it);
break;
}
it = _rankQueue.find(rank - i);
if(it != _rankQueue.end())
{
other = it->second;
_rankQueue.erase(it);
break;
}
}
}
_log->info(" rank分数: {}",rank);
if(other == nullptr) //没有匹配到对手,将用户加入到等待队列
{
_rankQueue.insert(std::make_pair(rank,s));
_log->info("当前等待 rank人数: {}",_rankQueue.size());
}
else //开始发题
{
startRank(s,other);
}
}
//rank时回答一道问题
void MyServer::rankAnswerQuestion(TcpSocket *s, Json::Value &inJson)
{
std::string enemyName = inJson["enemyName"].asString();
User *user = _users[enemyName];
Json::Value json;
if(user == nullptr)
{
json["cmd"] = EXIT;
json["enemyScore"] = 0;
writeData(s,json);
}
else
{
json["cmd"] = ANSWER;
json["enemyScore"] = inJson["score"].asInt();
json["enemyQuestionId"] = inJson["questionId"].asInt();
writeData(user->getSocket(),json);
}
}
void MyServer::rankResult(TcpSocket *s, const Json::Value &inJson)
{
char buf[256] = {0};
std::unique_lock<std::mutex> lock(_userLock);
User *user = _users[s->getUserName()];
int score = inJson["score"].asInt();
int enemyscore = inJson["enemyScore"].asInt();
if(score < enemyscore)
{
user->setRank(user->getRank() - 1);
}
else if(score > enemyscore)
{
user->setRank(user->getRank() + 1);
}
Json::Value json;
json["cmd"] = RANKRESULT;
json["newRank"] = _rankMap[user->getRank()];
writeData(s,json);
sprintf(buf,"UPDATE user SET rank = %d WHERE name = '%s'",user->getRank(),s->getUserName().c_str());
_db->db_exec(buf);
}
void MyServer::Enquire(TcpSocket *s)
{
Json::Value json;
Json::Value outJson;
char buf[1024] = {0};
sprintf(buf,"SELECT name,rank FROM user ORDER BY rank DESC");
_db->db_select(buf,outJson);
json["cmd"] = ENQUIRE;
json["order"] = outJson;
writeData(s,json);
}
tcpserver.cpp
#include "tcpserver.h"
TcpServer::TcpServer(int threadNum):m_nextThread(0)
{
if(threadNum <= 0)
{
printf("线程个数不能小于等于0!\n");
exit(-1);
}
//创建线程池
m_threadNum = threadNum;
m_threadPool = new Thread[threadNum];
if(m_threadPool == nullptr)
{
printf("线程池创建失败!\n");
exit(-1);
}
m_base = event_base_new();
if(!m_base)
{
printf("监听事件池创建失败\n");
exit(-1);
}
}
int TcpServer::listen(int port, const char *ip)
{
struct sockaddr_in sin;
memset(&sin,0,sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(port);
if(ip != nullptr)
{
inet_aton(ip,&sin.sin_addr);
}
m_listener = evconnlistener_new_bind(m_base,listenCb,this,LEV_OPT_REUSEABLE|LEV_OPT_CLOSE_ON_FREE,-1,(struct sockaddr *)&sin,sizeof(sin));
if(!m_listener)
{
printf("创建监听失败\n");
return -1;
}
//开启线程池
for(int i = 0;i < m_threadNum;i++)
{
m_threadPool[i].start();
printf("线程池 %d启动成功\n",i);
}
return 0;
}
void TcpServer::listenCb(struct evconnlistener *,evutil_socket_t fd ,struct sockaddr *clientAddr, int, void *data)
{
TcpServer *p = (TcpServer*)data;
p->listenEvent(fd,(struct sockaddr_in *)clientAddr);
}
void TcpServer::listenEvent(evutil_socket_t fd, struct sockaddr_in *clientAddr)
{
char *ip = inet_ntoa(clientAddr->sin_addr); //客户端的ip地址
uint16_t port = ntohs(clientAddr->sin_port);//客户端端口
//从线程池中选择一个线程去处理客户端的请求
//以轮询的方式选择线程
struct event_base *base = m_threadPool[m_nextThread].getBase();
m_nextThread = (m_nextThread + 1) % m_threadNum;
struct bufferevent *bev = bufferevent_socket_new(base,fd,BEV_OPT_CLOSE_ON_FREE);//句柄,用于通信
if(!bev)
{
printf("创建套接字失败!\n");
event_base_loopbreak(base);
return;
}
//创建一个通信对象
TcpSocket *s = new TcpSocket(this,bev,ip,port);
//服务器单独封装一个类负责和客户端的通信
bufferevent_setcb(bev,s->readEventCb,s->writeEventCb,s->closeEventCb,s);
bufferevent_enable(bev,EV_WRITE);//打开是否可写
bufferevent_enable(bev,EV_READ);//打开是否可读
bufferevent_enable(bev,EV_SIGNAL);//
//bufferevent_disable(bev,EV_READ);
//bufferevent_write(bev,MESSAGE,strlen(MESSAGE));
//调用客户端连接事件
connectEvent(s);
}
void TcpServer::start()
{
event_base_dispatch(m_base);
evconnlistener_free(m_listener);
event_base_free(m_base);
printf("服务器开启成功,服务器开始运行\n");
}
tcpsocket.cpp
#include "tcpsocket.h"
TcpServer *TcpSocket::m_tcpServer = NULL;
TcpSocket::TcpSocket(TcpServer *tcpServer,struct bufferevent *bev,char *ip,u_int16_t port)
{
m_tcpServer = tcpServer;
m_bev = bev;
m_ip = ip;
m_port = port;
}
void TcpSocket::readEventCb(bufferevent *, void *ctx)
{
TcpSocket *s = (TcpSocket*)ctx;
m_tcpServer->readEvent(s);//通过m_tcpServer指针返回服务器处理
}
void TcpSocket::writeEventCb(bufferevent *, void *ctx)
{
TcpSocket *s = (TcpSocket*)ctx;
m_tcpServer->writeEvent(s);//通过m_tcpServer指针返回服务器处理
}
void TcpSocket::closeEventCb(struct bufferevent *,short what,void *ctx)
{
TcpSocket *s = (TcpSocket*)ctx;
m_tcpServer->closeEvent(s,what);//通过m_tcpServer指针返回服务器处理
delete s;
}
char *TcpSocket::getIp()
{
return m_ip;
}
u_int16_t TcpSocket::getPort()
{
return m_port;
}
int TcpSocket::readData(void *data, int size)
{
return bufferevent_read(m_bev,data,size);
}
int TcpSocket::writeData(const void *data, int size)
{
return bufferevent_write(m_bev,data,size);
}
void TcpSocket::setUserName(std::string name)
{
_userName = name;
}
std::string TcpSocket::getUserName()
{
return _userName;
}
thread.cpp
#include "thread.h"
Thread::Thread()
{
m_base = event_base_new();//初始化base事件池
if(!m_base)
{
printf("创建base事件池失败\n");
exit(-1);
}
//创建管道
int fd[2];
if(pipe(fd) == -1)
{
perror("pipe");
exit(-1);
}
m_pipeReadFd = fd[0];//pipeEvent监听
m_pipeWriteFd = fd[1];//主线程收到连接请求写入
//让管道事件监听管道的读端
//如果监听到管道的读端有数据可读,通过回调函数写入m_pipeEvent
event_set(&m_pipeEvent,m_pipeReadFd,EV_READ|EV_PERSIST,pipeRead,this);
//将事件添加到base集合中
event_base_set(m_base,&m_pipeEvent);
//开启事件的监听
event_add(&m_pipeEvent,0);
}
void Thread::pipeRead(evutil_socket_t,short,void *)
{
}
void Thread::start()
{
//创建一个线程
pthread_create(&m_threadId,nullptr,worker,this);
//线程分离
pthread_detach(m_threadId);
}
void* Thread::worker(void* arg)
{
Thread *p = (Thread*)arg;
p->run();
}
void Thread::run()
{
//监听base事件池
event_base_dispatch(m_base);//dispatch死循环,用来处理事件,监听
event_base_free(m_base);
}
struct event_base *Thread::getBase()
{
return m_base;
}
user.cpp
#include "user.h"
User::User(std::string n,std::string p,int ran,TcpSocket *s):_userName(n),_passwd(p),_rank(ran),_s(s)
{
}
TcpSocket *User::getSocket()
{
return _s;
}
const char *User::getName()
{
return _userName.c_str();
}
int User::getRank()
{
return _rank;
}
void User::setRank(int rank)
{
if(rank <= 0)
{
rank = 0;
}
_rank = rank;
}