基于Linux C++ windows qt开发的在线答题系统

一、项目描述:

 实现功能

          用户的注册、登录,个人答题训练、排位赛(网络匹配对战)、排位系统

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颗星        

三、效果演示

录屏

四、服务器代码段展示

        服务器基于linux下的Qt开发, 需要配置环境: json   、 libev、spdlog(用于记录错误信息)

数据库单独封装成一个类

db.h

#ifndef DB_H
#define DB_H

#include "mysql/mysql.h"
#include 
#include "spdlog/spdlog.h"
#include 

class DB
{
public:
    DB(const char *host, const char *userName,const char *passwd,const char *dbName);

    //执行数据库语句
    bool db_exec(const char *sql);
    //数据库查询
    //[int]  sql       查询语句
    //[out]  outJson   查询结果保存到json变量中
    bool db_slect(const char *sql,Json::Value &outJson);



private:
    std::mutex _mutex;   //数据库句柄互斥锁
    MYSQL *_mysql;       //数据库的句柄


};

#endif // DB_H

myserver.h

#ifndef MYSERVER_H
#define MYSERVER_H

#include "tcpserver.h"
#include 
#include "db.h"
#include 
#include 
#include "../common.h"
#include 
#include 
#include "user.h"
#include

#define DEBUG


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 singleGetQuestion(TcpSocket *s);

    //进行排位
    void Rank(TcpSocket *s);

    //开始对决
    void startRank(TcpSocket *first,TcpSocket *second);

    //rank  结果
    void rankResult(TcpSocket *s,const Json::Value &inJson);

    //初始化rank积分对照表
    void initRankMap();

    //rank 回答一道问题
    void rankAnswerOneQuestion(TcpSocket *s,const Json::Value &inJson);

private:
    std::shared_ptr _log;   //记录日志的句柄
    DB *_db;  //数据库句柄

    //健是用户名, 值是用户指针
    std::mutex _userLock;
    std::map _users;    //在线用户列表

    //key   rank积分
    //值     对应的段位说明
    std::map  _rankMap;   //rank积分对比表


    //key    :   rank积分
    //value  :   参与  rank的用户socket句柄
    std::mutex _ranLock;
    std::map    _rankQueue; //等待排位的队列

};

#endif // MYSERVER_H

tcpserver.h

#ifndef TCPSERVER_H
#define TCPSERVER_H

#include "thread.h"
#include 
#include 
#include 
#include 

#include 
#include 
#include "tcpsocket.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

tcpsocket.h

#ifndef TCPSOCKET_H
#define TCPSOCKET_H
#include "tcpserver.h"
#include 

class TcpServer;
//通信类,负责与客户端进行通信
class TcpSocket
{
public:
    TcpSocket(TcpServer *server,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 *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

thread.h

#ifndef THREAD_H
#define THREAD_H
#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_pipeWriteFd;        //管道的写段
    struct event m_pipeEvent;   //管道事件
};

#endif // THREAD_H

user.h

#ifndef USER_H
#define USER_H
#include "tcpsocket.h"
#include 

class User
{
public:
    User(std::string n,std::string p, int rank, 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

db.cpp

#include "db.h"

DB::DB(const char *host, const char *userName,const char *passwd,const char *dbName)
{
    //初始化数据库句柄
    _mysql = mysql_init(NULL);
    if(_mysql == NULL)
    {
        spdlog::get("BrainStorm")->error ("mysql_init error\n");
        exit(-1);
    }
    //连接mysql服务器
    MYSQL *con = mysql_real_connect(_mysql,host,userName,passwd,dbName,0,NULL,0);
    if(con == NULL)
    {
        spdlog::get("BrainStorm")->error ("连接数据库失败: {}",mysql_error(_mysql));
        exit(-1);
    }
    _mysql = con;

    //设置字符集
    int ret = mysql_query(_mysql, "set names utf8");
    if(ret != 0)
    {
        spdlog::get("BrainStorm")->error("设置字符集失败:%s",mysql_error(_mysql));
        exit(-1);
    }
}

//执行数据库语句
bool DB::db_exec(const char *sql)
{
    std::unique_lock loc(_mutex);  //数据库句柄上锁
    int ret = mysql_query(_mysql, sql);
    if(ret != 0)
    {
        spdlog::get("BrainStorm")->error("mysql_query error: %s",mysql_error(_mysql));
        return false;
    }
    return true;
}
//数据库查询
bool DB::db_slect(const char *sql,Json::Value &outJson)
{
    std::unique_lock loc(_mutex);  //数据库句柄上锁
    int ret = mysql_query(_mysql, sql);

    if(ret != 0)
    {
        spdlog::get("BrainStorm")->error("mysql_query error: %s",mysql_error(_mysql));
        return false;
    }
    //从mysql服务器下载查询结果
    MYSQL_RES *sql_res = mysql_store_result(_mysql);
    if(sql_res == NULL)
    {
        if(mysql_errno(_mysql) == 0)
        {
            return true;
        }
        else
        {
            spdlog::get("BrainStorm")->error("mysql_query 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;
}

main.cpp

#include 
#include "thread.h"
#include "tcpserver.h"
#include "myserver.h"
using namespace std;



int main2()
{
    TcpServer s;
    s.listen(9999);
    s.start();
    return 0;
}

int main()
{
    MyServer s;
    s.listen(9999);
    s.start();
    return 0;
}

myserver.cpp

#include "myserver.h"

MyServer::MyServer()
{
#ifdef DEBUG
    _log = spdlog::stdout_color_mt("BrainStorm");
#else
    _log = spdlog::rotating_logger_mt("BrainStorm","BrainStorm",1024*1024*5,3);
    _log->flush_on(spdlog::level::info);
#endif
    _db = new DB(NULL,"xiaowang","xiaowang","BrainStorm");//第一个参数:主机-->NULL 默认当前主机

    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::Value root;
         Json::Reader reader;   //json解析器
         if(!reader.parse(buf,root))
         {
             _log->error("json 数据解析失败");
             return;
         }
         int cmd = root["cmd"].asInt();
         switch (cmd)
         {
         case REGISTER:                 //注册
             Register(s,root);
             break;
         case LOGIN:                    //登录
             Login(s,root);
             break;
         case SINGLE_GETQUESTION:       //个人训练
             singleGetQuestion(s);
             break;
         case RANK:                     //排位匹配
             Rank(s);
             break;
         case ANSWER:
             rankAnswerOneQuestion(s,root);
             break;
         case RANKRESULT:
             rankResult(s,root);
             break;
         default:
             break;
         }
     }

     memset(buf,0,sizeof(buf));
 }

//可写事件
void MyServer::writeEvent(TcpSocket *)
{

}
//关闭事件
void MyServer::closeEvent(TcpSocket *s,short )
{
    //将用户从 等待  rank 的队列中删除
    {
        std::unique_lock _lock(_ranLock);
        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);
            _log->info("用户{}[{}:{}] 用户退出",it->second->getName(),s->getIp(),s->getPort());

            //释放User
            delete it->second;
            return;
        }
        it++;
    }
    _log->info("[{}:{}] logout",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_slect(sql, outJson);
    if(!ret)
    {
        result = ERROR;
        _log->error("Register select user 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("Register insert user 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)
{
    std::string userName = inJson["userName"].asString();
    std::string passwd = inJson["passwd"].asString();

    int rank = 0;
    //检测用户是否注册过
    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_slect(sql, outJson);
    if(!ret)
    {
        result = ERROR;
        _log->error("Register select user error");

    }

    if(outJson.isMember("name"))  //用户存在,表明已经注册过了
    {
        std::unique_lock _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("用户{}[{}:{}] login",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::singleGetQuestion(TcpSocket *s)
{
    char sql[100] = {0};
    sprintf(sql,"select * from question order by rand() limit %d",QUESTION_NUM);



    int result = OK;
    Json::Value outJson;
    bool ret = _db->db_slect(sql, outJson);
    if(!ret || outJson["question"].size() != QUESTION_NUM)
    {
        result = ERROR;
        _log->error("singleGetQuestion select question error");

    }

    Json::Value json;
    json["cmd"] = SINGLE_GETQUESTION;
    json["result"] = result;
    json["question"] = outJson;

    _log->info("用户{}[{}:{}] 获取题目:{}\n",s->getUserName(),s->getIp(),s->getPort(),json.toStyledString());

    writeData(s,json);
}

//开始对决
void MyServer::startRank(TcpSocket *first,TcpSocket *second)
{
    char sql[100] = {0};
    sprintf(sql,"select * from question order by rand() limit %d",QUESTION_NUM);

    std::cout<<"*******1**************"<db_slect(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()];
    json["enemyScore"] = 0;
    std::cout<<"*******2**************"<info("获取 rank 题目 :  {}\n",json.toStyledString());


}


//进行排位
void MyServer::Rank(TcpSocket *s)
{
    TcpSocket *other = NULL;   //对手

    int rank = _users[s->getUserName()]->getRank();    //当前用户    rank  积分


    std::unique_lock lock(_ranLock);
    //查找统一段位的对手
    std::cout<<"1111111111111"<::iterator it = _rankQueue.find(rank);
    if(it != _rankQueue.end())
    {
        std::cout<<"222222222222"<second;
        _rankQueue.erase(it);
    }
    else
    {
        std::cout<<"333333333333"<second;
                _rankQueue.erase(it);
                break;
            }
            it = _rankQueue.find(rank-i);
            if(it != _rankQueue.end())
            {
                other = it->second;
                _rankQueue.erase(it);
                break;
            }
        }
    }

     _log->info("  rank 分数 :  {}\n",rank);
    if(other == NULL)  //没有匹配到用户
    {
        _rankQueue.insert(std::make_pair(rank, s));
        _log->info("当前等侯  rank  人数 :  {}\n",_rankQueue.size());
    }
    else    //找到
    {
        //开始对决
        startRank(s,other);
    }
}

//rank 回答一道问题
void MyServer::rankAnswerOneQuestion(TcpSocket *s,const Json::Value &inJson)
{
   std::string enemyName = inJson["enemyName"].asString();
   User* user = _users[enemyName];

   Json::Value json;
   json["cmd"]             = ANSWER;
   json["enemyscore"]      = inJson["score"].asInt();
   json["enemyQuestionId"] = inJson["questionId"].asInt();

   writeData(user->getSocket(), json);
}

//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);
    }

    char sql[100] = {0};
    sprintf(sql,"UPDATE user SET rank = %d WHERE name = '%s'",user->getRank(),user->getName());
    _db->db_exec(sql);


    Json::Value json;
    json["cmd"]     = RANKRESULT;
    json["newRank"] = _rankMap[user->getRank()];


    writeData(s, json);


}

//初始化rank积分对照表
void MyServer::initRankMap()
{
    char buf[100] = {0};
    int rank = 0;
    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<

tcpserver.cpp

#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[threadNum];
    if(m_threadPool == NULL)
    {
        printf("create threadPool error\n");
        exit(-1);
    }

    m_base = event_base_new();
    if(!m_base)
    {
        printf("Could not initialize libevent\n");
        exit(-1);
    }
}

void TcpServer::listenCb(struct evconnlistener *,evutil_socket_t fd,struct sockaddr *clientAdd,int ,void *data)
{
    TcpServer *p = (TcpServer*)data;
    p->listenEvent(fd,(struct sockaddr_in *)clientAdd);
}

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("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);
    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.sin_family = AF_INET;
    sin.sin_port = htons(port);
    if(ip!= NULL)
    {
        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("Could not create a listener!\n");
        return -1;
    }
    //开始线程池
    for(int i = 0; i < m_threadNum; i++)
    {
        m_threadPool[i].start();
        printf("线程 %d 启动\n", i+1);
    }
    return 0;
}

void TcpServer::start()
{
    event_base_dispatch(m_base);

    evconnlistener_free(m_listener);
    event_base_free(m_base);

    printf("done\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(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()   //获取IP地址
{
    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();   //新建一个事件集合
    if (!m_base)
    {
        printf("Couldn'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_pipeWriteFd = fd[1];

    //让管道事件监听管道的读端
    //如果监听到,管道的读端数据可读
    event_set(&m_pipeEvent,m_pipeReadFd,EV_READ|EV_PERSIST,pipeRead,this);  //pipeRead  回调函数
    //将事件添加到 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);

    //线程分离
    pthread_detach(m_threadId);

}
void* Thread::worker(void* arg)    //线程的工作函数
{
    Thread* p = (Thread*)arg;
    p->run();
    return NULL;

}
void Thread::run()
{
    //printf("%d: start\n",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: done\n",m_threadId);
}
struct event_base *Thread::getBase()
{
    return m_base;
}

user.cpp

#include "user.h"

User::User(std::string n,std::string p, int rank, TcpSocket *s)
{
    _userName = n;
    _passwd = p;
    _rank = rank;
    _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;
}

五、客户端代码段展示

        客户端是基于windows下Qt开发的

brainstorm.h

#ifndef BRAINSTORM_H
#define BRAINSTORM_H

#include 
#include "communicate.h"
#include 
#include 
#include 
#include 
namespace Ui {
class BrainStorm;
}

class BrainStorm : public QDialog
{
    Q_OBJECT

public:
    explicit BrainStorm(Communicate *_com, QJsonObject &json, QWidget *parent = 0);
    ~BrainStorm();

private:
    //单人训练从服务器获取题目
    void singleGetQuestion();

    //单人训练设置问题
    void singleSetQuestion();

    //单人训练答题
    void singleAnswerQuestion(int select);

    //设置得分
    void setEnemyScore();
    void setSelfScore();

    //rank设置问题
    void rankSetQuestion();

    //rank答题
    void rankAnswerQuestion(int select);

    //rank结果
    void rankSetResult(QJsonObject &json);

private slots:
    void on_singleButton_clicked();

    void on_single_back_clicked();

    void on_single_start_clicked();

    //单人训练从服务器接收到题目
    void receiveSingleQuestion(QJsonObject json);

    void singleTimerOut();

    void on_pushButton_clicked();

    void on_singleSelectButton1_clicked();

    void on_singleSelectButton2_clicked();

    void on_singleSelectButton3_clicked();

    void on_singleSelectButton4_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_2_clicked();

private:
    Ui::BrainStorm *ui;

    Communicate * _com;  //通信类指针   负责和服务器通信

    QJsonObject _singleQuestion;
    int _currentSingleQuestion;     //当前回答哪一个问题的下标
    QTimer _singleTimer;            //个人训练定时器
    int _singleSec;                 //个人训练答题时间
    int _singleScore;               //个人得分


    //-----------------------------rank-------------------------------------

    QJsonObject _rankQuestion;    //rank题
    QString _enemyName;           //对手的名字
    QString _enemyRank;           //对手的段位
    int     _enemyScore;          //对手的得分

    int _rankSec;                 //rank 答题计时
    int _myScore;                 //自己的得分

    QString _userName;           //自己的用户名
    QString _userRank;           //自己的段位
    QString _oldRank;

    int _currentRankQuestion;    //当前回答哪一个问题的下标
    int _enemyRankQuestion;      //对手当前问题的下标

    QTimer _rankTimer;            //rank定时器

};

#endif // BRAINSTORM_H

communicate.h

#ifndef COMMUNICATE_H
#define COMMUNICATE_H

#include 
#include 
#include 
#include 
#include 
#include "../common.h"

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 json);   //接收单人训练的信号

    void Rank(QJsonObject json);

public slots:
    void readData();

private:
    QTcpSocket s;
};

#endif // COMMUNICATE_H

login.h

#ifndef WIDGET_H
#define WIDGET_H

#include 
#include "communicate.h"
#include "brainstorm.h"
#include "../common.h"

namespace Ui {
class Widget;
}

class Widget : public QWidget
{
    Q_OBJECT

public:
    explicit Widget(QWidget *parent = 0);
    ~Widget();

private slots:
    void on_LoginButton_clicked();

    void on_RegisterButton_clicked();

    void login(QJsonObject json);

private:
    Ui::Widget *ui;

    Communicate * _com;  //通信类指针   负责和服务器通信
};

#endif // WIDGET_H

register.h

#ifndef REGISTER_H
#define REGISTER_H

#include 
#include 
#include "communicate.h"
#include "../common.h"

namespace Ui {
class Register;
}

class Register : public QDialog
{
    Q_OBJECT

public:
    explicit Register(Communicate *com,QWidget *parent = 0);
    ~Register();

private slots:
    void on_NowRegisterButton_clicked();

    void on_ReturnButton_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();

    _oldRank = _userRank;


    connect(&_singleTimer,SIGNAL(timeout()),this,SLOT(singleTimerOut()));
    connect(&_rankTimer,SIGNAL(timeout()),this,SLOT(rankTimerOut()));

    connect(_com, SIGNAL(receiveSingleQuestion(QJsonObject)), this, SLOT(receiveSingleQuestion(QJsonObject)));

    connect(_com, SIGNAL(Rank(QJsonObject)), this, SLOT(Rank(QJsonObject)));
}

BrainStorm::~BrainStorm()
{
    delete ui;
}

//个人训练   计时器
void BrainStorm::singleTimerOut()
{
    --_singleSec;
    if(_singleSec == 0)
    {
        ui->singleResult->setText("回答错误");
        QString str = QString("本次得分: %1").arg(_singleScore);
        ui->singleScore->setText(str);
        _singleTimer.stop();
        ui->stackedWidget->setCurrentWidget(ui->single_score);
    }
    ui->lcdNumber->display(_singleSec);
}

//个人训练获取问题
void BrainStorm::singleGetQuestion()
{
    QJsonObject json;
    json["cmd"] = SINGLE_GETQUESTION;

    _com->writeData(json);
}

//个人训练设置题目
void BrainStorm::singleSetQuestion()
{
   ui->singleQuestion->setText(_singleQuestion["question"].toArray().at(_currentSingleQuestion).toString());
   ui->singleSelectButton1->setText(_singleQuestion["selection1"].toArray().at(_currentSingleQuestion).toString());
   ui->singleSelectButton2->setText(_singleQuestion["selection2"].toArray().at(_currentSingleQuestion).toString());
   ui->singleSelectButton3->setText(_singleQuestion["selection3"].toArray().at(_currentSingleQuestion).toString());
   ui->singleSelectButton4->setText(_singleQuestion["selection4"].toArray().at(_currentSingleQuestion).toString());
}

//单人训练答题
void BrainStorm::singleAnswerQuestion(int select)
{
    if(select == _singleQuestion["answer"].toArray().at(_currentSingleQuestion).toString().toInt())
    {
        _singleScore += 20*_singleSec;
        _currentSingleQuestion++;
        singleSetQuestion();    //设置下一题
        _singleSec = 10;
        _singleTimer.stop();
        ui->lcdNumber->display(_singleSec);
        _singleTimer.start(1000);
    }
    else    //回答错误
    {
        ui->singleResult->setText("回答错误");
        QString str = QString("本次得分: %1").arg(_singleScore);
        ui->singleScore->setText(str);
        ui->stackedWidget->setCurrentWidget(ui->single_score);
        _singleTimer.stop();
    }

    //题目结束
    if(_currentSingleQuestion == QUESTION_NUM)
    {
        ui->singleResult->setText("恭喜全部答对");
        QString str = QString("本次得分: %1").arg(_singleScore);
        ui->singleScore->setText(str);
        ui->stackedWidget->setCurrentWidget(ui->single_score);
        _singleTimer.stop();
    }
}

//个人训练接收题目
void BrainStorm::receiveSingleQuestion(QJsonObject json)
{
    _singleQuestion = json;
    //个人训练答题时间
    _singleSec = 10;
    _currentSingleQuestion = 0;
    _singleScore = 0;
    ui->lcdNumber->display(_singleSec);
    singleSetQuestion();

    //开启定时器
    _singleTimer.start(1000);

    ui->stackedWidget->setCurrentWidget(ui->single_running);
}


//进入个人训练
void BrainStorm::on_singleButton_clicked()
{
    ui->stackedWidget->setCurrentWidget(ui->single_menu);
}

void BrainStorm::on_single_back_clicked()
{
    ui->stackedWidget->setCurrentWidget(ui->mainMenu);
}

//开始答题
void BrainStorm::on_single_start_clicked()
{
    singleGetQuestion();
}

void BrainStorm::on_pushButton_clicked()
{
    ui->stackedWidget->setCurrentWidget(ui->mainMenu);
}

void BrainStorm::on_singleSelectButton1_clicked()
{
    singleAnswerQuestion(1);
}

void BrainStorm::on_singleSelectButton2_clicked()
{
    singleAnswerQuestion(2);
}

void BrainStorm::on_singleSelectButton3_clicked()
{
    singleAnswerQuestion(3);
}

void BrainStorm::on_singleSelectButton4_clicked()
{
    singleAnswerQuestion(4);
}

// -----------------------------------rank----------------------------------------
void BrainStorm::on_rankButton_clicked()
{
    QJsonObject json;
    json["cmd"] = RANK;    //进行排位匹配

    _com->writeData(json);

    //跳到等待页面
    ui->stackedWidget->setCurrentWidget(ui->rank_wait);

}

void BrainStorm::rankTimerOut()
{
    _rankSec--;
    if(_rankSec == 0)
    {
        if(ui->rankSelectButton1->isEnabled())
        {
            _currentRankQuestion++;
        }
        rankSetQuestion();
        _rankSec = 10;
    }
    ui->lcdNumber_2->display(_rankSec);
}

//设置得分
void BrainStorm::setEnemyScore()
{
    QString str = QString("%1(%2): %3").arg(_enemyName,-5).arg(_enemyRank).arg(_enemyScore);
    ui->enemyStatus->setText(str);
}

void BrainStorm::setSelfScore()
{

    QString str = QString("%1(%2): %3").arg(_userName,-5).arg(_userRank).arg(_myScore);
    ui->selfStatus->setText(str);
}

//rank设置问题
void  BrainStorm::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)
    {


        _rankTimer.stop();
        //将结果发给服务器

        QJsonObject json;
        json["cmd"]        = RANKRESULT;    //rank结束  发送对局结果
        json["score"]      = _myScore;
        json["enemyName"]  = _enemyName;
        json["enemyScore"] = _enemyScore;

        _com->writeData(json);

    }

}

//rank答题
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);
        rankSetQuestion();

    }
    QJsonObject json;
    json["cmd"]        = ANSWER;     //排位回答同一个问题
    json["enemyName"]  = _enemyName;
    json["score"]      = _myScore;
    json["questionId"] = _currentRankQuestion;

    _com->writeData(json);

}

//rank结果
void BrainStorm::rankSetResult(QJsonObject &json)
{
    QString newRank = json["newRank"].toString();
    if(_myScore == _enemyScore)
    {
        ui->rankResult->setText("平局");
    }
    else if(_myScore < _enemyScore)
    {
        ui->rankResult->setText("失败");
    }
    else if(_myScore > _enemyScore)
    {
        ui->rankResult->setText("胜利");
    }

    QString str = QString("%1 ----> %2").arg(_oldRank).arg(newRank);
    ui->newRank->setText(str);
    _userRank = newRank;

    ui->stackedWidget->setCurrentWidget(ui->rank_result);

    _com->writeData(json);
}

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();

        _rankSec = 10;
        _myScore = 0;
        ui->lcdNumber_2->display(_rankSec);

        _currentRankQuestion = 0;
        _enemyRankQuestion   = 0;

        setEnemyScore();
        setSelfScore();
        rankSetQuestion();

        _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);
            rankSetQuestion();
        }
    }
    else if(cmd == RANKRESULT)
    {
        rankSetResult(json);
    }
}

void BrainStorm::on_rankSelectButton1_clicked()
{
    ui->rankSelectButton1->setStyleSheet("background-color: rgb(170, 170, 255)");
    ui->rankSelectButton1->setEnabled(false);
    ui->rankSelectButton2->setEnabled(false);
    ui->rankSelectButton3->setEnabled(false);
    ui->rankSelectButton4->setEnabled(false);
    rankAnswerQuestion(1);
}

void BrainStorm::on_rankSelectButton2_clicked()
{
    ui->rankSelectButton2->setStyleSheet("background-color: rgb(170, 170, 255)");
    ui->rankSelectButton1->setEnabled(false);
    ui->rankSelectButton2->setEnabled(false);
    ui->rankSelectButton3->setEnabled(false);
    ui->rankSelectButton4->setEnabled(false);
    rankAnswerQuestion(2);
}

void BrainStorm::on_rankSelectButton3_clicked()
{
    ui->rankSelectButton3->setStyleSheet("background-color: rgb(170, 170, 255)");
    ui->rankSelectButton1->setEnabled(false);
    ui->rankSelectButton2->setEnabled(false);
    ui->rankSelectButton3->setEnabled(false);
    ui->rankSelectButton4->setEnabled(false);
    rankAnswerQuestion(3);
}

void BrainStorm::on_rankSelectButton4_clicked()
{
    ui->rankSelectButton4->setStyleSheet("background-color: rgb(170, 170, 255)");
    ui->rankSelectButton1->setEnabled(false);
    ui->rankSelectButton2->setEnabled(false);
    ui->rankSelectButton3->setEnabled(false);
    ui->rankSelectButton4->setEnabled(false);
    rankAnswerQuestion(4);
}

void BrainStorm::on_pushButton_2_clicked()
{
    ui->stackedWidget->setCurrentWidget(ui->mainMenu);
}

communicate.cpp

#include "communicate.h"
#include "QHostAddress"
Communicate::Communicate(QObject *parent) : QObject(parent)
{
    //连接服务器
    s.connectToHost(QHostAddress("192.168.153.129"), 9999);

    connect(&s,SIGNAL(readyRead()),this,SLOT(readData()));
}

void Communicate::readData()
{
    QByteArray data;
    while(s.bytesAvailable())   //判断数据有没有读完
    {
        data += s.readAll();
    }

    //数据解析
    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());    //emit就是用来发射信号的
        break;
    case LOGIN:
        emit login(root);
        break;
    case SINGLE_GETQUESTION:
       emit receiveSingleQuestion(root["question"].toObject());
       break;
    case RANK:     //进行排位匹配
       emit Rank(root);
       break;
    case ANSWER:   //排位回答一个问题
       emit Rank(root);
       break;
    case RANKRESULT:     //rank结束  发送对局结果
       emit Rank(root);
       break;
    default:
        break;
    }

}

void Communicate::writeData(const QJsonObject &json)
{
    QJsonDocument d(json);  //把json转化为json类型的字符串
    QByteArray sendData = d.toJson();
    int len = sendData.size();

    s.write((char*)&len, sizeof(int));  //发送数据的长度
    s.write(sendData);                  //发送数据
}
//QJsonDocument  json类型的字符串

login.cpp

#include "login.h"
#include "ui_widget.h"
#include "register.h"
#include 
Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);
    this->setWindowTitle("登录");

    _com = new Communicate();
    connect(_com,SIGNAL(login(QJsonObject)),this,SLOT(login(QJsonObject)));
}

Widget::~Widget()
{
    delete ui;
}

void Widget::on_LoginButton_clicked()
{
    QString userName = ui->UserlineEdit->text();    //获取用户名
    QString passwd = ui->PasswordlineEdit->text();  //获取用户密码

    //将登录信息发送给服务器
    QJsonObject json;
    json["cmd"]  = LOGIN;
    json["userName"]  = userName;
    json["passwd"]  = passwd;

    //给服务器发送数据
    _com->writeData(json);

}

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 USERLOGIN:
        QMessageBox::critical(this,"登录","登陆失败","不允许重复登录");
        break;
    case NAMEORPASSWD:
        QMessageBox::critical(this,"登录","用户名或密码错误");
        break;
    default:
        break;
    }
}

void Widget::on_RegisterButton_clicked()
{
    this->hide();
    Register reg(_com);
    reg.exec();
    this->show();
}

main.cpp

#include "login.h"
#include 

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    w.show();

    return a.exec();
}

register.cpp

#include "register.h"
#include "ui_register.h"
#include 

Register::Register(Communicate *com,QWidget *parent) :
    QDialog(parent),
    ui(new Ui::Register)
{
    ui->setupUi(this);
    this->setWindowTitle("注册");
    _com = com;

    connect(_com,SIGNAL(rstResult(int)),this,SLOT(rstResult(int)));
}

Register::~Register()
{
    delete ui;
}

void Register::on_NowRegisterButton_clicked()
{
    QString userName = ui->UserlineEdit->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_ReturnButton_clicked()
{
    close();
}

brainstorm.ui

基于Linux C++ windows qt开发的在线答题系统_第1张图片

基于Linux C++ windows qt开发的在线答题系统_第2张图片

基于Linux C++ windows qt开发的在线答题系统_第3张图片

 基于Linux C++ windows qt开发的在线答题系统_第4张图片

基于Linux C++ windows qt开发的在线答题系统_第5张图片 

基于Linux C++ windows qt开发的在线答题系统_第6张图片 

基于Linux C++ windows qt开发的在线答题系统_第7张图片 

 

 

login.ui

基于Linux C++ windows qt开发的在线答题系统_第8张图片

 register.ui

基于Linux C++ windows qt开发的在线答题系统_第9张图片

 

 

你可能感兴趣的:(c语言,qt,c++,数据库,开发语言)