负载均衡的在线OJ

1.所用技术与开发环境

所用技术 :
C++ STL 标准库
Boost 准标准库 ( 字符串切割 )
cpp - httplib 第三方开源网络库
ctemplate 第三方开源前端网页渲染库
jsoncpp 第三方开源序列化、反序列化库
负载均衡设计
多进程、多线程
MySQL C connect
Ace 前端在线编辑器
html/css/js/jquery/ajax 

2.开发环境

Centos 7 云服务器
vscode
Mysql Workbench

3. 项目宏观结构

我们的项目核心是三个模块
1. comm : 公共模块
2. compile_server : 编译与运行模块
3. oj_server : 获取题目列表,查看题目编写题目界面,负载均衡,其他功能

I. leetcode 结构

只实现类似 leetcode 的题目列表 + 在线编程功能

II. 我们的项目宏观结构

III. 编写思路

1. 先编写 compile_server
2. oj_server
3. version1 基于文件版的在线 OJ
4. 前端的页面设计
5. version2 基于 MySQL 版的在线 OJ

4. compiler 服务设计

提供的服务:编译并运行代码,得到格式化的相关的结果

 第一个功能 compiler :编译功能

#pragma once
#include <iostream>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "../comm/util.hpp"
#include "../comm/log.hpp"
//只负责代码的编译
namespace ns_compiler
{
    //引入路径拼接功能
    using namespace ns_util;
    using namespace ns_log;
    class Compiler
    {
    public:
        Compiler() = default;
        ~Compiler() = default;

        //返回值:编译成功:True 编译失败:False
        //输入参数:编译文件名
        // file_name: 123
        // 123 ->./temp/123.cpp
        // 123 ->./temp/123.exe
        // 123 ->./temp/123.stderr
        static bool Compile(const std::string &file_name)
        {
            pid_t pid = fork();
            if (pid < 0)
            {
                LOG(ERROR)<<"内部错误,创建子进程失败"<<"\n";
                return false;
            }
            else if (pid == 0)
            {
                umask(0);
                int _stderr = open(PathUtil::Error(file_name).c_str(), O_CREAT | O_WRONLY, 0644);
                if (_stderr < 0)
                {
                    LOG(WARNING)<<"没有形成stderr文件"<<"\n";
                    exit(1);
                }
                dup2(_stderr, 2); //将错误信息重定向到文件中

                //子进程使用程序替换完成代码的编译功能
                execlp("g++","g++", "-o", PathUtil::Exe(file_name).c_str(),PathUtil::Src(file_name).c_str(), "-std=c++11", nullptr /*不要忘记*/);
                LOG(ERROR) << "启动编译器g++失败,可能是参数传入有误"<<"\n";
                exit(2);
            }
            else
            {
                waitpid(pid, nullptr, 0);
                //编译是否成功,就看有没有形成同名的可执行文件
                if (FileUtil::IsFileExists(PathUtil::Exe(file_name)))
                {
                    LOG(INFO)<<PathUtil::Src(file_name)<<"编译成功"<<"\n";
                    return true;
                }
            }
            LOG(ERROR)<<"编译失败,没有形成可执行文件"<<"\n";
            return false;
        }
    };
}
Log 功能
#pragma once
#include <iostream>
#include <string>
#include "util.hpp"
namespace ns_log
{
    //日志等级
    enum
    {
        INFO,
        DEBUG,
        WARNING,
        ERROR,
        FATAL
    };
    //LOG()<<"message"
    inline std::ostream &Log(const std::string &level,const std::string &file_name,const int line)
    {
        //添加日志等级
        std::string message = "[";
        message += level;
        message += "]";

        //添加报错文件名称
        message += "[";
        message += file_name;
        message += "]";

        //添加报错行
        message += "[";
        message += std::to_string(line);
        message += "]";

        //日志时间戳
        message += "[";
        message += ns_util::TimeUtil::GetTimeStamp();
        message += "]";


        std::cout << message;

        return std::cout;
    }

    //开放日志
    #define LOG(level) Log(#level,__FILE__,__LINE__)
}
第二个功能 runner :运行功能
#pragma once
#include <iostream>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <sys/resource.h>
#include "../comm/util.hpp"
#include "../comm/log.hpp"


namespace ns_runner
{
    class Runner
    {
    public:
        Runner() = default;
        ~Runner() = default;

    public:
        static void SetProcLimit(int cpu_limit,int mem_limit)
        {
            //设置CPU时长
            struct rlimit _cpu_limit;
            _cpu_limit.rlim_max = RLIM_INFINITY;
            _cpu_limit.rlim_cur = cpu_limit;
            setrlimit(RLIMIT_CPU,&_cpu_limit);

            //设置内存大小

             struct rlimit _mem_limit;
             _mem_limit.rlim_max = RLIM_INFINITY;
             _mem_limit.rlim_cur = mem_limit*1024; //转化成KB
             setrlimit(RLIMIT_AS,&_mem_limit);
        }
        //返回值 > 0 ,程序异常,收到信号,返回值就是信号编号
        //返回值 == 0 ,正常运行完毕,结果保存到对应的临时文件
        //返回值 < 0 ,内部错误
        //cpu_limit:该程序运行的时候,可以使用的最大CPU资源上限
        //mem_limit:该程序运行的时候,可以使用最大内存
        static int Run(const std::string &file_name,int cpu_limit,int mem_limit)
        {
            //只考虑是否正确运行,不考虑结果是否正确
            /*
            一个程序在默认启动的时候
            标准输入:不处理
            标准输出:结果
            标准错误:运行时错误信息
            */
            std::string _exectue = ns_util::PathUtil::Exe(file_name);
            std::string _stdin = ns_util::PathUtil::Stdin(file_name);
            std::string _stdout = ns_util::PathUtil::Stdout(file_name);
            std::string _stderr = ns_util::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)
            {
                ns_log::LOG(ERROR)<<"运行时打开文件失败"<<"\n";
                return -1;  //文件打开失败
            }
            pid_t pid = fork();
            if (pid < 0)
            {
                ns_log::LOG(ERROR)<<"运行时创建子进程失败"<<"\n";
                close(_stdin_fd);
                close(_stdout_fd);
                close(_stderr_fd);
            }
            else if (pid == 0)
            {
                dup2(_stdin_fd,0);
                dup2(_stdout_fd,1);
                dup2(_stderr_fd,2);

                SetProcLimit(cpu_limit,mem_limit);
                execl(_exectue.c_str(),_exectue.c_str(),nullptr);
                exit(1);
            }
            else
            {
               // std::cout<<"关闭文件描述符"<<std::endl;
                close(_stdin_fd);
                close(_stdout_fd);
                close(_stderr_fd);
                int status = 0;
                //进程异常收到信号
                waitpid(pid, &status,0);
                ns_log::LOG(INFO)<<"运行完毕,info:"<< (status & 0x7F) << "\n";
                return status & 0x7F;
            }
            return 0;
        }
    };
}
测试资源限制:
#include <iostream> 
#include <sys/time.h> 
#include <sys/resource.h>
 #include <unistd.h> 
 #include <signal.h> 
 void handler(int signo) 
 { 
    std::cout << "signo : " << signo << std::endl; exit(1);
 }
 int main() 
 { 
    //资源不足,导致OS终止进程,是通过信号终止的 for(int i =1; i <= 31; i++)
    { signal(i, handler);
// struct rlimit r; 
    // r.rlim_cur = 1; 
    // r.rlim_max = RLIM_INFINITY;
     // setrlimit(RLIMIT_CPU, &r); 
     //while(1); struct rlimit r;
      r.rlim_cur = 1024 * 1024 * 40;
     //20M r.rlim_max = RLIM_INFINITY;
      setrlimit(RLIMIT_AS, &r); 
      int count = 0; while(true) 
      { 
        int *p = new int[1024*1024];
         count++; std::cout << "size: " << count << std::endl; 
         sleep(1); 
      }
         return 0; 
    }
}// 限
// 内存申请失败
terminate called after throwing an instance of 'std::bad_alloc'
what (): std::bad_alloc
signo : 6
[ whb@bite - alicloud OnlineJudge ] $ kill - l
1 ) SIGHUP 2 ) SIGINT 3 ) SIGQUIT 4 ) SIGILL 5 ) SIGTRAP
6 ) SIGABRT 7 ) SIGBUS 8 ) SIGFPE 9 ) SIGKILL 10 ) SIGUSR1
11 ) SIGSEGV 12 ) SIGUSR2 13 ) SIGPIPE 14 ) SIGALRM 15 ) SIGTERM
16 ) SIGSTKFLT 17 ) SIGCHLD 18 ) SIGCONT 19 ) SIGSTOP 20 ) SIGTSTP
        
//CPU 使用超时
[ whb@bite - alicloud OnlineJudge ] $ . / a . out
signo : 24
[ whb@bite - alicloud OnlineJudge ] $ kill - l
1 ) SIGHUP 2 ) SIGINT 3 ) SIGQUIT 4 ) SIGILL 5 ) SIGTRAP
6 ) SIGABRT 7 ) SIGBUS 8 ) SIGFPE 9 ) SIGKILL 10 ) SIGUSR1
11 ) SIGSEGV 12 ) SIGUSR2 13 ) SIGPIPE 14 ) SIGALRM 15 ) SIGTERM
16 ) SIGSTKFLT 17 ) SIGCHLD 18 ) SIGCONT 19 ) SIGSTOP 20 ) SIGTSTP
21 ) SIGTTIN 22 ) SIGTTOU 23 ) SIGURG 24 ) SIGXCPU 25 ) SIGXFSZ
26 ) SIGVTALRM 27 ) SIGPROF 28 ) SIGWINCH 29 ) SIGIO 30 ) SIGPWR
第三个功能 compile_run :编译并运行功能
#pragma once
#include <jsoncpp/json/json.h>
#include <signal.h>
#include <unistd.h>
#include <vector>
#include "compiler.hpp"
#include "complie_run.hpp"
#include "../comm/log.hpp"
#include "../comm/util.hpp"
#include "runner.hpp"
namespace ns_compile_and_run
{
    class ComplieAndRun
    {
    public:
        static std::string CodeToDesc(int  code, const std::string &file_name)
        {
            std::string desc;
            switch (code)
            {
            case 0:
                desc = "编译运行成功";
                break;
            case -1:
                desc = "用户提交的代码为空";
                break;
            case -2:
                desc = "未知错误";
                break;
            case -3:
                // desc = "代码编译是出现错误";
                ns_util::FileUtil::ReadFile(ns_util::PathUtil::Error(file_name), &desc, true);
                break;
            case SIGABRT: // 6
                desc = "内存超过范围";
                break;
            case SIGXCPU: // 24
                desc = "CPU使用超时";
                break;
            case SIGFPE:             // 8
                desc = "浮点数溢出"; //除0
                break;
            default:
                desc = "未知错误" + std::to_string(code);
                break;
            }
            return desc;
        }

    public: 
        static void RemoveTempFile(const std::string &file_name)
        {
            std::vector<std::string> AllTempFile{ns_util::PathUtil::Src(file_name),
                                                ns_util::PathUtil::Error(file_name),
                                                ns_util::PathUtil::Exe(file_name),
                                                ns_util::PathUtil::Stderr(file_name),
                                                ns_util::PathUtil::Stdin(file_name),
                                                ns_util::PathUtil::Stdout(file_name)};
            for(const auto &e :AllTempFile)
            {
                if(ns_util::FileUtil::IsFileExists(e));
                unlink(e.c_str());
            }
        }
        /*
            输入:
            code:用户给自己提交的代码
            input:用户给自己的代码对应的输入,不作处理(后期可以扩展)
            cpu_limit:时间复杂度
            mem_limit:时间复杂度
            输出:
            status:状态码(必填)
            reason:请求结果(必填)
            stdout:程序运行结果(选填)
            stderr:程序运行完的错误结果(选填)

        */
        //参数
        // in_json:{"code":"";"input":"";"cpu_limit":"";"mem_limit":"";}
        // out_json:{"status":"0";"reason":"";"stdout":"";"stderr":""}
        static void Start(const std::string &in_json, std::string *out_json)
        {
            Json::Value in_vaule;
            Json::Reader reader;
            reader.parse(in_json, in_vaule); //最后在差错处理

            std::string code = in_vaule["code"].asString();
            std::string input = in_vaule["input"].asString();
            int cpu_limit = in_vaule["cpu_limit"].asInt();
            int mem_limit = in_vaule["mem_limit"].asInt();
            Json::Value out_vaule;

            int status_code = 0;
            std::string file_name;
            int run_result = 0;
            if (code.size() == 0)
            {
                // //最后差错处理
                // out_vaule["status"] = -1; //代码为空
                // out_vaule["reason"] = "用户提交的代码是空的";
                // //序列化
                // return;
                status_code = -1;
                goto END;
            }
            //形成唯一文件名 毫秒级时间戳 + 原子性递增唯一值
            file_name = ns_util::FileUtil::UniqFileName();
            if (!ns_util::FileUtil::WiterFile(ns_util::PathUtil::Src(file_name), code)) //形成临时源src文件
            {
                // out_vaule["status"] = -2; //未知错误
                // out_vaule["reason"] = "提交的代码发生了未知错误";
                // //序列化
                // return;
                status_code = -2;
                goto END;
            }
            if (!ns_compiler::Compiler::Compile(file_name)) //编译失败
            {
                // out_vaule["status"] = -3;
                // //编译失败的内容保存到了.error文件中,读取序列化
                // out_vaule["reason"] = us_util::FileUtil::ReadFile(us_util::PathUtil::Error(file_name));
                // //序列化
                // return;
                status_code = -3;
                goto END;
            }
            run_result = ns_runner::Runner::Run(file_name, cpu_limit, mem_limit); //需要知道时间复杂度和空间复杂度
            if (run_result < 0)
            {
                // out_vaule["status"] = -2; //未知错误
                // out_vaule["reason"] = "发生了未知错误";
                // //序列化
                // return;
                status_code = -2;
                goto END;
            }
            else if (run_result > 0)
            {
                // out_vaule["status"] = -4;  //运行时报错,收到信号
                // out_vaule["reason"] = SignoToDesc(); //将信号转化成报错原因;
                // //序列化
                // return;
                status_code = run_result;
                goto END;
            }
            else
            {
                // //运行成功
                // out_vaule["status"] = 0;
                // out_vaule["reason"] = "运行成功";
                status_code = 0;
            }
        END:
            out_vaule["status"] = status_code;
            out_vaule["reason"] = CodeToDesc(status_code,file_name);
            if (status_code == 0)
            {
                //全部成功
                std::string _stdout;
                ns_util::FileUtil::ReadFile(ns_util::PathUtil::Stdout(file_name), &_stdout, true);
                out_vaule["stdout"] = _stdout;
               // std::cout<<"标准输出:"<<_stdout<<std::endl;
                std::string _stderr;
                ns_util::FileUtil::ReadFile(ns_util::PathUtil::Stderr(file_name), &_stderr, true);
                out_vaule["stderr"] = _stderr;
            }
            //序列化
            Json::StyledWriter writer;
            *out_json = writer.write(out_vaule);
            RemoveTempFile(file_name);
        }
    };
}

5. 基于MVC 结构的oj 服务设计

本质:建立一个小型网站
1. 获取首页,用题目列表充当
2. 编辑区域页面
3. 提交判题功能 ( 编译并运行 )
M : Model , 通常是和数据交互的模块,比如,对题库进行增删改查(文件版, MySQL
V : view , 通常是拿到数据之后,要进行构建网页,渲染网页内容,展示给用户的 ( 浏览器 )
C : control , 控制器,就是我们的核心业务逻辑
第一个功能:用户请求的服务路由功能
#include <iostream>
#include "../comm/httplib.h"
#include "oj_control.hpp"
using namespace httplib;

int main()
{
    //用户请求的的路由功能
    Server svr;
    ns_control::Control ctrl;
    //获取所有题目列表
    svr.Get("/all_questions",[&ctrl](const Request& req,Response &resq){
        //返回一张带有所有题目的html网页
        std::string html;
        ctrl.ALlQuestions(&html);
        resq.set_content(html,"text/html;charset=utf-8");
    });
    //获取要根据题目编号,获取题目的内容
    svr.Get(R"(/question/(\d+))",[&ctrl](const Request& req,Response &resq){
        std::string num = req.matches[1];
        std::string html;
        ctrl.Question(num,&html);
        resq.set_content(html,"text/html;charset=utf-8");
    });
    //用户提交代码,使用我们的判题功能()
    svr.Post(R"(/judge/(\d+))",[&ctrl](const Request& req,Response &resq){
        std::string number = req.matches[1];
        std::string result_json;
        ctrl.Judge(number,req.body,&result_json);
        resq.set_content(result_json,"application/json;charset=utf-8");
       // resq.set_content("指明题目的判题:"+num,"text/plain;charset=utf-8");
    });

    
    svr.set_base_dir("./wwwroot");
    svr.listen("0.0.0.0",8080);
    return 0;
}
第二个功能:model功能,提供对数据的操作
#pragma once
//文件版本
#include "../comm/util.hpp"
#include "../comm/log.hpp"

#include <iostream>
#include <string>
#include <vector>
#include <unordered_map>
#include <fstream>
#include <cstdlib>
#include <cassert>

// 根据题目list文件,加载所有的题目信息到内存中
// model: 主要用来和数据进行交互,对外提供访问数据的接口

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

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

    const std::string questins_list = "./questions/questions.list";
    const std::string questins_path = "./questions/";

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

    public:
        Model()
        {
            assert(LoadQuestionList(questins_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, " ");
                // 1 判断回文数 简单 1 30000
                if (tokens.size() != 5)
                {
                    LOG(WARNING) << "加载部分题目失败, 请检查文件格式" << "\n";
                    continue;
                }
                Question q;
                q.number = tokens[0];
                q.title = tokens[1];
                //std::cout<<q.title<<std::endl;
                q.star = tokens[2];
                q.cpu_limit = atoi(tokens[3].c_str());
                q.mem_limit = atoi(tokens[4].c_str());

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

                FileUtil::ReadFile(path + "desc.txt", &(q.desc), true);
                FileUtil::ReadFile(path + "header.cpp", &(q.header), true);
                FileUtil::ReadFile(path + "tail.cpp", &(q.tail), true);

                questions.insert({q.number, q});
            }
            LOG(INFO) << "加载题库...成功!"
                      << "\n";
            in.close();

            return true;
        }
        bool GetAllQuestions(vector<Question> *out)
        {
            if (questions.size() == 0)
            {
                LOG(ERROR) << "用户获取题库失败"<< "\n";
                return false;
            }
            for (const auto &q : questions)
            {
                out->push_back(q.second); // first: key, second: value
            }

            return true;
        }
        bool GetOneQuestion(const std::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;
        }
        ~Model()
        {
        }
    };
} // namespace ns_model
第三个功能:control,逻辑控制模块
#pragma once
#include <iostream>
#include <string>
#include <mutex>
#include <cassert>
#include <fstream>
#include <jsoncpp/json/json.h>
#include "oj_model.hpp"
#include "../comm/log.hpp"
#include "../comm/util.hpp"
#include "oj_view.hpp"
#include "../comm/httplib.h"
namespace ns_control
{
    const std::string service_machine = "./conf/service_machine.conf";
    //提供服务的主机
    class Machine
    {
    public:
        std::string ip; //编译服务的ip
        int 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(); 
        }
        uint64_t Load() //获取负载
        {
            uint64_t _load = 0;
             if(mtx) mtx->lock();
            _load = load;
            if(mtx) mtx->unlock(); 

            return _load;
        }
    };
    //负载均衡模块
    class LoadBlance
    {
    private:
        std::vector<Machine> machines; //可以提供编译服务所有主机
        std::vector<int> online; //所有在线的主机
        std::vector<int> offline; //所有离线主机
        std::mutex mtx;           //保证LoadBlance数据安全
    public:
        LoadBlance()
        {
            assert(LoadConf(service_machine));
            ns_log::LOG(ns_log::INFO)<<" 加载 "<<service_machine<<" 成功 "<<"\n";
        }
        ~LoadBlance()
        {

        }
    public:
        bool LoadConf(const std::string &machine_conf)
        {
            std::ifstream in(machine_conf);
            if(!in.is_open())
            {
                ns_log::LOG(ns_log::FATAL)<<"加载配置:"<<machine_conf<<"文件失败"<<"\n";
                return false;
            }
            std::string line;
            while(getline(in,line))
            {
                std::vector<std::string> tokens;
                ns_util::StringUtil::SplitString(line,&tokens,":");

                if(tokens.size() != 2)
                {
                     ns_log::LOG(ns_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();
                ns_log::LOG(ns_log::FATAL) << "后端编译服务全部挂掉了,请运维的老铁尽快查看"<<"\n";
                return false;
            }
            //通过编译找到负载最小的机器
            *id = online[0];
            *m =  &machines[online[0]];
            uint64_t min_load = machines[online[0]].Load();
            for(int i = 0; i < online_num; ++i)
            {
                min_load = min_load < machines[online[i]].Load() ? machines[online[i]].Load() : min_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)
                {
                    online.erase(iter);
                    offline.push_back(which);
                    break;
                }
            }
            mtx.unlock();
        }
        void OnlineMachine()
        {

        }
        void ShowMachines()
        {
            mtx.lock();
            std::cout<<"当前在线主机列表:";
            for(auto &id : online)
            {
                std::cout << id <<" ";
            }
            std::cout<<std::endl;
            for(auto &id : offline)
            {
             std::cout<<"当前离线主机列表:";
                std::cout << id << " ";
            }
            std::cout<<std::endl;
            mtx.unlock();
        }
    };


    class Control
    {
    private:
        ns_model::Model _model; //提供后台服务
        ns_view::View _view;    //提供html渲染功能
        LoadBlance _load_blance; //提供负载均衡器
    public:
        Control()
        {

        }
        //根据题目数据构建网页
        bool ALlQuestions(std::string *html)
        {
            std::vector<ns_model::Question> all;
            if(_model.GetAllQuestions(&all))
            {
                //获取题目信息成功,将所有的题目数据构建成网页
                _view.AllExpandHtml(all,html);
            }
            else
            {
                *html = "获取题目失败,形成题目列表失败";
                return false;
            }
            return true;
        }
        bool Question(const std::string number,std::string *html)
        {
            ns_model::Question q;
            if(_model.GetOneQuestion(number,&q))
            {
                //获取指定题目成功,将题目数据构建成网页
                _view.OneExpandHtml(q,html);
            }
            else
            {
                *html = "指定题目" + number + "不存在";
                return false;
            }
            return true;
        }
        void Judge(const std::string& number, const std::string in_json,std::string *out_json)
        {
            //0.根据题号,直接拿到题目细节
            ns_model::Question q;
            _model.GetOneQuestion(number,&q);
            //1.in_json进行反序列化,得到题目的id,得到用户提交的源代码,input
            Json::Reader reader;
            Json::Value in_value;
            reader.parse(in_json,in_value);
            //2.重新拼接用户代码 + 测试用例代码,形成新代码
            std::string code = in_value["code"].asString();
            Json::Value compile_value;
            compile_value["input"] = in_value["input"].asString();
            compile_value["code"] = code + q.tail;
            compile_value["cpu_limit"] = q.cpu_limit;
            compile_value["mem_limit"] = q.mem_limit;
            Json::FastWriter writer;
            std::string complie_string = writer.write(compile_value);
            //3.选择负载最低的主机(差错处理)
            for( ; ;)
            {
                int id = 0;
                Machine *m = nullptr;
                if(!_load_blance.SmartChoice(&id,&m))
                {
                    break;
                }
                ns_log::LOG(ns_log::INFO) <<"选择主机成功"<<id<<"详情"<<m->ip<<":"<<m->port<<"\n";
                //4.然后发起http请求,得到结果
                httplib::Client cli(m->ip,m->port);
                m->IncLoad();
                if(auto res = cli.Post("/compile_and_run",complie_string, "application/json;charset=utf-8"))
                {
                    //5.将结果赋值给out_json
                    if(res->status == 200)
                    {
                         *out_json = res->body;
                         m->DecLoad();
                         break;
                    }
                     m->DecLoad();
                }
                else
                {
                    //请求失败
                    ns_log::LOG(ns_log::ERROR)<<"详情:" << id <<":"<< m->ip << ":"<<m->port<<"可能已经离线"<<"\n";
                    _load_blance.OfflineMachine(id);
                    _load_blance.ShowMachines();
                }   
            }  
        }
        ~Control()
        {

        }
    };
}
附加功能:需要有数据渲染
// 如果后续引入了 ctemplate ,一旦对网页结构进行修改,尽量的每次想看到结果,将 server 重启一下。 ctemplate 有 自己的优化加速策略,可能在内存中存在缓存网页数据(old)

 

当我们完成全部功能之后,需要注意:
要给编译模块添加—D条件编译掉测试用例中的头文件incldue

6. version1 文件版题目设计

1. 题目的编号
2. 题目的标题
3. 题目的难度
4. 题目的描述,题面
5. 时间要求 ( 内部处理 )
6. 空间要求 ( 内部处理 )
两批文件构成
1. 第一个: questions . list : 题目列表(不需要题目的内容)
2. 第二个:题目的描述,题目的预设置代码 ( header . cpp ), 测试用例代码 ( tail . cpp )
这两个内容是通过题目的编号,产生关联的
1. 当用户提交自己的代码的时候: 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;
}

后端全部写完使用Postman 来测试

 

 

 

7. 前端页面设计

8. version2 MySQL版题目设计

9.综合测试

10.项目扩展思路

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值