开发环境/工具:Ubuntu18.04、Qt、MySQL、libevent、spdlog、
实现功能:用户注册、用户登录、排位匹配对战答题、个人训练答题、段位记录系统、段位匹配系统
项目描述:
1、此项目基于Qt平台,分别在windows和Linux下构建客户端和服务器,实现“头脑风暴”在线对战小游戏
2、采用C/S架构,利用TCP协议通信,通过spdlog在服务器端记录日志信息
3、采用线程池和libevent事件通知库配合实现的高并发服务器
4、采用QT界面编程,提供较为美观的可视化界面,GUI 便于用户使用操作
5、采用 json数据格式进行服务器与客户端之间的数据交换
6、采用MySQL数据库对用户信息和游戏题目进行存储,通过Map容器对在线用户进行记录
#ifndef COMMUNICATE_H
#define COMMUNICATE_H
#include
#include
#include
#include
#include "../common.h"
#include
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 receiveSingleQuestion(QJsonObject);
void Rank(QJsonObject);
public slots:
void readData();
private:
QTcpSocket s;
};
#endif // COMMUNICATE_H
#ifndef WIDGET_H
#define WIDGET_H
#include
#include "communicate.h"
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
~Widget();
private slots:
void on_StartButton_clicked();
void on_RegisterButton_clicked();
void Login(QJsonObject json);
private:
Ui::Widget *ui;
Communicate *_com; //定义一个通信类指针
};
#endif // WIDGET_H
#ifndef REGISTER_H
#define REGISTER_H
#include
#include
#include "../common.h"
#include "communicate.h"
namespace Ui {
class Register;
}
class Register : public QDialog
{
Q_OBJECT
public:
explicit Register(Communicate *com, QWidget *parent = 0);
~Register();
private slots:
void on_RegisterButton_clicked();
void on_BackButton_clicked();
void rstResult(int);
private:
Ui::Register *ui;
Communicate *_com;
};
#endif // REGISTER_H
#ifndef TRAINING_H
#define TRAINING_H
#include
#include "communicate.h"
#include
#include
#include
#define QUESTIONTIME 8
#define QUESTION_NUM 5
namespace Ui {
class training;
}
class training : public QDialog
{
Q_OBJECT
public:
explicit training(Communicate *com, QJsonObject JSON, QWidget *parent = 0);
~training();
private:
void singleGetQuestion();
//个人训练答题
void singleAnswerQuestion(int select);
//设置个人训练题目
void singleSetQuestion();
// void singleTimerOut();
void SetSelfLineInfo();
void SetEnemyLineInfo();
void RankSetQuestion();
//排位答题
void RankAnswerQuestion(int select);
//处理排位结果
void RankSetResult(QJsonObject);
private slots:
void on_singleButton_clicked();
void on_single_back_clicked();
void on_single_start_clicked();
//接受个人训练题目
void receiveSingleQuestion(QJsonObject json);
void on_single_SelectButton_one_clicked();
void on_single_SelectButton_two_clicked();
void on_single_SelectButton_three_clicked();
void on_single_SelectButton_four_clicked();
// 个人训练 计时器
void singleTimerOut();
void on_singnal_score_backButton_clicked();
void on_rankButton_clicked();
//开始排位
void Rank(QJsonObject json);
void RankTimerOut();
void on_rankSelectButton1_clicked();
void on_rankSelectButton2_clicked();
void on_rankSelectButton3_clicked();
void on_rankSelectButton4_clicked();
void on_pushButton_clicked();
void on_RankWaitBackButton_clicked();
private:
Ui::training *ui;
Communicate *_com; //通信类指针
QJsonObject _singleQuestionJson; //接收个人训练问题的对象
int singleQuestionFlag; //个人训练当前问题的标志位(下标)
QTimer _singleTimer; // 个人训练项目定时器
int _singleSec; // 个人训练答题时间
int _singleScore; // 个人训练得分
QJsonObject RankQuestion; //rank的问题
QString EnemyName; //对手的名字
QString EnemyRank; //对手的段位
int EnemyScore; //对手的得分
int RankSec; //rank答题计时
int SelfScore; //个人得分
QString SelfName; //个人用户名
QString SelfRank; //个人段位
int CurrentRankQuestion; //rank当前回答问题的下标
int EnemyRankQueston; //对手当前回答问题的下标
QTimer RankTimer; //rank定时器
};
#endif // TRAINING_H
#include "communicate.h"
#include
#include
Communicate::Communicate(QObject *parent) : QObject(parent)
{
//连接服务器
s.connectToHost(QHostAddress("192.168.1.170"),9999);
connect(&s,SIGNAL(readyRead()),this,SLOT(readData()));
}
/*
QJsonArray 封装 JSON 数组
QJsonDocument 读写 JSON 文档
QJsonObject 封装 JSON 对象
QJsonObject::iterator 用于遍历QJsonObject的 STL 风格的非 const 遍历器
QJsonParseError 报告 JSON 处理过程中出现的错误
QJsonValue 封装 JSON 值
*/
void Communicate::readData()
{
QByteArray data;
while (s.bytesAvailable())
{
data += s.readAll();
}
//数据解析
QJsonDocument dt = QJsonDocument::fromJson(data);
//QJsonDocument::fromJson()可以由QByteArray对象构造一个QJsonDocument对象,用于我们的读写操作。
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 SINGLEGETQUESTION:
emit receiveSingleQuestion(root["question"].toObject());
break;
case RANK:
emit Rank(root);
break;
case ANSWER:
emit Rank(root);
break;
case RANKRESULT:
emit Rank(root);
break;
default:
break;
}
}
void Communicate::writeData(const QJsonObject &json)
{
//QJsonDocument:能将对象转变为json字符串
QJsonDocument d(json);
//toJSON() 方法可以将 Date 对象转换为字符串,并格式化为 JSON 数据格式。
QByteArray sendData = d.toJson();
int len = sendData.size();
//发送数据长度
s.write((char *)&len,sizeof(int));
//发送数据
s.write(sendData);
}
#include "login.h"
#include "ui_widget.h"
#include "register.h"
#include
#include "training.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_StartButton_clicked()
{
QString userName = ui->UserlineEdit->text();
QString passwd = ui->PasswdlineEdit->text();
//将登录信息发送给服务器
QJsonObject json;
json["cmd"] = LOGIN;
json["userName"] = userName;
json["passwd"] = passwd;
//给服务器发送数据
_com->writeData(json);
}
void Widget::on_RegisterButton_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();
training *dlg = new training(_com,json);
dlg->show();
//退出时自己销毁
dlg->setAttribute(Qt::WA_DeleteOnClose);
break;
}
case ERROR:
QMessageBox::critical(this,"登录","登录失败,未知错误");
break;
case USERLOGIN:
QMessageBox::critical(this,"登录","登录失败,不允许重复登录");
break;
case NAMEORPASSWD:
QMessageBox::critical(this,"登录","用户名或者密码错误");
break;
default:
break;
}
}
#include "register.h"
#include "ui_register.h"
#include
#include
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_RegisterButton_clicked()
{
QString userName = ui->UserlineEdit->text();
QString passwd = ui->PasswdlineEdit->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_BackButton_clicked()
{
this->close();
}
#include "training.h"
#include "ui_training.h"
#include
training::training(Communicate *com,QJsonObject JSON,QWidget *parent) :
QDialog(parent),
ui(new Ui::training)
{
SelfName = JSON["userName"].toString();
SelfRank = JSON["grade"].toString();
ui->setupUi(this);
_com = com;
connect(&RankTimer,SIGNAL(timeout()),this,SLOT(RankTimerOut()));
connect(&_singleTimer,SIGNAL(timeout()),this,SLOT(singleTimerOut()));
connect(_com,SIGNAL(receiveSingleQuestion(QJsonObject)),this,SLOT(receiveSingleQuestion(QJsonObject)));
connect(_com,SIGNAL(Rank(QJsonObject)),this,SLOT(Rank(QJsonObject)));
}
training::~training()
{
delete ui;
}
//个人训练获取题目
void training::singleGetQuestion()
{
QJsonObject json;
json["cmd"] = SINGLEGETQUESTION;
_com->writeData(json);
}
//设置个人训练题目
void training::singleSetQuestion()
{
//ui->single_question->setText("111111");
ui->single_question->setText(_singleQuestionJson["question"].toArray().at(singleQuestionFlag).toString());
ui->single_SelectButton_one->setText(_singleQuestionJson["selection1"].toArray().at(singleQuestionFlag).toString());
ui->single_SelectButton_two->setText(_singleQuestionJson["selection2"].toArray().at(singleQuestionFlag).toString());
ui->single_SelectButton_three->setText(_singleQuestionJson["selection3"].toArray().at(singleQuestionFlag).toString());
ui->single_SelectButton_four->setText(_singleQuestionJson["selection4"].toArray().at(singleQuestionFlag).toString());
}
//个人训练答题
void training::singleAnswerQuestion(int select)
{
//回答正确
if(select == _singleQuestionJson["answer"].toArray().at(singleQuestionFlag).toString().toInt())
{
_singleScore += 20 * _singleSec;
if(singleQuestionFlag == (QUESTION_NUM - 1))
{
ui->single_Result->setText("恭喜你,全部回答正确");
QString str = QString("本次得分:%1").arg(_singleScore);
ui->single_Result_Score->setText(str);
_singleTimer.stop();
ui->stackedWidget->setCurrentWidget(ui->single_score);
}
singleQuestionFlag++;
singleSetQuestion();
_singleSec = QUESTIONTIME;
_singleTimer.stop();
ui->lcdNumber->display(_singleSec);
_singleTimer.start(1000);
}
else
{
ui->single_Result->setText("回答错误");
QString str = QString("本次得分:%1").arg(_singleScore);
ui->single_Result_Score->setText(str);
_singleTimer.stop();
ui->stackedWidget->setCurrentWidget(ui->single_score);
}
}
//接受个人训练题目
void training::receiveSingleQuestion(QJsonObject json)
{
//qDebug()<<"槽函数";
_singleQuestionJson = json;
singleQuestionFlag = 0;
_singleScore = 0;
_singleSec = QUESTIONTIME;
ui->lcdNumber->display(_singleSec);
singleSetQuestion();
//开启定时器
_singleTimer.start(1000);
ui->stackedWidget->setCurrentWidget(ui->single_running);
}
// 个人训练 计时器
void training::singleTimerOut()
{
--_singleSec;
printf("_singleSec\n");
if(_singleSec == 0)
{
ui->single_Result->setText("回答错误");
QString str = QString("本次得分:%1").arg(_singleScore);
ui->single_Result_Score->setText(str);
_singleTimer.stop();
ui->stackedWidget->setCurrentWidget(ui->single_score);
}
ui->lcdNumber->display(_singleSec);
}
//个人训练
void training::on_singleButton_clicked()
{
ui->stackedWidget->setCurrentWidget(ui->single_menu);
}
void training::on_single_back_clicked()
{
ui->stackedWidget->setCurrentWidget(ui->mainMenu);
}
void training::on_single_start_clicked()
{
singleGetQuestion();
}
void training::on_single_SelectButton_one_clicked()
{
singleAnswerQuestion(1);
}
void training::on_single_SelectButton_two_clicked()
{
singleAnswerQuestion(2);
}
void training::on_single_SelectButton_three_clicked()
{
singleAnswerQuestion(3);
}
void training::on_single_SelectButton_four_clicked()
{
singleAnswerQuestion(4);
}
void training::on_singnal_score_backButton_clicked()
{
ui->single_score->close();
ui->stackedWidget->setCurrentWidget(ui->mainMenu);
}
//--------------------rank-------------------
void training::RankTimerOut()
{
--RankSec;
if(RankSec == 0)
{
if(ui->rankSelectButton1->isEnabled())
{
CurrentRankQuestion++;
}
RankSetQuestion();
RankSec = QUESTIONTIME;
}
ui->lcdNumber_2->display(RankSec);
}
void training::SetSelfLineInfo()
{
QString str = QString("%1(%2) %3").arg(SelfName,-5).arg(SelfRank).arg(SelfScore);
ui->selfScore->setText(str);
}
void training::SetEnemyLineInfo()
{
QString str = QString("%1(%2) %3").arg(EnemyName,-5).arg(EnemyRank).arg(EnemyScore);
ui->enemyScore->setText(str);
}
void training::RankSetQuestion()
{
ui->rankQuestion->setText(RankQuestion["question"].toArray().at(CurrentRankQuestion).toString());
ui->rankSelectButton1->setText(RankQuestion["selection1"].toArray().at(CurrentRankQuestion).toString());
ui->rankSelectButton2->setText(RankQuestion["selection2"].toArray().at(CurrentRankQuestion).toString());
ui->rankSelectButton3->setText(RankQuestion["selection3"].toArray().at(CurrentRankQuestion).toString());
ui->rankSelectButton4->setText(RankQuestion["selection4"].toArray().at(CurrentRankQuestion).toString());
ui->rankSelectButton1->setEnabled("true");
ui->rankSelectButton2->setEnabled("true");
ui->rankSelectButton3->setEnabled("true");
ui->rankSelectButton4->setEnabled("true");
ui->rankSelectButton1->setStyleSheet("");
ui->rankSelectButton2->setStyleSheet("");
ui->rankSelectButton3->setStyleSheet("");
ui->rankSelectButton4->setStyleSheet("");
if(CurrentRankQuestion == QUESTION_NUM - 1)
{
//将结果发送给服务器
RankTimer.stop();
QJsonObject json;
json["cmd"] = RANKRESULT;
json["score"] = SelfScore;
json["enemyName"] = EnemyName;
json["enemyScore"] = EnemyScore;
_com->writeData(json);
}
}
//点击进入排位赛
void training::on_rankButton_clicked()
{
QJsonObject json;
json["cmd"] = RANK;
_com->writeData(json);
//跳转到等待页面
ui->stackedWidget->setCurrentWidget(ui->rank_wait);
}
//开始排位
void training::Rank(QJsonObject json)
{
int result_rank = json["cmd"].toInt();
if(result_rank == RANK)
{
RankQuestion = json["question"].toObject();
EnemyName = json["enemyName"].toString();
EnemyRank = json["enemyRank"].toString();
EnemyScore = json["enemyScore"].toInt();
RankSec = QUESTIONTIME;
SelfScore = 0;
CurrentRankQuestion = 0;
ui->lcdNumber_2->display(RankSec);
SetSelfLineInfo();
SetEnemyLineInfo();
RankSetQuestion();
RankTimer.start(1000);
ui->stackedWidget->setCurrentWidget(ui->rank_running);
}
else if(result_rank == ANSWER)
{
EnemyScore = json["enemyScore"].toInt();
EnemyRankQueston = json["enemyQuestionId"].toInt();
SetEnemyLineInfo();
if(EnemyRankQueston == CurrentRankQuestion)
{
//std::cout<<"EnemyRankQueston = "<RankResult->setText("平局");
}
else if(SelfScore < EnemyScore)
{
ui->RankResult->setText("失败");
}
else if(SelfScore > EnemyScore)
{
ui->RankResult->setText("成功");
}
QString str = QString("%1 --> %2").arg(SelfRank).arg(newRank);
ui->NewRank->setText(str);
SelfRank = newRank;
ui->stackedWidget->setCurrentWidget(ui->rank_Result);
}
//排位答题
void training::RankAnswerQuestion(int select)
{
//计算得分
if(select == RankQuestion["answer"].toArray().at(CurrentRankQuestion).toString().toInt())
{
SelfScore += 20 * RankSec;
}
SetSelfLineInfo();
CurrentRankQuestion++;
//判断是否进入下一题
if(EnemyRankQueston == CurrentRankQuestion)
{
//std::cout<<"EnemyRankQueston = "<writeData(json);
}
void training::on_rankSelectButton1_clicked()
{
ui->rankSelectButton1->setStyleSheet("color : rgb(0, 170, 255)");
ui->rankSelectButton1->setEnabled(false);
ui->rankSelectButton2->setEnabled(false);
ui->rankSelectButton3->setEnabled(false);
ui->rankSelectButton4->setEnabled(false);
RankAnswerQuestion(1);
}
void training::on_rankSelectButton2_clicked()
{
ui->rankSelectButton2->setStyleSheet("color : rgb(0, 170, 255)");
ui->rankSelectButton1->setEnabled(false);
ui->rankSelectButton2->setEnabled(false);
ui->rankSelectButton3->setEnabled(false);
ui->rankSelectButton4->setEnabled(false);
RankAnswerQuestion(2);
}
void training::on_rankSelectButton3_clicked()
{
ui->rankSelectButton3->setStyleSheet("color : rgb(0, 170, 255)");
ui->rankSelectButton1->setEnabled(false);
ui->rankSelectButton2->setEnabled(false);
ui->rankSelectButton3->setEnabled(false);
ui->rankSelectButton4->setEnabled(false);
RankAnswerQuestion(3);
}
void training::on_rankSelectButton4_clicked()
{
ui->rankSelectButton4->setStyleSheet("color : rgb(0, 170, 255)");
ui->rankSelectButton1->setEnabled(false);
ui->rankSelectButton2->setEnabled(false);
ui->rankSelectButton3->setEnabled(false);
ui->rankSelectButton4->setEnabled(false);
RankAnswerQuestion(4);
}
void training::on_pushButton_clicked()
{
ui->rank_Result->close();
ui->stackedWidget->setCurrentWidget(ui->mainMenu);
}
void training::on_RankWaitBackButton_clicked()
{
ui->rank_wait->close();
ui->stackedWidget->setCurrentWidget(ui->mainMenu);
QJsonObject json;
json["cmd"] = ABNORMALEXIT;
_com->writeData(json);
}
#include "login.h"
#include
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
/*
QJsonArray 封装 JSON 数组
QJsonDocument 读写 JSON 文档
QJsonObject 封装 JSON 对象
QJsonObject::iterator 用于遍历QJsonObject的 STL 风格的非 const 遍历器
QJsonParseError 报告 JSON 处理过程中出现的错误
QJsonValue 封装 JSON 值
*/
TEMPLATE = app
CONFIG += console c++11
CONFIG -= app_bundle
CONFIG -= qt
CONFIG += C++11
SOURCES += main.cpp \
thread.cpp \
tcpserver.cpp \
tcpsocket.cpp \
myserver.cpp \
db.cpp \
user.cpp
HEADERS += \
thread.h \
tcpserver.h \
tcpsocket.h \
myserver.h \
db.h \
user.h
//给linevent添加库(以及线程)
LIBS += -L/usr/local/lib -levent -lpthread -ljson -lmysqlclient
LIBS += -L/usr/lib/ -lspdlog
#ifndef DB_H
#define DB_H
#include "mysql/mysql.h"
#include
#include
#include
class DB
{
public:
DB(const char *host, const char *usrName,const char *passwd,const char * dbName);
//执行数据库语句
bool db_exec(const char *sql);
//数据库查询
//[int] sql //查询语句
//[out] outJson //查询结果保存到json变量中
bool db_select(const char *sql, Json::Value &outJson);
private:
std::mutex _mutex;
MYSQL *_mysql; //数据库句柄
};
#endif // DB_H
#ifndef MYSERVER_H
#define MYSERVER_H
#include "tcpserver.h"
#include
#include "db.h"
#include "json/json.h"
#include "json/reader.h"
#include "../common.h"
#include
#include
#ifndef TCPSERVER_H
#define TCPSERVER_H
#include "thread.h"
#include
#include
#include
#include
#include
#include
#include "tcpsocket.h"
#include "string.h"
class TcpSocket;
//Tcp服务的基类
class TcpServer
{
friend class TcpSocket;
public:
TcpServer(int threadNum = 8);
int listen(int port,const char *ip = NULL);
//服务器开始运行
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 *);
//----------------虚函数 去具体处理客户端的逻辑----------------
//客户端连接事件
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
#ifndef TCPSOCKET_H
#define TCPSOCKET_H
#include "tcpserver.h"
#include
class TcpServer;
//通信类,负责与客户端进行通信
class TcpSocket
{
public:
TcpSocket(TcpServer *tcpServer, struct bufferevent *bev, char *ip, u_int16_t port);
//在类中使用回调函数必须用static进行修饰
//可读事件回调函数
static void readEventCb(struct bufferevent *bev, void *ctx);//ctx就是那个s
//创建一个通信对象
//TcpSocket *s = new TcpSocket(this,bev,ip,port);
//可写事件回调函数
static void writeEventCb(struct bufferevent *bev, void *ctx);
//异常事件回调函数
static void closeEventCb(struct bufferevent *bev,short what, void *ctx);
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
#ifndef THREAD_H
#define THREAD_H
#include
#include
#include
#include
#include
#include
#include
#include
//线程类
class Thread
{
public:
Thread();
void start(); //线程运行
//获取事件集合
struct event_base *getBase();
protected:
static void* worker(void *); //线程的工作函数
static void pipeRead(evutil_socket_t,short,void *); //读取管道
void run(); //线程的逻辑处理函数
private:
struct event_base *m_base; //事件集合
pthread_t m_threadId; //线程ID
int m_pipeReadFd; //管道读端
int m_pipWirteFd; //管道写端
struct event m_pipeEvent; //管道事件
};
#endif // THREAD_H
#ifndef USER_H
#define USER_H
#include
#include
class User
{
public:
User(std::string n,std::string p,int rank,TcpSocket *s);
TcpSocket *getSocket();
const char *getName();
int getRank();
int setRank(int rank);
private:
std::string _usrName; // 用户名
std::string _passwd; // 用户密码
int _rank; // 分数
TcpSocket *_s; // 通信套接字
};
#endif // USER_H
#include "db.h"
DB::DB(const char *host, const char *usrName, const char *passwd, const char *dbName)
{
//初始化数据库句柄
_mysql = mysql_init(NULL);
if(_mysql == NULL)
{
spdlog::get("log")->error("mysql init error\n");
exit(-1);
}
//连接数据库服务器
MYSQL *con = mysql_real_connect(_mysql,host,usrName,passwd,dbName,0,NULL,0);
//第一个参数是数据库句柄
//第二个参数是主机host
//第三个参数是用户名
//第四个参数是密码
//第五个参数是所要打开的数据库,后面的参数就不需要了
if(con ==NULL)
{
spdlog::get("log")->error("数据库连接失败:{}",mysql_error(_mysql));
exit(-1);
}
_mysql = con;
//设置字符集,使其支持中文
int ret = mysql_query(_mysql,"set names utf8");
if(ret != 0)
{
spdlog::get("log")->error("设置字符集失败:{}",mysql_error(_mysql));
exit(-1);
}
}
bool DB::db_exec(const char *sql)
{
std::unique_lock loc(_mutex); //数据库句柄上锁
int ret = mysql_query(_mysql,sql);
//mysql_query() 函数执行一条 MySQL 查询。
if(ret != 0)
{
spdlog::get("log")->error("设置字符集失败:{}",mysql_error(_mysql));
return false;
}
return true;
}
bool DB::db_select(const char *sql, Json::Value &outJson)
{
std::unique_lock loc(_mutex); //数据库句柄上锁
int ret = mysql_query(_mysql,sql);
//mysql_query() 函数执行一条 MySQL 查询。
if(ret != 0)
{
spdlog::get("log")->error ("mysql_query error:%s",mysql_error(_mysql));
return false;
}
//从mysql服务器下载查询结果
MYSQL_RES *sql_res = mysql_store_result(_mysql);
if(NULL == sql_res)
{
if(mysql_errno(_mysql) == 0)
{
return true;
}
else
{
spdlog::get("log")->error ("mysql_store_result error:%s",mysql_error(_mysql));
}
}
MYSQL_ROW row; //从结果集中一行一行的取出数据
unsigned int num_fields = 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_fields; i++)
{
outJson[fetch_field[i].name].append(row[i]);
}
}
mysql_free_result(sql_res);
return true;
}
#include "myserver.h"
Myserver::Myserver()
{
#ifdef DEBUG
//_log = spdlog::stdout_color_mt("log");
auto console = spdlog::stdout_color_mt("log");
//函数创建一个名字为_log的console logger,
//把这个logger注册到spdlog的全局注册表中,并且返回指向这个logger的指针(shared_ptr)。
#else
_log = spdlog::rotating_logger_mt("log","log",1024*1024 * 5,3);
/*
rotating_logger_mt
第一个参数是句柄名称
第二个参数是文件名称
第三个是日志大小
第四个参数是多少个文件进行循环
*/
_log->flush_on(spdlog::level::info); //显示的权限等级
#endif
_db = new DB(NULL,"qt","qt","qt_online_answer");
initRankMap();
}
//客户端连接事件
void Myserver::connectEvent(TcpSocket *s)
{
spdlog::get("log")->info("有一个新连接:[{}:{}]",s->getIp(),s->getPort());
}
//客户端可读
void Myserver::readEvent(TcpSocket *s)
{
char buf[1024] = {0};
memset(buf,0,sizeof(buf));
while(1)
{
int len = 0;
s->readData(&len,sizeof(len));
if(len <= 0)
{
break;
}
s->readData(buf,len);
//数据解析
Json::Value root;
Json::Reader reader; //json解析器
//将buf转化成root形式
if(!reader.parse(buf,root))
{
spdlog::get("log")->error("json 数据解析失败");
return;
}
int cmd = root["cmd"].asInt();
switch (cmd)
{
case REGISTER:
Register(s,root);
break;
case LOGIN:
Login(s,root);
break;
case SINGLEGETQUESTION:
singleGetQuestion(s);
break;
case RANK:
Rank(s);
break;
case ANSWER:
RankAnswerQuestion(s,root);
break;
case RANKRESULT:
RankResult(s,root);
break;
default:
break;
}
}
}
//可写事件
void Myserver::writeEvent(TcpSocket *)
{
}
//关闭事件
void Myserver::closeEvent(TcpSocket *s,short)
{
{
std::unique_lock lock(_rankLock);
int rank = _users[s->getUserName()]->getRank();
auto it = _rankQueue.find(rank);
if(it != _rankQueue.end())
{
_rankQueue.erase(it);
}
}
std::unique_lock lock(_userLock);
std::map::iterator it = _users.begin();
while (it != _users.end())
{
if(it->second->getSocket() == s)
{
_users.erase(it);
spdlog::get("log")->info("用户{}[{}:{}] log out",it->second->getName(),s->getIp(),s->getPort());
//释放User
delete it->second;
return;
}
it++;
}
spdlog::get("log")->info("[{}:{}] log out",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[1024] = {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;
spdlog::get("log")->error("Register select user error");
}
if(outJson.isMember("name")) //用户存在,表明已经注册过了
{
result = USEREXIST;
}
else
{
memset(sql,0,sizeof(sql));
sprintf(sql,"insert into user(name,passwd,rank) values('%s','%s',0)",userName.c_str(),passwd.c_str());
bool ret = _db->db_select(sql, outJson);
if(!ret)
{
result = ERROR;
spdlog::get("log")->error("Register insert user error");
}
else
{
spdlog::get("log")->info("Register user = {} success",userName);
}
}
Json::Value json;
json["cmd"] = REGISTER;
json["result"] = result;
writeData(s,json);
}
void Myserver::Login(TcpSocket *s,const Json::Value &inJson)
{
std::string userName = inJson["userName"].asString();
std::string passwd = inJson["passwd"].asString();
//int rank = inJson["rank"].asInt();
int i = 0;
int rank;
//检测用户是否已经注册
char sql[1024] = {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;
spdlog::get("log")->error("Login select user error");
}
if(outJson.isMember("name")) //用户存在,表明已经注册过了
{
std::unique_lock lock(_userLock);
if(_users.find(userName) != _users.end()) //用户已经登陆
{
result = USERLOGIN;
spdlog::get("log")->info("用户{}[{}:{}] 已经登陆",userName,s->getIp(),s->getPort());
}
else
{
rank = atoi(outJson["rank"][i].asString().c_str());
User* user = new User(userName,passwd,rank,s);
_users.insert(make_pair(userName,user));
s->setUserName(userName);
spdlog::get("log")->info("用户{}[{}:{}] login",userName,s->getIp(),s->getPort());
}
}
else
{
result = NAMEORPASSWD;
}
Json::Value json;
json["cmd"] = LOGIN;
json["result"] = result;
json["userName"] = userName;
json["rank"] = rank;
json["grade"] = _rankMap[rank];
//spdlog::get("log")->info("用户rank:{}\n",rank);
writeData(s,json);
}
void Myserver::singleGetQuestion(TcpSocket *s)
{
char sql[1024] = {0};
memset(sql,0,sizeof(sql));
sprintf(sql,"select * from question order by rand() limit %d",QUESTION_NUM);
int result = OK;
Json::Value outJson;
bool ret = _db->db_select(sql, outJson);
if(!ret)
{
result = ERROR;
spdlog::get("log")->error("Login select user error");
}
Json::Value json;
json["cmd"] = SINGLEGETQUESTION;
json["result"] = result;
json["question"] = outJson;
spdlog::get("log")->info("用户{}[{}:{}] 获取题目:{}\n",s->getUserName(),s->getIp(),s->getPort(),json.toStyledString());
writeData(s,json);
}
//-----------------rank-------------------
//初始化段位列表
void Myserver::initRankMap()
{
char buf[100];
int rank = 0;
int star = 0;
for(int i = 0;i < 100; i++)
{
memset(buf,0,sizeof(buf));
if(i < 9)
{
rank = i / 3;
star = i % 3;
sprintf(buf,"青铜%d %d颗星", 3-rank, star+1);
}
else if(i < 18)
{
rank = (i - 9) / 3;
star = (i - 9) % 3;
sprintf(buf,"白银%d %d颗星", 3-rank, star+1);
}
else if(i < 34)
{
rank = (i - 18) / 4;
star = (i - 18) % 4;
sprintf(buf,"黄金%d %d颗星", 4-rank, star+1);
}
else if(i < 50)
{
rank = (i - 34) / 4;
star = (i - 34) % 4;
sprintf(buf,"铂金%d %d颗星", 4-rank, star+1);
}
else if(i < 75)
{
rank = (i - 50) / 5;
star = (i - 50) % 5;
sprintf(buf,"钻石%d %d颗星", 5-rank, star+1);
}
else if(i < 100)
{
rank = (i - 75) / 5;
star = (i - 75) % 5;
sprintf(buf,"星曜%d %d颗星", 5-rank, star+1);
}
_rankMap.insert(std::make_pair(i,buf));
}
// for(int i = 0;i < 100; i++)
// {
// std::cout<< i <<":"<<_rankMap[i]<info("cmd:{}enemyScore:{}enemyQuestionId:{}",ANSWER,inJson["score"].asInt(),inJson["questionId"].asInt());
writeData(user->getSocket(),json);
}
//开始排位
void Myserver::Rank(TcpSocket *s)
{
TcpSocket *other = NULL; //对手
int rank = _users[s->getUserName()]->getRank(); //当前用户 rank 积分
std::unique_lock lock(_rankLock);
//查找同一段位的对手
std::map::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())
{
//spdlog::get("log")->info("it->first:{}\n",it->first);
other = it->second;
_rankQueue.erase(it);
break;
}
it = _rankQueue.find(rank - i);
if(it != _rankQueue.end())
{
//spdlog::get("log")->info("it->first:{}\n",it->first);
other = it->second;
_rankQueue.erase(it);
break;
}
}
}
spdlog::get("log")->info("当前分数:{}",rank);
if(other == NULL) //没有匹配到用户
{
_rankQueue.insert(std::make_pair(rank,s));
spdlog::get("log")->info("当前等候 rank 人数:{}",_rankQueue.size());
}
else //找到
{
startRank(s,other);
//开始对决
}
}
//开始对决
void Myserver::startRank(TcpSocket *first,TcpSocket *second)
{
char sql[1024] = {0};
memset(sql,0,sizeof(sql));
sprintf(sql,"select * from question order by rand() limit %d",QUESTION_NUM);
int result = OK;
Json::Value outJson;
bool ret = _db->db_select(sql, outJson);
if(!ret)
{
result = ERROR;
spdlog::get("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;
//spdlog::get("log")->info("enemyName = {},enemyRank = {},enemyScore = {}",second->getUserName(),_rankMap[_users[second->getUserName()]->getRank()],_users[second->getUserName()]->getRank());
writeData(first,json);
//second user
json["enemyName"] = first->getUserName();
json["enemyRank"] = _rankMap[_users[first->getUserName()]->getRank()];
writeData(second,json);
spdlog::get("log")->info("获取题目:{}\n",json.toStyledString());
}
//rank结果
void Myserver::RankResult(TcpSocket *s,const Json::Value &inJson)
{
std::unique_lock 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);
}
#include "tcpserver.h"
TcpServer::TcpServer(int threadNum):m_nextThread(0)
{
if(threadNum <= 0)
{
printf("threadNum <= 0\n");
exit(-1);
}
//创建线程池
m_threadNum = threadNum;
m_threadPool = new Thread[m_threadNum];
if(m_threadPool == NULL)
{
printf("create threadPool error!\n");
exit(-1);
}
m_base = event_base_new();
if(!m_base)
{
printf("Clouldn't create an event_base:exiting\n");
exit(-1);
}
}
void TcpServer::listenCb(struct evconnlistener *,evutil_socket_t fd,struct sockaddr *clientAdd,int ,void *date)
{
//第一个参数是listen的监听事件
//第二个参数与客户端通信的文件描述符
//第三个参数客户端的IP地址
//第五个参数是回调函数参数
TcpServer *p = (TcpServer *)date;
p->listenEvent(fd,(struct sockaddr_in *)clientAdd);
}
void TcpServer::listenEvent(evutil_socket_t fd,struct sockaddr_in *clientAdd)
{
char *ip = inet_ntoa(clientAdd->sin_addr); //客户端的IP地址
uint16_t port = ntohs(clientAdd->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);
//功能:创建一个用于socket的bufferevent,用来缓存套接字读或写的数据,然后调用对应的回调函数
//base:表示event_base
//options:是bufferevent选项的位掩(BEV_OPT_CLOSE_ON_FREE等)。
//fd:参数是一个可选的socket文件描述符。如果希望以后再设置socket文件描述符,可以将fd置为-1。
//bev客户端句柄
if(!bev)
{
printf("Error constructing bufferevent!");
event_base_loopbreak(base);
return;
}
//创建一个通信对象
TcpSocket *s = new TcpSocket(this,bev,ip,port);
//单独封装一个类负责和客户端的通信
bufferevent_setcb(bev,s->readEventCb,s->writeEventCb,s->closeEventCb,s);
//该函数的作用主要是赋值,把该函数后面的参数,赋值给第一个参数struct bufferevent *bufev定义的变量
bufferevent_enable(bev, EV_WRITE);
bufferevent_enable(bev, EV_READ);
bufferevent_enable(bev, EV_SIGNAL);//打开(读、写、信号)三个开关
//调用客户端连接事件
connectEvent(s);
}
int TcpServer::listen(int port,const char *ip)
{
struct sockaddr_in sin;
memset(&sin,0,sizeof(sin));
/*
sin_family主要用来定义是哪种地址族
sin_port主要用来保存端口号
sin_addr主要用来保存IP地址信息
*/
sin.sin_family = AF_INET; //指定IP地址地址版本人为IPV4
sin.sin_port = htons(port); //存放端口信息
if(ip != NULL)
{
//功能:将一个字符串表示的点分十进制IP地址IP转换为网络字节序存储在addr中,并且返回该网络字节序表示的无符号整数。
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));
/*
base: event_base对象。参考event_base。
cb: 回调函数, 当有新的TCP连接发生时,会唤醒回调函数。
ptr: 传递给回调函数的参数。
flags: 一些标志, 后面会进一步介绍。
//LEV_OPT_CLOSE_ON_FREE:释放listener时,先关闭潜在的套接字
//LEV_OPT_REUSEABLE:一些系统平台在默认情况下即使已经关闭了socket,也要等待一段时间才能使用对应的port,
//而这个flag则设置成只要已关闭socket就是重新使用对应的port
backlog: 监听队列允许容纳的最大连接数。
sa: evconnlistener_new_bind帮助我们绑定监听地址。sa就是传入的监听地址。
socklen: sa的长度。
*/
if(!m_listener)
{
printf("Couldn't create alistener\n");
exit(1);
return -1;
}
//开启线程池
for(int i = 0;i
#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(struct bufferevent *, void *ctx)
{
TcpSocket *s = (TcpSocket *)ctx;
m_tcpServer->readEvent(s);
}
//可写事件回调函数
void TcpSocket::writeEventCb(struct bufferevent *, void *ctx)
{
TcpSocket *s = (TcpSocket *)ctx;
m_tcpServer->writeEvent(s);
}
//异常事件回调函数
void TcpSocket::closeEventCb(struct bufferevent *,short what, void *ctx)
{
TcpSocket *s = (TcpSocket *)ctx;
m_tcpServer->closeEvent(s,what);
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;
}
#include "thread.h"
Thread::Thread()
{
m_base = event_base_new();
if(!m_base)
{
printf("Clouldn't create an event_base:exiting\n");
exit(-1);
}
//创建管道
int fd[2];
if(pipe(fd) == -1)
{
perror("pipe");
exit(-1);
}
m_pipeReadFd = fd[0];
m_pipWirteFd = fd[1];
//让管道事件监听管道读端
//如果监听到 管道的读端有数据可读
event_set(&m_pipeEvent,m_pipeReadFd,EV_READ | EV_PERSIST,pipeRead,this);
//这就是主线程,不仅可以监听,而且可以防止base里面事件不为空;
//将事件添加到m_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,NULL,worker,this);
//第一参数线程ID
//属性一般为NULL
//线程函数(线程要去执行的函数)
//传给线程函数的参数
//线程分离
pthread_detach(m_threadId);
}
void* Thread::worker(void *arg)
{
Thread *p = (Thread *)arg;
p->run();
// sleep(10);
return NULL;
}
void Thread::run()
{
// while(1)
// {
// printf("thread = %d\n",m_threadId);
// sleep(2);
// }
//printf("%d:start",m_threadId);
//监听base事件集合 event_base_dispatch 死循环 类似Qt 的exec()
//如果 m_base 事件集合内部是空的花,则event_base_dispatch会立马返回
//初始化的时候,需要给m_base
event_base_dispatch(m_base);
event_base_free(m_base);
printf("%d:close",m_threadId);
return ;
}
struct event_base *Thread::getBase()
{
return m_base;
}
#include "user.h"
User::User(std::string n,std::string p,int rank,TcpSocket *s):
_usrName(n),
_passwd(p),
_rank(rank),
_s(s)
{
}
TcpSocket *User::getSocket()
{
return _s;
}
const char *User::getName()
{
return _usrName.c_str();
}
int User::getRank()
{
return _rank;
}
int User::setRank(int rank)
{
if(rank <= 0)
{
_rank = 0;
}
_rank = rank;
}
#include
#include "thread.h"
#include "tcpserver.h"
#include "myserver.h"
using namespace std;
int main1()
{
// cout << "Hello World!" << endl;
Thread *pt1 = new Thread;
Thread *pt2 = new Thread;
pt1->start();
pt2->start();
//printf("11111111\n");
while (1) {
printf("11111111\n");
sleep(1);
}
return 0;
}
int main2()
{
TcpServer s;
s.listen(9999);
s.start();
return 0;
}
int main()
{
Myserver s;
s.listen(9999);
s.start();
return 0;
}
#ifndef COMMON_H
#define COMMON_H
//5XXX 用户操作指令
#define OK 5000
#define REGISTER 5001 //注册
#define LOGIN 5002 //登录
#define SINGLEGETQUESTION 5003 //个人训练获取问题
#define RANK 5004 //进入排位模式
#define ANSWER 5005 //排位回答一个问题
#define RANKRESULT 5006 //排位结果
//8XXX 错误
#define ERROR 8001 //未知错误
#define USEREXIST 8002 //用户已经存在
#define NAMEORPASSWD 8003 //用户名或者密码错误
#define USERLOGIN 8004 //用户已经登陆
#define ABNORMALEXIT 8005 //用户异常退出
#endif
Qt入门(九)——“头脑风暴”(在线答题