实战项目: 负载均衡

news2024/12/23 13:46:56

0. 前言

这个项目使用了前后端,实现一个丐版的LeetCode刷题网站,并根据每台主机的实际情况,选择对应的主机,负载均衡的调度 

0.1 所用技术与开发环境

所用技术:  

  • C++ STL 标准库
  • Boost 准标准库 ( 字符串切割 )
  • cpp- httplib 第三方开源网络库
  • ctemplate 第三方开源前端网页渲染库
  • jsoncpp 第三方开源序列化、反序列化库
  • 负载均衡设计
  • 多进程、多线程
  • MySQL C connect
  • Ace前端在线编辑器 ( 部分 )
  • html/css/js/jquery/ajax (部分 )
开发环境Centos 7 云服务器, vscode,  Mysql Workbench

0.2 建立目录及文件

0.3 项目宏观结构

  • 具体的功能类似 leetcode 的题目列表+在线编程功能 

1. compile 服务设计

  •  由于compiler这个模块管理的是编译与运行,则可以先直接就创建所需要的文件

1.0 书写makefile文件 

  •   随着后续代码的跟进,并不断引入第三方库,这里还会新增编译选项

1.1 compiler_server

1.1.0 编译功能(compiler.hpp)

  •  在编译的时候,无非存在2种情况,a)要么通过,b)要么不通过
  • 要确定编译通过:
    只需要确定是否生成对应的.exe文件
  • 要当编译出错的时候(stderr):
    需要将出错信息,重定向到一个临时文件中,保存编译出错的结果
    还需要调用fork();子进程完成编译工作
    父进程继续执行

  •  由于需要频繁的文件名转换,所以在comm模块中,新建util.hpp文件并将文件名转换的函数放在一起

 

  • 还有后面判断编译成功生成的可执行程序,虽然可以直接暴力的打开文件判断是否存在,但这里使用stat函数会好一些
  • stat结构体会记录文件的各种信息

  •  注意: 程序替换是不会影响进程的文件符描述符表的

1.1.1 日志模块(log.hpp)

由于一般日志都会带上时间, 这里还需要实现一个得到当前时间的函数,则我又在util.hpp把得到时间函数的类封装成了一个类


 


由于会频繁的调用日志进行打印信息,也为了更简便的调用,我进行了以下处理

 

  • 如果在宏定义中使用#,那么这个宏就被称为带有字符串化操作的宏。这种宏可以将其参数转换成字符串常量,并在预处理阶段进行替换。 

由于引入了日志,则就可以把之前所有的输出信息,换成日志输出 

1.1.2 测试编译模块 

  •  Compile的参数是文件名,它内部会自动拼接
  • 我们还需要再./temp中创建一个code.cpp文件

  •  上面我的代码有一个错误,在编译成功的时候,并没有return,导致LOG日志打印有问题

  •  要是我们的源文件有问题,错误信息就会重定向到 文件.compile_error中
  • 在测试的时候,还需要把 文件.exe 文件.compile_error文件删除,就是上次生成的文件

1.1.3 运行功能(runner.hpp) 

程序运行: 1)代码跑完,结果正确 2)代码跑完,结果不正确, 3)代码没跑完,异常了

程序结果是否正确,是由oj_server中的测试用例决定的,则run模块只考虑是否正确运行完毕

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>// fork接口需要

#include "../comm/log.hpp"
#include "../comm/util.hpp"
namespace ns_runner
{

    using namespace ns_util;
    using namespace ns_log;
    class Runner
    {
    public:
        Runner(){}
        ~Runner(){}
        static int Run(const std::string &file_name){
            std::string _execute = PathUtil::Exe(file_name);// 可执行
            std::string _stdin = PathUtil::Stdin(file_name);// 输入
            std::string _stdout = PathUtil::Stdout(file_name);// 输出
            std::string _stderr = PathUtil::Stderr(file_name);// 错误

            umask(0);
            int _stdin_fd = open(_stdin.c_str(),O_CREAT | O_RDONLY,0644);
            int _stdout_fd = open(_stdout.c_str(),O_CREAT | O_WRONLY,0644);
            int _stderr_fd = open(_stderr.c_str(),O_CREAT | O_WRONLY,0644);

            if(_stdin_fd < 0 || _stdout_fd < 0 || _stderr_fd < 0){
                LOG(ERROR) << "运行时打开标准文件失败" << "\n";
                return -1;// 代表打开文件失败
            }

            pid_t pid = fork();
            if(pid < 0){
                LOG(ERROR) << "运行创建子进程失败" << "\n";
                close(_stdin_fd);
                close(_stdout_fd);
                close(_stderr_fd);
                return -2;// 代表创建自己失败
            }
            else if(pid == 0){
                // 子进程
                dup2(_stdin_fd,0);
                dup2(_stdout_fd,1);
                dup2(_stderr_fd,2);
                LOG(INFO) << "123";// 是不是有问题啊
                // 这个程序替换等价于 ./tmp/code.exe ./tmp/code.exe
                execl(_execute.c_str(),_execute.c_str(),nullptr);
                exit(1);
            }
            else{
                close(_stdin_fd);
                close(_stdout_fd);
                close(_stderr_fd);
                int status = 0;// 表示输出型参数
                waitpid(pid,&status,0);// 阻塞式等待

                // 程序运行异常,一定是因为收到信号
                LOG(INFO) << "运行完毕,infor: " << (status & 0x7f) << "\n";
                return status & 0x7f;
            }
        }
    };
}

  • 返回值 > 0: 程序异常了,退出时收到了信号,返回值就是对应的信号编号
    返回值 == 0: 正常运行完毕的,结果保存到了对应的临时文件中
    返回值 < 0: 内部错误

  • run.hpp也是一样的,把自己的各种输出信息,输出到一个临时文件中
  • 要判断一个程序是否异常,只需要看它是否收到了异常信号

解释waitpid第2个输出型参数 

 

  •  status并不是按照整数来整体使用的,而是按照比特位的方式,将32个比特位进行划分,只需要学习低16位
  • 这也是上面为什么会写成status & 0x7F的原因

6个程序替换的系统接口,具体使用那个看实际情况 

  • 没有p就需要带路径
  • 有l,就是列表式传命令
  • 有v就是数组式传命令
  • 有e就需要传自己设置的环境变量

1.1.4 测试运行模块

  • 虽然运行模块已经能正常运行了,但是万一code.cpp是恶意程序了,比如死循环,不停消耗CPU资源 , 所以还需要进一步的资源约束

1.1.5 添加资源限制(setrlimit)

  • 资源不足,导致OS终止进程,是通过信号终止的  

 

  •  为了方便上层调用,我直接在Run函数中增加了cpu_limit和mem_limit形参

 这个项目走到这里就需要编写compile_run.hpp,将编译和运行的逻辑连接在一起,且code.cpp需要被处理的源文件,不应该是我们自己添加的,而是需要再客户端中导入

1.1.6 编译 && 运行功能 (compile_run.hpp)

这个模块要做的是:
a)适配用户请求,引入json定制通信协议字段
b)形成唯一文件名
c)正确调用compile and run
在centos中安装: sudo yum install json-c-devel
头文件 #include <jsoncpp/json/json.h>

  •  注意: 在编译引入了json的文件,需要加上-ljsoncpp

 

  • 虽然这个code就是文件名了,但client可能会提交大量的代码,所以内部就会需要形成唯一的文件名(待完善)
  • 还有很多个出错问题怎么解决(待完善)

 complie_run.hpp

#pragma once

#include "compiler.hpp"
#include "runner.hpp"
#include "../comm/log.hpp"
#include "../comm/util.hpp"
#include <jsoncpp/json/json.h>

namespace ns_compile_and_run
{
    using namespace ns_log;
    using namespace ns_util;
    using namespace ns_compiler;
    using namespace ns_runner;
    
    // in_json: {"code": "#include...", "input": "","cpu_limit":1, "mem_limit":10240}
    // out_json: {"status":"0", "reason":"","stdout":"","stderr":"",}
    static void Start(const std::string &in_json,std::string *out_json){
        // step1: 反序列化过程
        Json::Value in_value;
        Json::Reader reader;
        // 把in_json中的数据写到in_value中
        reader.parse(in_json,in_value);// 最后再处理差错问题

        std::string code = in_value["code"].asString();
        std::string input = in_value["input"].asString();
        int cpu_limit = in_value["cpu_limit"].asInt();
        int mem_limit = in_value["mem_limit"].asInt();

        Json::Value out_value;
        int status_code = 0;
        int run_result = 0;
        std::string file_name;// 唯一文件名
        if(code.size() == 0){
            status_code = -1;// 代码为空
            goto END;
        }
        // 形成的文件名只居有唯一性,没有目录没有后缀
        // 使用: 毫秒级时间戳 + 原子性递增唯一值 : 来保证唯一性
        file_name = FileUtil::UniqFileName();
        // 形成临时的src文件
        if(!FileUtil::WriteFile(PathUtil::Src(file_name),code)){
            status_code = -2;// 未知错误
            goto END;
        }

        if(!Compiler::Compile(file_name)){
            status_code = -3;// 编译错误
            goto END;
        }
        run_result = Runner::Run(file_name,cpu_limit,mem_limit);
        if(run_result < 0){
            // runnem模块内部错误
            status_code = -2;// 未知错误
        }
        else if(run_result > 0){
            // 程序运行崩溃
            status_code = run_result;// 这里的run_result是信号
        }
        else{
            // 运行成功
            status_code = 0;
        }
    END:
        out_value["status"] = status_code;
        out_value["reason"] = CodeToDest(status_code,file_name);// 得到错误信息字符串
        if(status_code == 0){
            // 整个过程全部成功
            std::string _stdout;
            FileUtil::ReadFile(PathUtil::Stdout(file_name),&_stdout,true);
            out_value["stdout"] = _stdout;

            std::string _stderr;
            FileUtil::ReadFile(PathUtil::Stdout(file_name),&_stderr,true);
            out_value["stdout"] = _stdout;
        }

        // step2: 序列化
        Json::StyledWriter writer;
        *out_json = writer.write(out_value);
        

    }
    

}

 1.1.7 基于compile_run.hpp对util.hpp的补充


 

  • 注意引入流时需要引入头文件: #include <fstream>  
  • getline:不保存行分割符,有些时候需要保留\n,getline内部重载了强制类型转化 

1.1.8 测试编译运行模块 

#include "compile_run.hpp"
using namespace ns_compile_and_run;

int main()
{
    std::string in_json;
    Json::Value in_value;
    // R"()", raw string
    in_value["code"] = R"(#include<iostream>
    int main(){
    std::cout << "你可以看见我了" << std::endl;
    return 0;
    })";
    in_value["input"] = "";
    in_value["cpu_limit"] = 1;
    in_value["mem_limit"] = 10240 * 3;
    Json::FastWriter writer;
    in_json = writer.write(in_value);
    // std::cout << in_json << std::endl;
    // 这个是将来给客户端返回的json串
    std::string out_json;
    CompileAndRun::Start(in_json, &out_json);
    std::cout << out_json << std::endl;
    return 0;
}

  •  实际上这里的代码应该是client自动提交给我们的,我们直接使用第三方库就行了
  • 待优化: 可以把临时生成的这些文件都清理掉,

1.1.9 清理临时文件

  •  这个函数直接放在compile_server.cc中的start函数的最后,清理临时文件

1.1.10 引入cpp-httplib 网络库

 下载地址: cpp-httplib: C++ http 网络库 - Gitee.com

  •  这个就是别人写好的网络库,我们直接使用就行了

1.1.11 更新gcc

安装scl : sudo yum install centos-release-scl scl-utils-build

安装新版本gcc:  sudo yum install - y devtoolset - 9 - gcc devtoolset - 9 - gcc - c ++

  • 把 scl enable devtoolset-9 bash 放在 ~/.bash_profile中
  • 想每次登陆的时候,都是较新的gcc

 如果不更新在使用cpp-httplib时可能会报错, 用老的编译器,要么编译不通过,要么直接运行报错

1.1.12 测试cpp-httplib网络库 

  • 可能会出现服务器的公网ip无法访问的问题,可以试试把防火墙关闭,并打开端口

1.1.13 将compiler_server打包成网络服务

compiler_server.cc

#include "compile_run.hpp"
#include "../comm/httplib.h"// 引入

using namespace ns_compile_and_run;
using namespace httplib;// 引入

void Usage(std::string proc){
    std::cerr << "Usage: " << "\n\t" << proc << " port" << std::endl;
}

//./copile_server port
int main(int argc,char *argv[])
{
    if(argc != 2){
        Usage(argv[0]);
        return 1;
    }
    Server svr;
    svr.Post("/compile_and_run",[](const Request&req,Response & resp){
        // 用户请求的服务正文是我们想要的json string
        std::string in_json = req.body;
        std::string out_json;
        if(!in_json.empty()){
            CompileAndRun::Start(in_json,&out_json);
            resp.set_content(out_json,"application/json;charset=uft-8");
        }
    });
    svr.listen("0.0.0.0",atoi(argv[1]));
    return 0;
}


  • 由于我这里没有写客户端代码,则这里暂时不好测试,不过可以借助第三方工具进行测试

 2. oj_server服务设计

本质: 建立一个小型网站
1. 获取首页,用题目列表充当
2. 编辑区域页面
3. 提交判题功能(编译并运行)

2.1 书写makefile文件

  •  随着后续代码的跟进,并不断引入第三方库,这里还会新增编译选项

2.2 服务路由功能(oj_server.cc) 

  • 为用户实现的路由功能就3个 a. 获取所有的题目列表 b.根据题目编号,获取题目内容 c.判断用户提交的代码  

2.3 MVC 结构的oj 服务设计(M)

Model , 通常是和数据交互的模块 ,比如,对题库进行增删改查(文件版, MySQL

2.3.1 安装boost库 && 字符切分功能

sudo yum install -y boost-devel //boost 开发库

  •  第一个参数为缓冲区,第二个参数为被分割的字符串
  • 第三个参数为分割符,第四个参数为是否压缩
    • 要压缩: 当sep = "空格"时,sepsepsep -> 空格
    • 不压缩: 当sep = "空格"时,sepsepsep -> 空格空格空格

 2.3.2 数据结构

header.cpp

#include <iostream>
#include <string>
#include <vector>
#include <map>
#include <algorithm>

using namespace std;

class Solution{
    public:
        bool isPalindrome(int x)
        {
            //将你的代码写在下面
            
            return true;
        }
};

tail.cpp

#ifndef COMPILER_ONLINE
#include "header.cpp"
#endif

// 这里先把测试用例 暴露出来
void Test1()
{
    // 通过定义临时对象,来完成方法的调用
    bool ret = Solution().isPalindrome(121);
    if(ret){
        std::cout << "通过用例1, 测试121通过 ... OK!" << std::endl;
    }
    else{
        std::cout << "没有通过用例1, 测试的值是: 121"  << std::endl;
    }
}

void Test2()
{
    // 通过定义临时对象,来完成方法的调用
    bool ret = Solution().isPalindrome(-10);
    if(!ret){
        std::cout << "通过用例2, 测试-10通过 ... OK!" << std::endl;
    }
    else{
        std::cout << "没有通过用例2, 测试的值是: -10"  << std::endl;
    }
}

int main()
{
    Test1();
    Test2();

    return 0;
}

  • des.txt表示题目信息
  •  header.cpp表示预设代码
  • tail.cpp表示测试用例

  •  真正代码 = 用户在head.cpp中的代码 + header.cpp + tail.cpp 去到COMPILER_ONLINE
  •  这个条件编译只是为了编写tail.cpp时不报错

2.3.3 model功能(oj_model.cpp)

数据交互 && 提供接口

 

#pragma once
// 文件版本
#include "../comm/util.hpp"
#include "../comm/log.hpp"
#include <unordered_map>
#include <cassert>
#include <vector>

namespace ns_model
{
    using namespace std;
    using namespace ns_log;
    using namespace ns_util;

    struct Question{
        string number;// 题目编号,唯一
        string tile;// 题目标题
        string star;// 难度: 简单 中等 困难
        
        int cpu_limit;// 题目的时间复杂度(S)
        int mem_limit;// 题目的空间复杂度(KB)
        
        string desc;// 题目描述
        string header; // 题目预设给用户在线编辑器的代码
        string tail;// 题目测试用例,需要和header拼接
    };    

    const string questions_list = "./question/quetions.list";
    const string questions_path = "./question";

    class Model
    {
    public:
        Model(){
            // 加载所有题目:底层是用hash表映射的
            assert(LoadQuestionList(questions_list));
        }
        ~Model(){
            ;
        }

        // 获取所有题目,这里的out是输出型参数
        bool GetAllQuestions(vector<Question>*out){
            if(questions.size() == 0){
                LOG(ERROR) << "用户获取题库失败" << "\n";
                return false;
            }
            for(const auto&q: questions){
                out->push_back(q.second);
            }
            return true;
        }

        // 获取指定题目,这里的q是输出型参数
        bool GetOneQuestion(const string& number,Question* q){
            const auto& iter = questions.find(number);
            if(iter == questions.end()){
                LOG(ERROR) << "用户获取题目失败,题目编号: " << number << "\n";
                return false;
            }
            (*q) = iter->second;
            return true;
        }

        // 加载配置文件: questions/questions.list + 题目编号文件
        bool LoadQuestionList(const string&question_list){
            // 加载配置文件: questions/questions.list +题目编号文件
            ifstream in(question_list);
            if(!in.is_open()){
                LOG(FATAL) << "加载题库失败,请检查是否存在题库文件" << "\n";
                return false;
            }
            string line;
            while(getline(in,line)){
                vector<string>tokens;
                StringUtil::SplitString(line,&tokens," ");// 被分割的字符串 缓冲区 分割符
                // eg: 1 判断回文数 简单 1 30000
                if(tokens.size()!=5){
                    LOG(WARNING) << "加载部分题目失败,请检查文件格式" << "\n";
                    continue;
                }
                Question q;
                q.number = tokens[0];
                q.tile = tokens[1];
                q.star = tokens[2];
                q.cpu_limit = atoi(tokens[3].c_str());
                q.mem_limit = atoi(tokens[4].c_str());

                string path = questions_list;
                path += q.number;
                path += "/";

                // 第三个参数代表 是否加上 \n
                FileUtil::ReadFile(path+"desc.txt",&(q.desc),true);
                FileUtil::ReadFile(path+"header.cpp",&(q.header),true);
                FileUtil::ReadFile(path+"tail.txt",&(q.tail),true);
               
                questions.insert({q.number,q});// 录题成功
            }
            LOG(INFO) << "加载题库...成功" << "\n";
            in.close();
        }
    private:
        // 题号 : 题目细节
        unordered_map<string,Question> questions;
    };
} 

2.4 MVC 结构的oj 服务设计(C) 

2.4.1 负载均衡模块

namespace ns_control
{
    using namespace std;
    using namespace ns_log;
    using namespace ns_util;
    using namespace ns_model;
    using namespace ns_view;
    using namespace httplib;

    // 提供服务的主机
    class Machine
    {
    public:
        std::string ip;  //编译服务的ip
        int port;        //编译服务的port
        uint64_t load;   //编译服务的负载
        std::mutex *mtx; // mutex禁止拷贝的,使用指针
    public:
        Machine() : ip(""), port(0), load(0), mtx(nullptr)
        {
        }
        ~Machine()
        {
        }

    public:
        // 提升主机负载
        void IncLoad()
        {
            if (mtx) mtx->lock();
            ++load;
            if (mtx) mtx->unlock();
        }
        // 减少主机负载
        void DecLoad()
        {
            if (mtx) mtx->lock();
            --load;
            if (mtx) mtx->unlock();
        }
        void ResetLoad()
        {
            if(mtx) mtx->lock();
            load = 0;
            if(mtx) mtx->unlock();
        }
        // 获取主机负载,没有太大的意义,只是为了统一接口
        uint64_t Load()
        {
            uint64_t _load = 0;
            if (mtx) mtx->lock();
            _load = load;
            if (mtx) mtx->unlock();

            return _load;
        }
    };

    const std::string service_machine = "./conf/service_machine.conf";
    class LoadBlance
    {
    private:
        // 可以给我们提供编译服务的所有的主机
        // 每一台主机都有自己的下标,充当当前主机的id
        std::vector<Machine> machines;
        // 所有在线的主机id
        std::vector<int> online;
        // 所有离线的主机id
        std::vector<int> offline;
        // 保证LoadBlance它的数据安全
        std::mutex mtx;

    public:
        LoadBlance()
        {
            assert(LoadConf(service_machine));
            LOG(INFO) << "加载 " << service_machine << " 成功"
                      << "\n";
        }
        ~LoadBlance()
        {
        }

    public:
        bool LoadConf(const std::string &machine_conf)
        {
            std::ifstream in(machine_conf);
            if (!in.is_open())
            {
                LOG(FATAL) << " 加载: " << machine_conf << " 失败"
                           << "\n";
                return false;
            }
            std::string line;
            while (std::getline(in, line))
            {
                std::vector<std::string> tokens;
                StringUtil::SplitString(line, &tokens, ":");
                if (tokens.size() != 2)
                {
                    LOG(WARNING) << " 切分 " << line << " 失败"
                                 << "\n";
                    continue;
                }
                Machine m;
                m.ip = tokens[0];
                m.port = atoi(tokens[1].c_str());
                m.load = 0;
                m.mtx = new std::mutex();

                online.push_back(machines.size());
                machines.push_back(m);
            }

            in.close();
            return true;
        }
        // id: 输出型参数
        // m : 输出型参数
        bool SmartChoice(int *id, Machine **m)
        {
            // 1. 使用选择好的主机(更新该主机的负载)
            // 2. 我们需要可能离线该主机
            mtx.lock();
            // 负载均衡的算法
            // 1. 随机数+hash
            // 2. 轮询+hash
            int online_num = online.size();
            if (online_num == 0)
            {
                mtx.unlock();
                LOG(FATAL) << " 所有的后端编译主机已经离线, 请运维的同事尽快查看"
                           << "\n";
                return false;
            }
            // 通过遍历的方式,找到所有负载最小的机器
            *id = online[0];
            *m = &machines[online[0]];
            uint64_t min_load = machines[online[0]].Load();
            for (int i = 1; i < online_num; i++)
            {
                uint64_t curr_load = machines[online[i]].Load();
                if (min_load > curr_load)
                {
                    min_load = curr_load;
                    *id = online[i];
                    *m = &machines[online[i]];
                }
            }
            mtx.unlock();
            return true;
        }
        void OfflineMachine(int which)
        {
            mtx.lock();
            for(auto iter = online.begin(); iter != online.end(); iter++)
            {
                if(*iter == which)
                {
                    machines[which].ResetLoad();
                    //要离线的主机已经找到啦
                    online.erase(iter);
                    offline.push_back(which);
                    break; //因为break的存在,所有我们暂时不考虑迭代器失效的问题
                }
            }
            mtx.unlock();
        }
        void OnlineMachine()
        {
            //我们统一上线,后面统一解决
            mtx.lock();
            online.insert(online.end(), offline.begin(), offline.end());
            offline.erase(offline.begin(), offline.end());
            mtx.unlock();

            LOG(INFO) << "所有的主机有上线啦!" << "\n";
        }
        //for test
        void ShowMachines()
        {
             mtx.lock();
             std::cout << "当前在线主机列表: ";
             for(auto &id : online)
             {
                 std::cout << id << " ";
             }
             std::cout << std::endl;
             std::cout << "当前离线主机列表: ";
             for(auto &id : offline)
             {
                 std::cout << id << " ";
             }
             std::cout << std::endl;
             mtx.unlock();
        }
    };
}

2.4.1 control功能(oj_control.hpp)

逻辑控制模块

// 这是我们的核心业务逻辑的控制器
    class Control
    {
    private:
        Model model_; //提供后台数据
        View view_;   //提供html渲染功能
        LoadBlance load_blance_; //核心负载均衡器
    public:
        Control()
        {
        }
        ~Control()
        {
        }

    public:
        void RecoveryMachine()
        {
            load_blance_.OnlineMachine();
        }
        //根据题目数据构建网页
        // html: 输出型参数
        bool AllQuestions(string *html)
        {
            bool ret = true;
            vector<struct Question> all;
            if (model_.GetAllQuestions(&all))
            {
                sort(all.begin(), all.end(), [](const struct Question &q1, const struct Question &q2){
                    return atoi(q1.number.c_str()) < atoi(q2.number.c_str());
                });
                // 获取题目信息成功,将所有的题目数据构建成网页
                // ...
            }
            else
            {
                *html = "获取题目失败, 形成题目列表失败";
                ret = false;
            }
            return ret;
        }
        bool Question(const string &number, string *html)
        {
            bool ret = true;
            struct Question q;
            if (model_.GetOneQuestion(number, &q))
            {
                // 获取指定题目信息成功,将所有的题目数据构建成网页
                // ....
            }
            else
            {
                *html = "指定题目: " + number + " 不存在!";
                ret = false;
            }
            return ret;
        }
        // code: #include...
        // input: ""
        void Judge(const std::string &number, const std::string in_json, std::string *out_json)
        {
            
        }
    };
  •  control模块中的判题功能,我打算最后设计

2.5  MVC 结构的oj 服务设计(V)

2.4 安装与测试 ctemplate(网页渲染)

渲染本质就是key-value之间的替换

  • 安装镜像源: git clone https://gitee.com/mirrors_OlafvdSpek/ctemplate.git 
  • . / autogen . sh
  • . / configure
  • make // 编译 如果报错请 更新gcc
  • make install

test.cpp 

#include <iostream>
#include <string>
#include <ctemplate/template.h>
int main()
{
    std::string html = "./test.html";
    std::string html_info = "测试ctemplate渲染";

    // 建立ctemplate参数目录结构
    ctemplate::TemplateDictionary root("test"); // unordered_map<string,string> test;
    
    // 向结构中添加你要替换的数据,kv的
    root.SetValue("info", html_info); // test.insert({key, value});
    
    // 获取被渲染对象
    // DO_NOT_STRIP:保持html网页原貌
    ctemplate::Template *tpl = ctemplate::Template::GetTemplate(html,ctemplate::DO_NOT_STRIP); 
    
    // 开始渲染,返回新的网页结果到out_html
    std::string out_html;
    tpl->Expand(&out_html, &root);
    
    std::cout << "渲染的带参html是:" << std::endl;
    std::cout << out_html << std::endl;
    
    return 0;
}

 test.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <p>{{info}}</p>
    <p>{{info}}</p>
    <p>{{info}}</p>
    <p>{{info}}</p>
</body>

</html>


 错误原因: error while loading shared libraries: libmpc.so.3: cannot open shared object file 

export LD\_LIBRARY\_PATH=$LD\_LIBRARY\_PATH:/usr/local/lib
  •  在命令行上输入 上面这段命令,注:只在当前会话中有效

#  cat /etc/ld.so.conf
include ld.so.conf.d/*.conf
#  echo "/usr/local/lib" >> /etc/ld.so.conf
#  ldconfig

2.5.2 渲染功能(oj_view.hpp)

#pragma once
#include <iostream>
#include <ctemplate/template.h>
#include "./oj_model.hpp"

namespace ns_view
{
    using namespace ns_model;

    const std::string template_path = "./template_html/";

    class View
    {
    public:
        View(){}
        ~View(){}

        // 渲染所有题目
        void ALLExpandHtml(const vector<struct Question>&question,std::string *html){
            // 题目编号 题目标题 题难度
            // 推荐表格实现
            // 1.形成路径
            string src_html = template_path + "all_quetions.html";

            // 2.形成数字典
            ctemplate::TemplateDictionary root("all_question");
            for(const auto& q: question){
                ctemplate::TemplateDictionary *sub = root.AddSectionDictionary("question_list");
                sub->SetValue("number",q.number);
                sub->SetValue("title",q.title);
                sub->SetValue("star",q.star);
            }

            // 3. 获取被渲染的html
            ctemplate::Template*tpl = ctemplate::Template::GetTemplate(src_html,ctemplate::DO_NOT_STRIP);

            // 4.开始完成渲染功能
            tpl->Expand(html,&root);
        }

        // 渲染一道题目
        void OneExpandHtml(const struct Question &q,string *html){
            // 1.形成路径
            std::string src_html = template_path + "one_question.html";

            // 2. 形成数字典
            ctemplate::TemplateDictionary root("one_question");
            root.SetValue("number",q.number);
            root.SetValue("title",q.title);
            root.SetValue("star",q.star);
            root.SetValue("desc",q.desc);
            root.SetValue("header",q.header);

            // 3.获取被渲染的html
            ctemplate::Template*tpl = ctemplate::Template::GetTemplate(src_html,ctemplate::DO_NOT_STRIP);
            // 4.开始完成渲染功能
            tpl->Expand(html,&root);
        }
    };
}

2.6  联动MVC模块并测试


oj_server.cc 

#include <iostream>
#include "../comm/httplib.h"// 引入
#include "oj_control.hpp"

using namespace httplib;// 引入
using namespace ns_control;

int main()
{
    // 用户请求的服务器路由功能
    Server svr;
    Control ctrl;
    // 获取所有的题目列表
    svr.Get("/all_questions",[&ctrl](const Request&req,Response &resp){
        // 返回一张包含所有题目的html网页
        std::string html;// 待处理
        ctrl.AllQuestions(&html);
        resp.set_content(html,"text/html;charset=utf-8");
    });

    // 根据题目编号,获取题目内容
    // \d+ 是正则表达式的特殊符合
    svr.Get(R"(/question/(\d+))",[&ctrl](const Request&req,Response &resp){
        std::string number = req.matches[1];
        std::string html;
        ctrl.Question(number,&html);
        resp.set_content(html,"text/html;charset=utf-8");
    });

    // 判断用户提交的代码(1.每道题c测试用例,2.compile_and_run)
    svr.Post(R"(/judge/(\d+))",[&ctrl](const Request&req,Response &resp){
        std::string number = req.matches[1];
        std::string result_json;
        ctrl.Judge(number,req.body,&result_json);
        resp.set_content(result_json,"application/json;charset=utf-8");
    });
    svr.set_base_dir("./wwwroot");
    svr.listen("0.0.0.0",8080);
    return 0;
}

  •  这里的前端都是提前做好了的,我们可以不关心前端;
  • control功能还有个判题功能没有实现

 2.7 完善oj_control.hpp中的判题功能

void Judge(const std::string &number, const std::string in_json, std::string *out_json)
        {
            // LOG(DEBUG) << in_json << " \nnumber:" << number << "\n";
            
            // 0. 根据题目编号,直接拿到对应的题目细节
            struct Question q;
            model_.GetOneQuestion(number, &q);

            // 1. in_json进行反序列化,得到题目的id,得到用户提交源代码,input
            Json::Reader reader;
            Json::Value in_value;
            reader.parse(in_json, in_value);
            std::string code = in_value["code"].asString();

            // 2. 重新拼接用户代码+测试用例代码,形成新的代码
            Json::Value compile_value;
            compile_value["input"] = in_value["input"].asString();
            compile_value["code"] = code + "\n" + q.tail;
            compile_value["cpu_limit"] = q.cpu_limit;
            compile_value["mem_limit"] = q.mem_limit;
            Json::FastWriter writer;
            std::string compile_string = writer.write(compile_value);

            // 3. 选择负载最低的主机(差错处理)
            // 规则: 一直选择,直到主机可用,否则,就是全部挂掉
            while(true)
            {
                int id = 0;
                Machine *m = nullptr;
                if(!load_blance_.SmartChoice(&id, &m))
                {
                    break;
                }

                // 4. 然后发起http请求,得到结果
                Client cli(m->ip, m->port);
                m->IncLoad();
                LOG(INFO) << " 选择主机成功, 主机id: " << id << " 详情: " << m->ip << ":" << m->port << " 当前主机的负载是: " << m->Load() << "\n";
                if(auto res = cli.Post("/compile_and_run", compile_string, "application/json;charset=utf-8"))
                {
                    // 5. 将结果赋值给out_json
                    if(res->status == 200)
                    {
                        *out_json = res->body;
                        m->DecLoad();
                        LOG(INFO) << "请求编译和运行服务成功..." << "\n";
                        break;
                    }
                    m->DecLoad();
                }
                else
                {
                    //请求失败
                    LOG(ERROR) << " 当前请求的主机id: " << id << " 详情: " << m->ip << ":" << m->port << " 可能已经离线"<< "\n";
                    load_blance_.OfflineMachine(id);
                    load_blance_.ShowMachines(); //仅仅是为了用来调试
                }
            }

2.8 测试oj_server服务

  • 在编译时需要加上-D COMPILER_ONLINE条件编译, 

  •  设计到前端网页,下面会有提及

2.9 一个BUG 

  •  把tail.txt改成tail.cpp,不然后面无法进行代码拼接

 3. 前端页面设计(了解)

3.1 index.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>这是我的个人OJ系统</title>
    <style>
        /* 起手式, 100%保证我们的样式设置可以不受默认影响 */
        * {
            /* 消除网页的默认外边距 */
            margin: 0px;
            /* 消除网页的默认内边距 */
            padding: 0px;
        }

        html,
        body {
            width: 100%;
            height: 100%;
        }

        .container .navbar {
            width: 100%;
            height: 50px;
            background-color: black;
            /* 给父级标签设置overflow,取消后续float带来的影响 */
            overflow: hidden;
        }

        .container .navbar a {
            /* 设置a标签是行内块元素,允许你设置宽度 */
            display: inline-block;
            /* 设置a标签的宽度,a标签默认行内元素,无法设置宽度 */
            width: 80px;
            /* 设置字体颜色 */
            color: white;
            /* 设置字体的大小 */
            font-size: large;
            /* 设置文字的高度和导航栏一样的高度 */
            line-height: 50px;
            /* 去掉a标签的下划线 */
            text-decoration: none;
            /* 设置a标签中的文字居中 */
            text-align: center;
        }
        /* 设置鼠标事件 */
        .container .navbar a:hover {
            background-color: green;
        }
        .container .navbar .login {
            float: right;
        }

        .container .content {
            /* 设置标签的宽度 */
            width: 800px;
            /* 用来调试 */
            /* background-color: #ccc; */
            /* 整体居中 */
            margin: 0px auto;
            /* 设置文字居中 */
            text-align: center;
            /* 设置上外边距 */
            margin-top: 200px;
        }

        .container .content .font_ {
            /* 设置标签为块级元素,独占一行,可以设置高度宽度等属性 */
            display: block;
            /* 设置每个文字的上外边距 */
            margin-top: 20px;
            /* 去掉a标签的下划线 */
            text-decoration: none;
            /* 设置字体大小
            font-size: larger; */
        }
    </style>
</head>

<body>
    <div class="container">
        <!-- 导航栏, 功能不实现-->
        <div class="navbar">
            <a href="/">首页</a>
            <a href="/all_questions">题库</a>
            <a href="#">竞赛</a>
            <a href="#">讨论</a>
            <a href="#">求职</a>
            <a class="login" href="#">登录</a>
        </div>
        <!-- 网页的内容 -->
        <div class="content">
            <h1 class="font_">欢迎来到我的OnlineJudge平台</h1>
            <p class="font_">这个我个人独立开发的一个在线OJ平台</p>
            <a class="font_" href="/all_questions">点击我开始编程啦!</a>
        </div>
    </div>
</body>

</html>

 3.2 all_questions.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>在线OJ-题目列表</title>
    <style>
        /* 起手式, 100%保证我们的样式设置可以不受默认影响 */
        * {
            /* 消除网页的默认外边距 */
            margin: 0px;
            /* 消除网页的默认内边距 */
            padding: 0px;
        }

        html,
        body {
            width: 100%;
            height: 100%;
        }

        .container .navbar {
            width: 100%;
            height: 50px;
            background-color: black;
            /* 给父级标签设置overflow,取消后续float带来的影响 */
            overflow: hidden;
        }

        .container .navbar a {
            /* 设置a标签是行内块元素,允许你设置宽度 */
            display: inline-block;
            /* 设置a标签的宽度,a标签默认行内元素,无法设置宽度 */
            width: 80px;
            /* 设置字体颜色 */
            color: white;
            /* 设置字体的大小 */
            font-size: large;
            /* 设置文字的高度和导航栏一样的高度 */
            line-height: 50px;
            /* 去掉a标签的下划线 */
            text-decoration: none;
            /* 设置a标签中的文字居中 */
            text-align: center;
        }

        /* 设置鼠标事件 */
        .container .navbar a:hover {
            background-color: green;
        }

        .container .navbar .login {
            float: right;
        }

        .container .question_list {
            padding-top: 50px;
            width: 800px;
            height: 100%;
            margin: 0px auto;
            /* background-color: #ccc; */
            text-align: center;
        }

        .container .question_list table {
            width: 100%;
            font-size: large;
            font-family: 'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif;
            margin-top: 50px;
            background-color: rgb(243, 248, 246);
        }

        .container .question_list h1 {
            color: green;
        }

        .container .question_list table .item {
            width: 100px;
            height: 40px;
            font-size: large;
            font-family: 'Times New Roman', Times, serif;
        }

        .container .question_list table .item a {
            text-decoration: none;
            color: black;
        }

        .container .question_list table .item a:hover {
            color: blue;
            text-decoration: underline;
        }

        .container .footer {
            width: 100%;
            height: 50px;
            text-align: center;
            line-height: 50px;
            color: #ccc;
            margin-top: 15px;
        }
    </style>
</head>

<body>
    <div class="container">
        <!-- 导航栏, 功能不实现-->
        <div class="navbar">
            <a href="/">首页</a>
            <a href="/all_questions">题库</a>
            <a href="#">竞赛</a>
            <a href="#">讨论</a>
            <a href="#">求职</a>
            <a class="login" href="#">登录</a>
        </div>
        <div class="question_list">
            <h1>OnlineJuge题目列表</h1>
            <table>
                <tr>
                    <th class="item">编号</th>
                    <th class="item">标题</th>
                    <th class="item">难度</th>
                </tr>
                {{#question_list}}
                <tr>
                    <td class="item">{{number}}</td>
                    <td class="item"><a href="/question/{{number}}">{{title}}</a></td>
                    <td class="item">{{star}}</td>
                </tr>
                {{/question_list}}
            </table>
        </div>
        <div class="footer">
            <!-- <hr> -->
            <h4>@lyc</h4>
        </div>
    </div>

</body>

</html>

3.3 one_questions.html(ACE插件&&JQuery&&ajax)

<!-- 引入ACE插件 -->
    <!-- 引入ACE CDN -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.2.6/ace.js" type="text/javascript"
        charset="utf-8"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.2.6/ext-language_tools.js" type="text/javascript"
        charset="utf-8"></script>
    <!-- 引入jquery CDN -->
    <script src="http://code.jquery.com/jquery-2.1.1.min.js"></script>

  • ACE插件是一个 编写代码的编译框
  • 收集当前页面的有关数据 , a. 题号 a. 代码 , 我们采用 JQuery 来进行获取 html 中的内容
  • 构建json,并通过 ajax向后台 发起基于http的json请求

全部代码

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{{number}}.{{title}}</title>
    <!-- 引入ACE插件 -->
    <!-- 引入ACE CDN -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.2.6/ace.js" type="text/javascript"
        charset="utf-8"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.2.6/ext-language_tools.js" type="text/javascript"
        charset="utf-8"></script>
    <!-- 引入jquery CDN -->
    <script src="http://code.jquery.com/jquery-2.1.1.min.js"></script>

    <style>
        * {
            margin: 0;
            padding: 0;
        }

        html,
        body {
            width: 100%;
            height: 100%;
        }

        .container .navbar {
            width: 100%;
            height: 50px;
            background-color: black;
            /* 给父级标签设置overflow,取消后续float带来的影响 */
            overflow: hidden;
        }

        .container .navbar a {
            /* 设置a标签是行内块元素,允许你设置宽度 */
            display: inline-block;
            /* 设置a标签的宽度,a标签默认行内元素,无法设置宽度 */
            width: 80px;
            /* 设置字体颜色 */
            color: white;
            /* 设置字体的大小 */
            font-size: large;
            /* 设置文字的高度和导航栏一样的高度 */
            line-height: 50px;
            /* 去掉a标签的下划线 */
            text-decoration: none;
            /* 设置a标签中的文字居中 */
            text-align: center;
        }

        /* 设置鼠标事件 */
        .container .navbar a:hover {
            background-color: green;
        }

        .container .navbar .login {
            float: right;
        }
        
        .container .part1 {
            width: 100%;
            height: 600px;
            overflow: hidden;
        }

        .container .part1 .left_desc {
            width: 50%;
            height: 600px;
            float: left;
            overflow: scroll;
        }

        .container .part1 .left_desc h3 {
            padding-top: 10px;
            padding-left: 10px;
        }

        .container .part1 .left_desc pre {
            padding-top: 10px;
            padding-left: 10px;
            font-size: medium;
            font-family:'Gill Sans', 'Gill Sans MT', Calibri, 'Trebuchet MS', sans-serif;
        }

        .container .part1 .right_code {
            width: 50%;
            float: right;
        }

        .container .part1 .right_code .ace_editor {
            height: 600px;
        }
        .container .part2 {
            width: 100%;
            overflow: hidden;
        }

        .container .part2 .result {
            width: 300px;
            float: left;
        }

        .container .part2 .btn-submit {
            width: 120px;
            height: 50px;
            font-size: large;
            float: right;
            background-color: #26bb9c;
            color: #FFF;
            /* 给按钮带上圆角 */
            /* border-radius: 1ch; */
            border: 0px;
            margin-top: 10px;
            margin-right: 10px;
        }
        .container .part2 button:hover {
            color:green;
        }

        .container .part2 .result {
            margin-top: 15px;
            margin-left: 15px;
        }

        .container .part2 .result pre {
            font-size: large;
        }
    </style>
</head>

<body>
    <div class="container">
        <!-- 导航栏, 功能不实现-->
        <div class="navbar">
            <a href="/">首页</a>
            <a href="/all_questions">题库</a>
            <a href="#">竞赛</a>
            <a href="#">讨论</a>
            <a href="#">求职</a>
            <a class="login" href="#">登录</a>
        </div>
        <!-- 左右呈现,题目描述和预设代码 -->
        <div class="part1">
            <div class="left_desc">
                <h3><span id="number">{{number}}</span>.{{title}}_{{star}}</h3>
                <pre>{{desc}}</pre>
            </div>
            <div class="right_code">
                <pre id="code" class="ace_editor"><textarea class="ace_text-input">{{pre_code}}</textarea></pre>
            </div>
        </div>
        <!-- 提交并且得到结果,并显示 -->
        <div class="part2">
            <div class="result"></div>
            <button class="btn-submit" onclick="submit()">提交代码</button>
        </div>
    </div>
    <script>
        //初始化对象
        editor = ace.edit("code");

        //设置风格和语言(更多风格和语言,请到github上相应目录查看)
        // 主题大全:http://www.manongjc.com/detail/25-cfpdrwkkivkikmk.html
        editor.setTheme("ace/theme/monokai");
        editor.session.setMode("ace/mode/c_cpp");

        // 字体大小
        editor.setFontSize(16);
        // 设置默认制表符的大小:
        editor.getSession().setTabSize(4);

        // 设置只读(true时只读,用于展示代码)
        editor.setReadOnly(false);

        // 启用提示菜单
        ace.require("ace/ext/language_tools");
        editor.setOptions({
            enableBasicAutocompletion: true,
            enableSnippets: true,
            enableLiveAutocompletion: true
        });

        function submit(){
            // alert("嘿嘿!");
            // 1. 收集当前页面的有关数据, 1. 题号 2.代码
            var code = editor.getSession().getValue();
            // console.log(code);
            var number = $(".container .part1 .left_desc h3 #number").text();
            // console.log(number);
            var judge_url = "/judge/" + number;
            // console.log(judge_url);
            // 2. 构建json,并通过ajax向后台发起基于http的json请求
            $.ajax({
                method: 'Post',   // 向后端发起请求的方式
                url: judge_url,   // 向后端指定的url发起请求
                dataType: 'json', // 告知server,我需要什么格式
                contentType: 'application/json;charset=utf-8',  // 告知server,我给你的是什么格式
                data: JSON.stringify({
                    'code':code,
                    'input': ''
                }),
                success: function(data){
                    //成功得到结果
                    // console.log(data);
                    show_result(data);
                }
            });
            // 3. 得到结果,解析并显示到 result中
            function show_result(data)
            {
                // console.log(data.status);
                // console.log(data.reason);
                // 拿到result结果标签
                var result_div = $(".container .part2 .result");
                // 清空上一次的运行结果
                result_div.empty();

                // 首先拿到结果的状态码和原因结果
                var _status = data.status;
                var _reason = data.reason;

                var reason_lable = $( "<p>",{
                       text: _reason
                });
                reason_lable.appendTo(result_div);

                if(status == 0){
                    // 请求是成功的,编译运行过程没出问题,但是结果是否通过看测试用例的结果
                    var _stdout = data.stdout;
                    var _stderr = data.stderr;

                    var stdout_lable = $("<pre>", {
                        text: _stdout
                    });

                    var stderr_lable = $("<pre>", {
                        text: _stderr
                    })

                    stdout_lable.appendTo(result_div);
                    stderr_lable.appendTo(result_div);
                }
                else{
                    // 编译运行出错,do nothing
                }
            }
        }
    </script>
</body>

</html>

3.4 相关测试


 4. MySQL版题目设计

4.1 注册用户 && 赋予权限

  • create user 'oj_client'@'localhost' identified by '123456';
  • create database oj;
  • grant select on oj.* to 'oj_client'@'localhost';
  • select user,Host from user;

 4.2 下载第三方工具-workbench

 

  •  下载下来之后,就不断的下一步,下一步就行了

4.3 录题到mysql中

use oj;
drop table if exists oj_table;

create table if not exists oj_table(
	_number varchar(200)  comment '题目编号',
    _titie varchar(200) comment '题目标题',
    _start varchar(200) comment '题目简单中等困难',
    _desc varchar(2000) comment '题目描述',
    _header varchar(2000) comment '题目预设',
    _tail varchar(2000) comment '题目测试用例',
    _cpu_limit int comment '时间要求',
    _mem_limt int comment '空间要求'
);

insert into oj_table values(
	1,
    '判断回文数',
    '简单',
    '判断一个整数是否是回文数。回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。

	示例 1:

	输入: 121
	输出: true
	示例 2:

	输入: -121
	输出: false
	解释: 从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。
	示例 3:

	输入: 10
	输出: false
	解释: 从右向左读, 为 01 。因此它不是一个回文数。
	进阶:

	你能不将整数转为字符串来解决这个问题吗?',
    '#include <iostream>
	#include <string>
	#include <vector>
	#include <map>
	#include <algorithm>

	using namespace std;

	class Solution{
		public:
			bool isPalindrome(int x)
			{
				//将你的代码写在下面
				
				return true;
			}
	};',
    '#ifndef COMPILER_ONLINE
	#include "header.cpp"
	#endif


	void Test1()
	{
		// 通过定义临时对象,来完成方法的调用
		bool ret = Solution().isPalindrome(121);
		if(ret){
			std::cout << "通过用例1, 测试121通过 ... OK!" << std::endl;
		}
		else{
			std::cout << "没有通过用例1, 测试的值是: 121"  << std::endl;
		}
	}

	void Test2()
	{
		// 通过定义临时对象,来完成方法的调用
		bool ret = Solution().isPalindrome(-10);
		if(!ret){
			std::cout << "通过用例2, 测试-10通过 ... OK!" << std::endl;
		}
		else{
			std::cout << "没有通过用例2, 测试的值是: -10"  << std::endl;
		}
	}

	int main()
	{
		Test1();
		Test2();

		return 0;
	}',
    1,
    30000
);


select * from oj_table;

 

  •  这里我只录入了一道题为了测试

4.4 下载并引入mysql库文件

MySQL :: Download MySQL Community Server

要使用C/C++连接MySQL,需要使用MySQL官网提供的库

 

 下载完毕后需要将其上传到云服务器,这里将下载的库文件存放在下面的目录:

然后使用tar命令将压缩包解压到当前目录下: 

xz -d mysql-8.0.37-linux-glibc2.28-i686.tar.xz

tar xvf mysql-8.0.37-linux-glibc2.28-i686.tar

进入解压后的目录当中,可以看到有一个include子目录和一个lib子目录,其中,include目录下存放的一堆头文件。而lib64目录下存放的就是动静态库。 

​ 


 然后在我们的项目中建立软连接

4.5 一个BUG

  • 如果你当时下载myql把mysql-devel也下载了,不需要进行上面步骤

  • 这种引入第三方库的操作,可能会因为版本不兼容,而导致出错
    skipping incompatible ./lib/libmysqlclient.so when searching for -lmysqlclient

  •  建议直接安装: yum -y install mysql-devel

 4.5 重新设计oj_model

因为oj_model模块是管理数据,提供接口的模块,所以要把这个项目变成mysql就需要重新设计

#pragma once
// 文件版本
#include "../comm/util.hpp"
#include "../comm/log.hpp"
#include <unordered_map>
#include <cassert>
#include <vector>

#include "./include/mysql.h"

namespace ns_model
{
    using namespace std;
    using namespace ns_log;
    using namespace ns_util;

    struct Question{
        string number;// 题目编号,唯一
        string title;// 题目标题
        string star;// 难度: 简单 中等 困难
        
        int cpu_limit;// 题目的时间复杂度(S)
        int mem_limit;// 题目的空间复杂度(KB)
        
        string desc;// 题目描述
        string header; // 题目预设给用户在线编辑器的代码
        string tail;// 题目测试用例,需要和header拼接
    };    

    const std::string oj_questions = "oj_table";
    const std::string host = "127.0.0.1";
    const std::string user = "oj_client";
    const std::string passwd = "123456";
    const std::string db = "oj";
    const int port = 3306;

    class Model
    {
    public:
        Model(){
        }
        ~Model(){
            ;
        }
        bool QueryMysql(const std::string &sql,vector<Question>*out){
            // 这里的out是输出型参数
            // 创建mysql句柄
            MYSQL *my = mysql_init(nullptr);
            // 连接数据库
            if(nullptr == mysql_real_connect(my,host.c_str(),user.c_str(),passwd.c_str(),db.c_str(),port,nullptr,0)){
                LOG(FATAL) << "连接数据库失败!" << "\n";
                return false;
            }

            // 一定要设置该链接的编码格式,要不然会出现乱码的问题
            mysql_set_character_set(my,"utf8");
            LOG(INFO) << "连接数据库成功!" << "\n";

            // 执行sql语句
            if(0 != mysql_query(my,sql.c_str())){
                LOG(WARNING) << sql << " execute error! " << "\n";
                return false;
            }

            // 提取结果
            MYSQL_RES *res = mysql_store_result(my);// 本质就是一个2级指针

            // 分析结果
            int rows = mysql_num_rows(res);// 获取行的数量
            int cols = mysql_num_fields(res);// 获取列的数量

            Question q;
            for(int i = 0;i < rows;i++){
                MYSQL_ROW row = mysql_fetch_row(res);
                q.number = row[0];
                q.title = row[1];
                q.star = row[2];
                q.desc = row[3];
                q.header = row[4];
                q.tail = row[5];
                q.cpu_limit = atoi(row[6]);
                q.mem_limit = atoi(row[7]);
                out->push_back(q);
            }

            // 释放控件
            free(res);
            // 关闭mysql连接
            mysql_close(my);

            return true;
        }
        // 获取所有题目,这里的out是输出型参数
        bool GetAllQuestions(vector<Question>*out){
            std::string sql = "select * from ";
            sql += oj_questions;
            return QueryMysql(sql,out);
        }

        // 获取指定题目,这里的q是输出型参数
        bool GetOneQuestion(const string& number,Question* q){
           bool res = false;
           std::string sql = "select * from ";
           sql += oj_questions;
           sql += " where number=";
           sql += number;

           vector<Question> result;
           if(QueryMysql(sql,&result)){
                if(result.size() == 1){
                    *q = result[0];
                    res = true;
                }
           }

           return res;
        }

        
    private:
        // 题号 : 题目细节
        unordered_map<string,Question> questions;
    };
} 

  • mysql_init: 创建mysql句柄
  • mysql_real_connect: 创建mysql连接
  • mysql_query: 发起mysql请求

  • mysql_close: 关闭mysql连接

 4.6 相关测试

  • 编译期间告诉编译器头文件和库文件在哪里 -I指明搜索的头文件,-L指明搜索的lib
  • 并加上-lmysqlclient

​ 

​ 

5. 扩展

  • 功能上更完善一下,判断一道题目正确之后,自动下一道题目
  • 基于注册和登陆的录题功能
    .....

6. 完整项目链接 

projects/负载均衡/OnlineJudge at main · 1LYC/projects · GitHub

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1829261.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

pikachu中pkxss数据库怎么创建

在用小皮时候&#xff0c;只是知道个pikachu这个数据库&#xff0c;跟着视频看人家用pkxss数据库&#xff0c;自己也想用&#xff0c;查看了很多资料&#xff0c;又蒙又查&#xff0c;终于明白怎么弄&#xff0c;特此传授经验 图像中画横线的就是平常怎么创建数据库的&#xff…

HP惠普暗影精灵10 OMEN Gaming Laptop 16-wf1xxx原厂Win11系统镜像下载

惠普hp暗影精灵10笔记本电脑16-wf1000TX原装出厂Windows11&#xff0c;恢复开箱状态oem预装系统安装包&#xff0c;带恢复重置还原 适用型号:16-wf1xxx 16-wf1000TX,16-wf1023TX,16-wf1024TX,16-wf1025TX, 16-wf1026TX,16-wf1027TX,16-wf1028TX,16-wf1029TX, 16-wf1030TX,16-…

C++ 算法教程

归并排序 #include<iostream> using namespace std; template <class T> void Merge(T data[],int start,int mid,int end) {int len1 mid - start 1, len2 end - mid;int i, j, k;T* left new int[len1];T* right new int[len2];for (i 0; i < len1; i)…

爆火的治愈系插画工具又来了,额度居然有18w,根本花不完?

AI治愈插画又又又来了 今天给大家推荐一款完全免费的软件&#xff0c;用过的人都说好&#xff01; 先来看看我生成的图 制作过程非常简单&#xff0c;输入你想要生成的画面咒语。 工具地址&#xff1a;https://www.qiyuai.net/ 模型目前有两种 我上面的图就是用的第一种通用…

Shell 学习笔记 - 导读 + 变量定义

初识 Shell 本章学习目标 了解什么是 Shell了解 Shell 的版本及用途掌握 Shell 变量的用法 导读&#xff08; 了解 \color{cyan}{了解} 了解&#xff09; 现在的人们使用的操作系统&#xff08;Windows、Android、iOS 等&#xff09;都带有图形化界面&#xff0c;简单直观&…

秋招突击——第五弹——Java的SSN框架快速入门——SpringBoot的使用

文章目录 引言SpringBoot简介入门案例SpringBoot项目快速启动 SpringBoot概述起步依赖辅助功能 基础配置配置文件格式基础配置Yaml格式书写Yaml格式读取多环境开发多环境启动命令格式多环境开发兼容问题配置文件分类 整合第三方技术整合Junit基于SpringBoot实现SSM整合Springbo…

axure9设置组件自适应浏览器大小

问题&#xff1a;预览时不展示下方的滚动条 方法一&#xff1a;转化为动态面板 1.在页面上创建一个矩形 2.右键-转化为动态面板 3.双击进入动态面板设置 4.设置动态面板矩形的颜色 5.删除原来的矩形 6.关闭动态面板&#xff0c;点击预览 7.此时可以发现底部没有滚动条了 方法…

23 华三(自动获取的IP地址)

华三交换机 DHCP 配置 #version 7.1.070, Alpha 7170 //设备的版本信息 #sysname sw1 //修改设备的名字 #irf mac-address persistent timerirf auto-update enableundo irf link-delayirf member 1 priority 1#dhcp enable //开启DHCP 服务dhcp server forbidden-ip 192.168.…

PDFFactoryFinePrint软件安装包下载+详细安装教程

简介&#xff1a; pdfFactory Pro(虚拟打印机)是一个无须 Acrobat 创建 Adobe PDF 文件的打印机驱动程序。 pdffactory pro虚拟打印机提供了比其他程序提供得更简单、更有效率和更少的花费的创建 PDF 文件的解决方案。用于需要安全的 PDF(法律文档、公司信息等)和其他高级功能…

Vue53-Todo-list案例

一、需求&#xff1a; 二、组件的划分&#xff1a;按照功能划分 组件起名&#xff0c;不要和html内置元素重名&#xff01; Vue鼓励组件名用多个单词。 三、组件化编码流程 3-1、实现静态组件 将各个组件的基本框架写好&#xff0c;并在App.vue文件中进行引入和注册。 将已有…

HTTP协议 快速入门

http概述 无状态性&#xff1a;HTTP是一个无状态协议&#xff0c;这意味着服务器不会在请求之间保存任何会话信息。每个请求都是独立的&#xff0c;服务器不会记住之前的请求。 请求-响应模型&#xff1a;HTTP通信是基于客户端发送请求和服务器返回响应的模型。客户端&#xf…

ffmpeg解封装rtsp并录制视频-(1)解封装rtsp断网或摄像机重启后自动重连处理

头文件&#xff1a; xtools.h #pragma once #include <thread> #include <iostream> #include <mutex> //日志级别 DEBUG INFO ERROR FATAL enum XLogLevel {XLOG_TYPE_DEBUG,XLOG_TYPE_INFO,XLOG_TPYE_ERROR,XLOG_TYPE_FATAL }; #define LOG_MIN_LEVEL XLO…

【Android】基于webView打造富文本编辑器(H5)

目录 前言一、实现效果二、具体实现1. 导入网页资源2. 页面设计3. 功能调用4. 完整代码 总结 前言 HTML5是构建Web内容的一种语言描述方式。HTML5是Web中核心语言HTML的规范&#xff0c;用户使用任何手段进行网页浏览时看到的内容原本都是HTML格式的&#xff0c;在浏览器中通过…

C语言实现动态栈

#include<stdio.h> #include<stdlib.h> #include<stdbool.h>// 每一个节点的数据类型 typedef struct Node {int data;struct Node * pNext; }NODE, * PNODE; // NODE等价 struct Node PNODE等价于 struct Node *// 栈 typedef struct Stack {PNODE pTop;P…

如何判断一个js对象是否存在循环引用

一、背景 在前端JSON.stringfy是我们常用的一个方法&#xff0c;可以将一个对象序列化。 例如将如下对象序列化 const person { name: kalory, age:18}JSON.stringfy(person) // 结果 {"name":"kalory","age":18}将一个数组序列化const arr …

Gson的常见用法

一引入依赖 <!-- json解析的工具包 --> <dependency><groupId>com.google.code.gson</groupId><artifactId>gson</artifactId><version>2.8.6</version> </dependency> <!-- 主要为了代码简洁和日志打印 --> <…

天翼云认证专家解决方案架构师(理论)

1.某大型互联网公司为了提升应用程序和基础设施的稳定性&#xff0c;计划引入自动化监控工具。以下哪些工具可以满足公司的需求? A.Grafana B.Nagios C.Prometheus D.Jenkins 2.天翼智能边缘云ECX是位于网络边缘位置的云&#xff0c;兼具云和CDN的特性&#xff0c;将计算、存…

Windows11安装并使用Gstreamer-1.0

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、下载二、使用步骤1.安装2.命令行测试 总结 前言 Gstreamer这个工具的重要性就不用多说了吧&#xff0c;在一些视频播放和解码领域大放异彩。以前更多的是在…

[AIGC] python遍历以及字符串的切片

以下是一篇关于Python遍历方法和字符串切片的文章&#xff0c;以及一个在LeetCode中的问题进行解释。文章最后会给出解题思路和代码实现。 Python遍历方法与字符串切片入门教程 在Python语言中&#xff0c;我们包含了许多内置的函数和方法令其适合于各种数据处理任务。在这个…

使用 Oracle SQL Developer 导入数据

使用 Oracle SQL Developer 导入数据 1. 导入过程 1. 导入过程 选择要导入数据的表&#xff0c; 然后单击右键&#xff0c;选择"导入数据"&#xff0c; 浏览本地文件&#xff0c;选择正确的工作表&#xff0c; 按默认&#xff0c; 按默认&#xff0c; 根据情况修改&…