• 实战项目: 负载均衡


    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模块只考虑是否正确运行完毕

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include // fork接口需要
    7. #include "../comm/log.hpp"
    8. #include "../comm/util.hpp"
    9. namespace ns_runner
    10. {
    11. using namespace ns_util;
    12. using namespace ns_log;
    13. class Runner
    14. {
    15. public:
    16. Runner(){}
    17. ~Runner(){}
    18. static int Run(const std::string &file_name){
    19. std::string _execute = PathUtil::Exe(file_name);// 可执行
    20. std::string _stdin = PathUtil::Stdin(file_name);// 输入
    21. std::string _stdout = PathUtil::Stdout(file_name);// 输出
    22. std::string _stderr = PathUtil::Stderr(file_name);// 错误
    23. umask(0);
    24. int _stdin_fd = open(_stdin.c_str(),O_CREAT | O_RDONLY,0644);
    25. int _stdout_fd = open(_stdout.c_str(),O_CREAT | O_WRONLY,0644);
    26. int _stderr_fd = open(_stderr.c_str(),O_CREAT | O_WRONLY,0644);
    27. if(_stdin_fd < 0 || _stdout_fd < 0 || _stderr_fd < 0){
    28. LOG(ERROR) << "运行时打开标准文件失败" << "\n";
    29. return -1;// 代表打开文件失败
    30. }
    31. pid_t pid = fork();
    32. if(pid < 0){
    33. LOG(ERROR) << "运行创建子进程失败" << "\n";
    34. close(_stdin_fd);
    35. close(_stdout_fd);
    36. close(_stderr_fd);
    37. return -2;// 代表创建自己失败
    38. }
    39. else if(pid == 0){
    40. // 子进程
    41. dup2(_stdin_fd,0);
    42. dup2(_stdout_fd,1);
    43. dup2(_stderr_fd,2);
    44. LOG(INFO) << "123";// 是不是有问题啊
    45. // 这个程序替换等价于 ./tmp/code.exe ./tmp/code.exe
    46. execl(_execute.c_str(),_execute.c_str(),nullptr);
    47. exit(1);
    48. }
    49. else{
    50. close(_stdin_fd);
    51. close(_stdout_fd);
    52. close(_stderr_fd);
    53. int status = 0;// 表示输出型参数
    54. waitpid(pid,&status,0);// 阻塞式等待
    55. // 程序运行异常,一定是因为收到信号
    56. LOG(INFO) << "运行完毕,infor: " << (status & 0x7f) << "\n";
    57. return status & 0x7f;
    58. }
    59. }
    60. };
    61. }

    • 返回值 > 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

    1. #pragma once
    2. #include "compiler.hpp"
    3. #include "runner.hpp"
    4. #include "../comm/log.hpp"
    5. #include "../comm/util.hpp"
    6. #include
    7. namespace ns_compile_and_run
    8. {
    9. using namespace ns_log;
    10. using namespace ns_util;
    11. using namespace ns_compiler;
    12. using namespace ns_runner;
    13. // in_json: {"code": "#include...", "input": "","cpu_limit":1, "mem_limit":10240}
    14. // out_json: {"status":"0", "reason":"","stdout":"","stderr":"",}
    15. static void Start(const std::string &in_json,std::string *out_json){
    16. // step1: 反序列化过程
    17. Json::Value in_value;
    18. Json::Reader reader;
    19. // 把in_json中的数据写到in_value中
    20. reader.parse(in_json,in_value);// 最后再处理差错问题
    21. std::string code = in_value["code"].asString();
    22. std::string input = in_value["input"].asString();
    23. int cpu_limit = in_value["cpu_limit"].asInt();
    24. int mem_limit = in_value["mem_limit"].asInt();
    25. Json::Value out_value;
    26. int status_code = 0;
    27. int run_result = 0;
    28. std::string file_name;// 唯一文件名
    29. if(code.size() == 0){
    30. status_code = -1;// 代码为空
    31. goto END;
    32. }
    33. // 形成的文件名只居有唯一性,没有目录没有后缀
    34. // 使用: 毫秒级时间戳 + 原子性递增唯一值 : 来保证唯一性
    35. file_name = FileUtil::UniqFileName();
    36. // 形成临时的src文件
    37. if(!FileUtil::WriteFile(PathUtil::Src(file_name),code)){
    38. status_code = -2;// 未知错误
    39. goto END;
    40. }
    41. if(!Compiler::Compile(file_name)){
    42. status_code = -3;// 编译错误
    43. goto END;
    44. }
    45. run_result = Runner::Run(file_name,cpu_limit,mem_limit);
    46. if(run_result < 0){
    47. // runnem模块内部错误
    48. status_code = -2;// 未知错误
    49. }
    50. else if(run_result > 0){
    51. // 程序运行崩溃
    52. status_code = run_result;// 这里的run_result是信号
    53. }
    54. else{
    55. // 运行成功
    56. status_code = 0;
    57. }
    58. END:
    59. out_value["status"] = status_code;
    60. out_value["reason"] = CodeToDest(status_code,file_name);// 得到错误信息字符串
    61. if(status_code == 0){
    62. // 整个过程全部成功
    63. std::string _stdout;
    64. FileUtil::ReadFile(PathUtil::Stdout(file_name),&_stdout,true);
    65. out_value["stdout"] = _stdout;
    66. std::string _stderr;
    67. FileUtil::ReadFile(PathUtil::Stdout(file_name),&_stderr,true);
    68. out_value["stdout"] = _stdout;
    69. }
    70. // step2: 序列化
    71. Json::StyledWriter writer;
    72. *out_json = writer.write(out_value);
    73. }
    74. }

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


     

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

    1.1.8 测试编译运行模块 

    1. #include "compile_run.hpp"
    2. using namespace ns_compile_and_run;
    3. int main()
    4. {
    5. std::string in_json;
    6. Json::Value in_value;
    7. // R"()", raw string
    8. in_value["code"] = R"(#include
    9. int main(){
    10. std::cout << "你可以看见我了" << std::endl;
    11. return 0;
    12. })";
    13. in_value["input"] = "";
    14. in_value["cpu_limit"] = 1;
    15. in_value["mem_limit"] = 10240 * 3;
    16. Json::FastWriter writer;
    17. in_json = writer.write(in_value);
    18. // std::cout << in_json << std::endl;
    19. // 这个是将来给客户端返回的json串
    20. std::string out_json;
    21. CompileAndRun::Start(in_json, &out_json);
    22. std::cout << out_json << std::endl;
    23. return 0;
    24. }

    •  实际上这里的代码应该是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

    1. #include "compile_run.hpp"
    2. #include "../comm/httplib.h"// 引入
    3. using namespace ns_compile_and_run;
    4. using namespace httplib;// 引入
    5. void Usage(std::string proc){
    6. std::cerr << "Usage: " << "\n\t" << proc << " port" << std::endl;
    7. }
    8. //./copile_server port
    9. int main(int argc,char *argv[])
    10. {
    11. if(argc != 2){
    12. Usage(argv[0]);
    13. return 1;
    14. }
    15. Server svr;
    16. svr.Post("/compile_and_run",[](const Request&req,Response & resp){
    17. // 用户请求的服务正文是我们想要的json string
    18. std::string in_json = req.body;
    19. std::string out_json;
    20. if(!in_json.empty()){
    21. CompileAndRun::Start(in_json,&out_json);
    22. resp.set_content(out_json,"application/json;charset=uft-8");
    23. }
    24. });
    25. svr.listen("0.0.0.0",atoi(argv[1]));
    26. return 0;
    27. }
    • 由于我这里没有写客户端代码,则这里暂时不好测试,不过可以借助第三方工具进行测试

     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

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. using namespace std;
    7. class Solution{
    8. public:
    9. bool isPalindrome(int x)
    10. {
    11. //将你的代码写在下面
    12. return true;
    13. }
    14. };

    tail.cpp

    1. #ifndef COMPILER_ONLINE
    2. #include "header.cpp"
    3. #endif
    4. // 这里先把测试用例 暴露出来
    5. void Test1()
    6. {
    7. // 通过定义临时对象,来完成方法的调用
    8. bool ret = Solution().isPalindrome(121);
    9. if(ret){
    10. std::cout << "通过用例1, 测试121通过 ... OK!" << std::endl;
    11. }
    12. else{
    13. std::cout << "没有通过用例1, 测试的值是: 121" << std::endl;
    14. }
    15. }
    16. void Test2()
    17. {
    18. // 通过定义临时对象,来完成方法的调用
    19. bool ret = Solution().isPalindrome(-10);
    20. if(!ret){
    21. std::cout << "通过用例2, 测试-10通过 ... OK!" << std::endl;
    22. }
    23. else{
    24. std::cout << "没有通过用例2, 测试的值是: -10" << std::endl;
    25. }
    26. }
    27. int main()
    28. {
    29. Test1();
    30. Test2();
    31. return 0;
    32. }

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

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

    2.3.3 model功能(oj_model.cpp)

    数据交互 && 提供接口

     

    1. #pragma once
    2. // 文件版本
    3. #include "../comm/util.hpp"
    4. #include "../comm/log.hpp"
    5. #include
    6. #include
    7. #include
    8. namespace ns_model
    9. {
    10. using namespace std;
    11. using namespace ns_log;
    12. using namespace ns_util;
    13. struct Question{
    14. string number;// 题目编号,唯一
    15. string tile;// 题目标题
    16. string star;// 难度: 简单 中等 困难
    17. int cpu_limit;// 题目的时间复杂度(S)
    18. int mem_limit;// 题目的空间复杂度(KB)
    19. string desc;// 题目描述
    20. string header; // 题目预设给用户在线编辑器的代码
    21. string tail;// 题目测试用例,需要和header拼接
    22. };
    23. const string questions_list = "./question/quetions.list";
    24. const string questions_path = "./question";
    25. class Model
    26. {
    27. public:
    28. Model(){
    29. // 加载所有题目:底层是用hash表映射的
    30. assert(LoadQuestionList(questions_list));
    31. }
    32. ~Model(){
    33. ;
    34. }
    35. // 获取所有题目,这里的out是输出型参数
    36. bool GetAllQuestions(vector*out){
    37. if(questions.size() == 0){
    38. LOG(ERROR) << "用户获取题库失败" << "\n";
    39. return false;
    40. }
    41. for(const auto&q: questions){
    42. out->push_back(q.second);
    43. }
    44. return true;
    45. }
    46. // 获取指定题目,这里的q是输出型参数
    47. bool GetOneQuestion(const string& number,Question* q){
    48. const auto& iter = questions.find(number);
    49. if(iter == questions.end()){
    50. LOG(ERROR) << "用户获取题目失败,题目编号: " << number << "\n";
    51. return false;
    52. }
    53. (*q) = iter->second;
    54. return true;
    55. }
    56. // 加载配置文件: questions/questions.list + 题目编号文件
    57. bool LoadQuestionList(const string&question_list){
    58. // 加载配置文件: questions/questions.list +题目编号文件
    59. ifstream in(question_list);
    60. if(!in.is_open()){
    61. LOG(FATAL) << "加载题库失败,请检查是否存在题库文件" << "\n";
    62. return false;
    63. }
    64. string line;
    65. while(getline(in,line)){
    66. vectortokens;
    67. StringUtil::SplitString(line,&tokens," ");// 被分割的字符串 缓冲区 分割符
    68. // eg: 1 判断回文数 简单 1 30000
    69. if(tokens.size()!=5){
    70. LOG(WARNING) << "加载部分题目失败,请检查文件格式" << "\n";
    71. continue;
    72. }
    73. Question q;
    74. q.number = tokens[0];
    75. q.tile = tokens[1];
    76. q.star = tokens[2];
    77. q.cpu_limit = atoi(tokens[3].c_str());
    78. q.mem_limit = atoi(tokens[4].c_str());
    79. string path = questions_list;
    80. path += q.number;
    81. path += "/";
    82. // 第三个参数代表 是否加上 \n
    83. FileUtil::ReadFile(path+"desc.txt",&(q.desc),true);
    84. FileUtil::ReadFile(path+"header.cpp",&(q.header),true);
    85. FileUtil::ReadFile(path+"tail.txt",&(q.tail),true);
    86. questions.insert({q.number,q});// 录题成功
    87. }
    88. LOG(INFO) << "加载题库...成功" << "\n";
    89. in.close();
    90. }
    91. private:
    92. // 题号 : 题目细节
    93. unordered_map questions;
    94. };
    95. }

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

    2.4.1 负载均衡模块

    1. namespace ns_control
    2. {
    3. using namespace std;
    4. using namespace ns_log;
    5. using namespace ns_util;
    6. using namespace ns_model;
    7. using namespace ns_view;
    8. using namespace httplib;
    9. // 提供服务的主机
    10. class Machine
    11. {
    12. public:
    13. std::string ip; //编译服务的ip
    14. int port; //编译服务的port
    15. uint64_t load; //编译服务的负载
    16. std::mutex *mtx; // mutex禁止拷贝的,使用指针
    17. public:
    18. Machine() : ip(""), port(0), load(0), mtx(nullptr)
    19. {
    20. }
    21. ~Machine()
    22. {
    23. }
    24. public:
    25. // 提升主机负载
    26. void IncLoad()
    27. {
    28. if (mtx) mtx->lock();
    29. ++load;
    30. if (mtx) mtx->unlock();
    31. }
    32. // 减少主机负载
    33. void DecLoad()
    34. {
    35. if (mtx) mtx->lock();
    36. --load;
    37. if (mtx) mtx->unlock();
    38. }
    39. void ResetLoad()
    40. {
    41. if(mtx) mtx->lock();
    42. load = 0;
    43. if(mtx) mtx->unlock();
    44. }
    45. // 获取主机负载,没有太大的意义,只是为了统一接口
    46. uint64_t Load()
    47. {
    48. uint64_t _load = 0;
    49. if (mtx) mtx->lock();
    50. _load = load;
    51. if (mtx) mtx->unlock();
    52. return _load;
    53. }
    54. };
    55. const std::string service_machine = "./conf/service_machine.conf";
    56. class LoadBlance
    57. {
    58. private:
    59. // 可以给我们提供编译服务的所有的主机
    60. // 每一台主机都有自己的下标,充当当前主机的id
    61. std::vector machines;
    62. // 所有在线的主机id
    63. std::vector<int> online;
    64. // 所有离线的主机id
    65. std::vector<int> offline;
    66. // 保证LoadBlance它的数据安全
    67. std::mutex mtx;
    68. public:
    69. LoadBlance()
    70. {
    71. assert(LoadConf(service_machine));
    72. LOG(INFO) << "加载 " << service_machine << " 成功"
    73. << "\n";
    74. }
    75. ~LoadBlance()
    76. {
    77. }
    78. public:
    79. bool LoadConf(const std::string &machine_conf)
    80. {
    81. std::ifstream in(machine_conf);
    82. if (!in.is_open())
    83. {
    84. LOG(FATAL) << " 加载: " << machine_conf << " 失败"
    85. << "\n";
    86. return false;
    87. }
    88. std::string line;
    89. while (std::getline(in, line))
    90. {
    91. std::vector tokens;
    92. StringUtil::SplitString(line, &tokens, ":");
    93. if (tokens.size() != 2)
    94. {
    95. LOG(WARNING) << " 切分 " << line << " 失败"
    96. << "\n";
    97. continue;
    98. }
    99. Machine m;
    100. m.ip = tokens[0];
    101. m.port = atoi(tokens[1].c_str());
    102. m.load = 0;
    103. m.mtx = new std::mutex();
    104. online.push_back(machines.size());
    105. machines.push_back(m);
    106. }
    107. in.close();
    108. return true;
    109. }
    110. // id: 输出型参数
    111. // m : 输出型参数
    112. bool SmartChoice(int *id, Machine **m)
    113. {
    114. // 1. 使用选择好的主机(更新该主机的负载)
    115. // 2. 我们需要可能离线该主机
    116. mtx.lock();
    117. // 负载均衡的算法
    118. // 1. 随机数+hash
    119. // 2. 轮询+hash
    120. int online_num = online.size();
    121. if (online_num == 0)
    122. {
    123. mtx.unlock();
    124. LOG(FATAL) << " 所有的后端编译主机已经离线, 请运维的同事尽快查看"
    125. << "\n";
    126. return false;
    127. }
    128. // 通过遍历的方式,找到所有负载最小的机器
    129. *id = online[0];
    130. *m = &machines[online[0]];
    131. uint64_t min_load = machines[online[0]].Load();
    132. for (int i = 1; i < online_num; i++)
    133. {
    134. uint64_t curr_load = machines[online[i]].Load();
    135. if (min_load > curr_load)
    136. {
    137. min_load = curr_load;
    138. *id = online[i];
    139. *m = &machines[online[i]];
    140. }
    141. }
    142. mtx.unlock();
    143. return true;
    144. }
    145. void OfflineMachine(int which)
    146. {
    147. mtx.lock();
    148. for(auto iter = online.begin(); iter != online.end(); iter++)
    149. {
    150. if(*iter == which)
    151. {
    152. machines[which].ResetLoad();
    153. //要离线的主机已经找到啦
    154. online.erase(iter);
    155. offline.push_back(which);
    156. break; //因为break的存在,所有我们暂时不考虑迭代器失效的问题
    157. }
    158. }
    159. mtx.unlock();
    160. }
    161. void OnlineMachine()
    162. {
    163. //我们统一上线,后面统一解决
    164. mtx.lock();
    165. online.insert(online.end(), offline.begin(), offline.end());
    166. offline.erase(offline.begin(), offline.end());
    167. mtx.unlock();
    168. LOG(INFO) << "所有的主机有上线啦!" << "\n";
    169. }
    170. //for test
    171. void ShowMachines()
    172. {
    173. mtx.lock();
    174. std::cout << "当前在线主机列表: ";
    175. for(auto &id : online)
    176. {
    177. std::cout << id << " ";
    178. }
    179. std::cout << std::endl;
    180. std::cout << "当前离线主机列表: ";
    181. for(auto &id : offline)
    182. {
    183. std::cout << id << " ";
    184. }
    185. std::cout << std::endl;
    186. mtx.unlock();
    187. }
    188. };
    189. }

    2.4.1 control功能(oj_control.hpp)

    逻辑控制模块

    1. // 这是我们的核心业务逻辑的控制器
    2. class Control
    3. {
    4. private:
    5. Model model_; //提供后台数据
    6. View view_; //提供html渲染功能
    7. LoadBlance load_blance_; //核心负载均衡器
    8. public:
    9. Control()
    10. {
    11. }
    12. ~Control()
    13. {
    14. }
    15. public:
    16. void RecoveryMachine()
    17. {
    18. load_blance_.OnlineMachine();
    19. }
    20. //根据题目数据构建网页
    21. // html: 输出型参数
    22. bool AllQuestions(string *html)
    23. {
    24. bool ret = true;
    25. vector<struct Question> all;
    26. if (model_.GetAllQuestions(&all))
    27. {
    28. sort(all.begin(), all.end(), [](const struct Question &q1, const struct Question &q2){
    29. return atoi(q1.number.c_str()) < atoi(q2.number.c_str());
    30. });
    31. // 获取题目信息成功,将所有的题目数据构建成网页
    32. // ...
    33. }
    34. else
    35. {
    36. *html = "获取题目失败, 形成题目列表失败";
    37. ret = false;
    38. }
    39. return ret;
    40. }
    41. bool Question(const string &number, string *html)
    42. {
    43. bool ret = true;
    44. struct Question q;
    45. if (model_.GetOneQuestion(number, &q))
    46. {
    47. // 获取指定题目信息成功,将所有的题目数据构建成网页
    48. // ....
    49. }
    50. else
    51. {
    52. *html = "指定题目: " + number + " 不存在!";
    53. ret = false;
    54. }
    55. return ret;
    56. }
    57. // code: #include...
    58. // input: ""
    59. void Judge(const std::string &number, const std::string in_json, std::string *out_json)
    60. {
    61. }
    62. };
    •  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 

    1. #include
    2. #include
    3. #include
    4. int main()
    5. {
    6. std::string html = "./test.html";
    7. std::string html_info = "测试ctemplate渲染";
    8. // 建立ctemplate参数目录结构
    9. ctemplate::TemplateDictionary root("test"); // unordered_map test;
    10. // 向结构中添加你要替换的数据,kv的
    11. root.SetValue("info", html_info); // test.insert({key, value});
    12. // 获取被渲染对象
    13. // DO_NOT_STRIP:保持html网页原貌
    14. ctemplate::Template *tpl = ctemplate::Template::GetTemplate(html,ctemplate::DO_NOT_STRIP);
    15. // 开始渲染,返回新的网页结果到out_html
    16. std::string out_html;
    17. tpl->Expand(&out_html, &root);
    18. std::cout << "渲染的带参html是:" << std::endl;
    19. std::cout << out_html << std::endl;
    20. return 0;
    21. }

     test.html

    1. "en">
    2. "UTF-8">
    3. "X-UA-Compatible" content="IE=edge">
    4. "viewport" content="width=device-width, initial-scale=1.0">
    5. Document
    6. {{info}}

    7. {{info}}

    8. {{info}}

    9. {{info}}


     错误原因: 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)

    1. #pragma once
    2. #include
    3. #include
    4. #include "./oj_model.hpp"
    5. namespace ns_view
    6. {
    7. using namespace ns_model;
    8. const std::string template_path = "./template_html/";
    9. class View
    10. {
    11. public:
    12. View(){}
    13. ~View(){}
    14. // 渲染所有题目
    15. void ALLExpandHtml(const vector<struct Question>&question,std::string *html){
    16. // 题目编号 题目标题 题难度
    17. // 推荐表格实现
    18. // 1.形成路径
    19. string src_html = template_path + "all_quetions.html";
    20. // 2.形成数字典
    21. ctemplate::TemplateDictionary root("all_question");
    22. for(const auto& q: question){
    23. ctemplate::TemplateDictionary *sub = root.AddSectionDictionary("question_list");
    24. sub->SetValue("number",q.number);
    25. sub->SetValue("title",q.title);
    26. sub->SetValue("star",q.star);
    27. }
    28. // 3. 获取被渲染的html
    29. ctemplate::Template*tpl = ctemplate::Template::GetTemplate(src_html,ctemplate::DO_NOT_STRIP);
    30. // 4.开始完成渲染功能
    31. tpl->Expand(html,&root);
    32. }
    33. // 渲染一道题目
    34. void OneExpandHtml(const struct Question &q,string *html){
    35. // 1.形成路径
    36. std::string src_html = template_path + "one_question.html";
    37. // 2. 形成数字典
    38. ctemplate::TemplateDictionary root("one_question");
    39. root.SetValue("number",q.number);
    40. root.SetValue("title",q.title);
    41. root.SetValue("star",q.star);
    42. root.SetValue("desc",q.desc);
    43. root.SetValue("header",q.header);
    44. // 3.获取被渲染的html
    45. ctemplate::Template*tpl = ctemplate::Template::GetTemplate(src_html,ctemplate::DO_NOT_STRIP);
    46. // 4.开始完成渲染功能
    47. tpl->Expand(html,&root);
    48. }
    49. };
    50. }

    2.6  联动MVC模块并测试


    oj_server.cc 

    1. #include
    2. #include "../comm/httplib.h"// 引入
    3. #include "oj_control.hpp"
    4. using namespace httplib;// 引入
    5. using namespace ns_control;
    6. int main()
    7. {
    8. // 用户请求的服务器路由功能
    9. Server svr;
    10. Control ctrl;
    11. // 获取所有的题目列表
    12. svr.Get("/all_questions",[&ctrl](const Request&req,Response &resp){
    13. // 返回一张包含所有题目的html网页
    14. std::string html;// 待处理
    15. ctrl.AllQuestions(&html);
    16. resp.set_content(html,"text/html;charset=utf-8");
    17. });
    18. // 根据题目编号,获取题目内容
    19. // \d+ 是正则表达式的特殊符合
    20. svr.Get(R"(/question/(\d+))",[&ctrl](const Request&req,Response &resp){
    21. std::string number = req.matches[1];
    22. std::string html;
    23. ctrl.Question(number,&html);
    24. resp.set_content(html,"text/html;charset=utf-8");
    25. });
    26. // 判断用户提交的代码(1.每道题c测试用例,2.compile_and_run)
    27. svr.Post(R"(/judge/(\d+))",[&ctrl](const Request&req,Response &resp){
    28. std::string number = req.matches[1];
    29. std::string result_json;
    30. ctrl.Judge(number,req.body,&result_json);
    31. resp.set_content(result_json,"application/json;charset=utf-8");
    32. });
    33. svr.set_base_dir("./wwwroot");
    34. svr.listen("0.0.0.0",8080);
    35. return 0;
    36. }

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

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

    1. void Judge(const std::string &number, const std::string in_json, std::string *out_json)
    2. {
    3. // LOG(DEBUG) << in_json << " \nnumber:" << number << "\n";
    4. // 0. 根据题目编号,直接拿到对应的题目细节
    5. struct Question q;
    6. model_.GetOneQuestion(number, &q);
    7. // 1. in_json进行反序列化,得到题目的id,得到用户提交源代码,input
    8. Json::Reader reader;
    9. Json::Value in_value;
    10. reader.parse(in_json, in_value);
    11. std::string code = in_value["code"].asString();
    12. // 2. 重新拼接用户代码+测试用例代码,形成新的代码
    13. Json::Value compile_value;
    14. compile_value["input"] = in_value["input"].asString();
    15. compile_value["code"] = code + "\n" + q.tail;
    16. compile_value["cpu_limit"] = q.cpu_limit;
    17. compile_value["mem_limit"] = q.mem_limit;
    18. Json::FastWriter writer;
    19. std::string compile_string = writer.write(compile_value);
    20. // 3. 选择负载最低的主机(差错处理)
    21. // 规则: 一直选择,直到主机可用,否则,就是全部挂掉
    22. while(true)
    23. {
    24. int id = 0;
    25. Machine *m = nullptr;
    26. if(!load_blance_.SmartChoice(&id, &m))
    27. {
    28. break;
    29. }
    30. // 4. 然后发起http请求,得到结果
    31. Client cli(m->ip, m->port);
    32. m->IncLoad();
    33. LOG(INFO) << " 选择主机成功, 主机id: " << id << " 详情: " << m->ip << ":" << m->port << " 当前主机的负载是: " << m->Load() << "\n";
    34. if(auto res = cli.Post("/compile_and_run", compile_string, "application/json;charset=utf-8"))
    35. {
    36. // 5. 将结果赋值给out_json
    37. if(res->status == 200)
    38. {
    39. *out_json = res->body;
    40. m->DecLoad();
    41. LOG(INFO) << "请求编译和运行服务成功..." << "\n";
    42. break;
    43. }
    44. m->DecLoad();
    45. }
    46. else
    47. {
    48. //请求失败
    49. LOG(ERROR) << " 当前请求的主机id: " << id << " 详情: " << m->ip << ":" << m->port << " 可能已经离线"<< "\n";
    50. load_blance_.OfflineMachine(id);
    51. load_blance_.ShowMachines(); //仅仅是为了用来调试
    52. }
    53. }

    2.8 测试oj_server服务

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

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

    2.9 一个BUG 

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

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

    3.1 index.html

    1. html>
    2. <html lang="en">
    3. <head>
    4. <meta charset="UTF-8">
    5. <meta http-equiv="X-UA-Compatible" content="IE=edge">
    6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
    7. <title>这是我的个人OJ系统title>
    8. <style>
    9. /* 起手式, 100%保证我们的样式设置可以不受默认影响 */
    10. * {
    11. /* 消除网页的默认外边距 */
    12. margin: 0px;
    13. /* 消除网页的默认内边距 */
    14. padding: 0px;
    15. }
    16. html,
    17. body {
    18. width: 100%;
    19. height: 100%;
    20. }
    21. .container .navbar {
    22. width: 100%;
    23. height: 50px;
    24. background-color: black;
    25. /* 给父级标签设置overflow,取消后续float带来的影响 */
    26. overflow: hidden;
    27. }
    28. .container .navbar a {
    29. /* 设置a标签是行内块元素,允许你设置宽度 */
    30. display: inline-block;
    31. /* 设置a标签的宽度,a标签默认行内元素,无法设置宽度 */
    32. width: 80px;
    33. /* 设置字体颜色 */
    34. color: white;
    35. /* 设置字体的大小 */
    36. font-size: large;
    37. /* 设置文字的高度和导航栏一样的高度 */
    38. line-height: 50px;
    39. /* 去掉a标签的下划线 */
    40. text-decoration: none;
    41. /* 设置a标签中的文字居中 */
    42. text-align: center;
    43. }
    44. /* 设置鼠标事件 */
    45. .container .navbar a:hover {
    46. background-color: green;
    47. }
    48. .container .navbar .login {
    49. float: right;
    50. }
    51. .container .content {
    52. /* 设置标签的宽度 */
    53. width: 800px;
    54. /* 用来调试 */
    55. /* background-color: #ccc; */
    56. /* 整体居中 */
    57. margin: 0px auto;
    58. /* 设置文字居中 */
    59. text-align: center;
    60. /* 设置上外边距 */
    61. margin-top: 200px;
    62. }
    63. .container .content .font_ {
    64. /* 设置标签为块级元素,独占一行,可以设置高度宽度等属性 */
    65. display: block;
    66. /* 设置每个文字的上外边距 */
    67. margin-top: 20px;
    68. /* 去掉a标签的下划线 */
    69. text-decoration: none;
    70. /* 设置字体大小
    71. font-size: larger; */
    72. }
    73. style>
    74. head>
    75. <body>
    76. <div class="container">
    77. <div class="navbar">
    78. <a href="/">首页a>
    79. <a href="/all_questions">题库a>
    80. <a href="#">竞赛a>
    81. <a href="#">讨论a>
    82. <a href="#">求职a>
    83. <a class="login" href="#">登录a>
    84. div>
    85. <div class="content">
    86. <h1 class="font_">欢迎来到我的OnlineJudge平台h1>
    87. <p class="font_">这个我个人独立开发的一个在线OJ平台p>
    88. <a class="font_" href="/all_questions">点击我开始编程啦!a>
    89. div>
    90. div>
    91. body>
    92. html>

     3.2 all_questions.html

    1. html>
    2. <html lang="en">
    3. <head>
    4. <meta charset="UTF-8">
    5. <meta http-equiv="X-UA-Compatible" content="IE=edge">
    6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
    7. <title>在线OJ-题目列表title>
    8. <style>
    9. /* 起手式, 100%保证我们的样式设置可以不受默认影响 */
    10. * {
    11. /* 消除网页的默认外边距 */
    12. margin: 0px;
    13. /* 消除网页的默认内边距 */
    14. padding: 0px;
    15. }
    16. html,
    17. body {
    18. width: 100%;
    19. height: 100%;
    20. }
    21. .container .navbar {
    22. width: 100%;
    23. height: 50px;
    24. background-color: black;
    25. /* 给父级标签设置overflow,取消后续float带来的影响 */
    26. overflow: hidden;
    27. }
    28. .container .navbar a {
    29. /* 设置a标签是行内块元素,允许你设置宽度 */
    30. display: inline-block;
    31. /* 设置a标签的宽度,a标签默认行内元素,无法设置宽度 */
    32. width: 80px;
    33. /* 设置字体颜色 */
    34. color: white;
    35. /* 设置字体的大小 */
    36. font-size: large;
    37. /* 设置文字的高度和导航栏一样的高度 */
    38. line-height: 50px;
    39. /* 去掉a标签的下划线 */
    40. text-decoration: none;
    41. /* 设置a标签中的文字居中 */
    42. text-align: center;
    43. }
    44. /* 设置鼠标事件 */
    45. .container .navbar a:hover {
    46. background-color: green;
    47. }
    48. .container .navbar .login {
    49. float: right;
    50. }
    51. .container .question_list {
    52. padding-top: 50px;
    53. width: 800px;
    54. height: 100%;
    55. margin: 0px auto;
    56. /* background-color: #ccc; */
    57. text-align: center;
    58. }
    59. .container .question_list table {
    60. width: 100%;
    61. font-size: large;
    62. font-family: 'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif;
    63. margin-top: 50px;
    64. background-color: rgb(243, 248, 246);
    65. }
    66. .container .question_list h1 {
    67. color: green;
    68. }
    69. .container .question_list table .item {
    70. width: 100px;
    71. height: 40px;
    72. font-size: large;
    73. font-family: 'Times New Roman', Times, serif;
    74. }
    75. .container .question_list table .item a {
    76. text-decoration: none;
    77. color: black;
    78. }
    79. .container .question_list table .item a:hover {
    80. color: blue;
    81. text-decoration: underline;
    82. }
    83. .container .footer {
    84. width: 100%;
    85. height: 50px;
    86. text-align: center;
    87. line-height: 50px;
    88. color: #ccc;
    89. margin-top: 15px;
    90. }
    91. style>
    92. head>
    93. <body>
    94. <div class="container">
    95. <div class="navbar">
    96. <a href="/">首页a>
    97. <a href="/all_questions">题库a>
    98. <a href="#">竞赛a>
    99. <a href="#">讨论a>
    100. <a href="#">求职a>
    101. <a class="login" href="#">登录a>
    102. div>
    103. <div class="question_list">
    104. <h1>OnlineJuge题目列表h1>
    105. <table>
    106. <tr>
    107. <th class="item">编号th>
    108. <th class="item">标题th>
    109. <th class="item">难度th>
    110. tr>
    111. {{#question_list}}
    112. <tr>
    113. <td class="item">{{number}}td>
    114. <td class="item"><a href="/question/{{number}}">{{title}}a>td>
    115. <td class="item">{{star}}td>
    116. tr>
    117. {{/question_list}}
    118. table>
    119. div>
    120. <div class="footer">
    121. <h4>@lych4>
    122. div>
    123. div>
    124. body>
    125. html>

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

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

    全部代码

    1. html>
    2. <html lang="en">
    3. <head>
    4. <meta charset="UTF-8">
    5. <meta http-equiv="X-UA-Compatible" content="IE=edge">
    6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
    7. <title>{{number}}.{{title}}title>
    8. <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.2.6/ace.js" type="text/javascript"
    9. charset="utf-8">script>
    10. <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.2.6/ext-language_tools.js" type="text/javascript"
    11. charset="utf-8">script>
    12. <script src="http://code.jquery.com/jquery-2.1.1.min.js">script>
    13. <style>
    14. * {
    15. margin: 0;
    16. padding: 0;
    17. }
    18. html,
    19. body {
    20. width: 100%;
    21. height: 100%;
    22. }
    23. .container .navbar {
    24. width: 100%;
    25. height: 50px;
    26. background-color: black;
    27. /* 给父级标签设置overflow,取消后续float带来的影响 */
    28. overflow: hidden;
    29. }
    30. .container .navbar a {
    31. /* 设置a标签是行内块元素,允许你设置宽度 */
    32. display: inline-block;
    33. /* 设置a标签的宽度,a标签默认行内元素,无法设置宽度 */
    34. width: 80px;
    35. /* 设置字体颜色 */
    36. color: white;
    37. /* 设置字体的大小 */
    38. font-size: large;
    39. /* 设置文字的高度和导航栏一样的高度 */
    40. line-height: 50px;
    41. /* 去掉a标签的下划线 */
    42. text-decoration: none;
    43. /* 设置a标签中的文字居中 */
    44. text-align: center;
    45. }
    46. /* 设置鼠标事件 */
    47. .container .navbar a:hover {
    48. background-color: green;
    49. }
    50. .container .navbar .login {
    51. float: right;
    52. }
    53. .container .part1 {
    54. width: 100%;
    55. height: 600px;
    56. overflow: hidden;
    57. }
    58. .container .part1 .left_desc {
    59. width: 50%;
    60. height: 600px;
    61. float: left;
    62. overflow: scroll;
    63. }
    64. .container .part1 .left_desc h3 {
    65. padding-top: 10px;
    66. padding-left: 10px;
    67. }
    68. .container .part1 .left_desc pre {
    69. padding-top: 10px;
    70. padding-left: 10px;
    71. font-size: medium;
    72. font-family:'Gill Sans', 'Gill Sans MT', Calibri, 'Trebuchet MS', sans-serif;
    73. }
    74. .container .part1 .right_code {
    75. width: 50%;
    76. float: right;
    77. }
    78. .container .part1 .right_code .ace_editor {
    79. height: 600px;
    80. }
    81. .container .part2 {
    82. width: 100%;
    83. overflow: hidden;
    84. }
    85. .container .part2 .result {
    86. width: 300px;
    87. float: left;
    88. }
    89. .container .part2 .btn-submit {
    90. width: 120px;
    91. height: 50px;
    92. font-size: large;
    93. float: right;
    94. background-color: #26bb9c;
    95. color: #FFF;
    96. /* 给按钮带上圆角 */
    97. /* border-radius: 1ch; */
    98. border: 0px;
    99. margin-top: 10px;
    100. margin-right: 10px;
    101. }
    102. .container .part2 button:hover {
    103. color:green;
    104. }
    105. .container .part2 .result {
    106. margin-top: 15px;
    107. margin-left: 15px;
    108. }
    109. .container .part2 .result pre {
    110. font-size: large;
    111. }
    112. style>
    113. head>
    114. <body>
    115. <div class="container">
    116. <div class="navbar">
    117. <a href="/">首页a>
    118. <a href="/all_questions">题库a>
    119. <a href="#">竞赛a>
    120. <a href="#">讨论a>
    121. <a href="#">求职a>
    122. <a class="login" href="#">登录a>
    123. div>
    124. <div class="part1">
    125. <div class="left_desc">
    126. <h3><span id="number">{{number}}span>.{{title}}_{{star}}h3>
    127. <pre>{{desc}}pre>
    128. div>
    129. <div class="right_code">
    130. <pre id="code" class="ace_editor"><textarea class="ace_text-input">{{pre_code}}textarea>pre>
    131. div>
    132. div>
    133. <div class="part2">
    134. <div class="result">div>
    135. <button class="btn-submit" onclick="submit()">提交代码button>
    136. div>
    137. div>
    138. <script>
    139. //初始化对象
    140. editor = ace.edit("code");
    141. //设置风格和语言(更多风格和语言,请到github上相应目录查看)
    142. // 主题大全:http://www.manongjc.com/detail/25-cfpdrwkkivkikmk.html
    143. editor.setTheme("ace/theme/monokai");
    144. editor.session.setMode("ace/mode/c_cpp");
    145. // 字体大小
    146. editor.setFontSize(16);
    147. // 设置默认制表符的大小:
    148. editor.getSession().setTabSize(4);
    149. // 设置只读(true时只读,用于展示代码)
    150. editor.setReadOnly(false);
    151. // 启用提示菜单
    152. ace.require("ace/ext/language_tools");
    153. editor.setOptions({
    154. enableBasicAutocompletion: true,
    155. enableSnippets: true,
    156. enableLiveAutocompletion: true
    157. });
    158. function submit(){
    159. // alert("嘿嘿!");
    160. // 1. 收集当前页面的有关数据, 1. 题号 2.代码
    161. var code = editor.getSession().getValue();
    162. // console.log(code);
    163. var number = $(".container .part1 .left_desc h3 #number").text();
    164. // console.log(number);
    165. var judge_url = "/judge/" + number;
    166. // console.log(judge_url);
    167. // 2. 构建json,并通过ajax向后台发起基于http的json请求
    168. $.ajax({
    169. method: 'Post', // 向后端发起请求的方式
    170. url: judge_url, // 向后端指定的url发起请求
    171. dataType: 'json', // 告知server,我需要什么格式
    172. contentType: 'application/json;charset=utf-8', // 告知server,我给你的是什么格式
    173. data: JSON.stringify({
    174. 'code':code,
    175. 'input': ''
    176. }),
    177. success: function(data){
    178. //成功得到结果
    179. // console.log(data);
    180. show_result(data);
    181. }
    182. });
    183. // 3. 得到结果,解析并显示到 result中
    184. function show_result(data)
    185. {
    186. // console.log(data.status);
    187. // console.log(data.reason);
    188. // 拿到result结果标签
    189. var result_div = $(".container .part2 .result");
    190. // 清空上一次的运行结果
    191. result_div.empty();
    192. // 首先拿到结果的状态码和原因结果
    193. var _status = data.status;
    194. var _reason = data.reason;
    195. var reason_lable = $( "

      ",{

    196. text: _reason
    197. });
    198. reason_lable.appendTo(result_div);
    199. if(status == 0){
    200. // 请求是成功的,编译运行过程没出问题,但是结果是否通过看测试用例的结果
    201. var _stdout = data.stdout;
    202. var _stderr = data.stderr;
    203. var stdout_lable = $("
      ", {
    204. text: _stdout
    205. });
    206. var stderr_lable = $("
      ", {
    207. text: _stderr
    208. })
    209. stdout_lable.appendTo(result_div);
    210. stderr_lable.appendTo(result_div);
    211. }
    212. else{
    213. // 编译运行出错,do nothing
    214. }
    215. }
    216. }
    217. script>
    218. body>
    219. 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中

    1. use oj;
    2. drop table if exists oj_table;
    3. create table if not exists oj_table(
    4. _number varchar(200) comment '题目编号',
    5. _titie varchar(200) comment '题目标题',
    6. _start varchar(200) comment '题目简单中等困难',
    7. _desc varchar(2000) comment '题目描述',
    8. _header varchar(2000) comment '题目预设',
    9. _tail varchar(2000) comment '题目测试用例',
    10. _cpu_limit int comment '时间要求',
    11. _mem_limt int comment '空间要求'
    12. );
    13. insert into oj_table values(
    14. 1,
    15. '判断回文数',
    16. '简单',
    17. '判断一个整数是否是回文数。回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。
    18. 示例 1:
    19. 输入: 121
    20. 输出: true
    21. 示例 2:
    22. 输入: -121
    23. 输出: false
    24. 解释: 从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。
    25. 示例 3:
    26. 输入: 10
    27. 输出: false
    28. 解释: 从右向左读, 为 01 。因此它不是一个回文数。
    29. 进阶:
    30. 你能不将整数转为字符串来解决这个问题吗?',
    31. '#include
    32. #include
    33. #include
    34. #include
    35. #include
    36. using namespace std;
    37. class Solution{
    38. public:
    39. bool isPalindrome(int x)
    40. {
    41. //将你的代码写在下面
    42. return true;
    43. }
    44. };',
    45. '#ifndef COMPILER_ONLINE
    46. #include "header.cpp"
    47. #endif
    48. void Test1()
    49. {
    50. // 通过定义临时对象,来完成方法的调用
    51. bool ret = Solution().isPalindrome(121);
    52. if(ret){
    53. std::cout << "通过用例1, 测试121通过 ... OK!" << std::endl;
    54. }
    55. else{
    56. std::cout << "没有通过用例1, 测试的值是: 121" << std::endl;
    57. }
    58. }
    59. void Test2()
    60. {
    61. // 通过定义临时对象,来完成方法的调用
    62. bool ret = Solution().isPalindrome(-10);
    63. if(!ret){
    64. std::cout << "通过用例2, 测试-10通过 ... OK!" << std::endl;
    65. }
    66. else{
    67. std::cout << "没有通过用例2, 测试的值是: -10" << std::endl;
    68. }
    69. }
    70. int main()
    71. {
    72. Test1();
    73. Test2();
    74. return 0;
    75. }',
    76. 1,
    77. 30000
    78. );
    79. 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就需要重新设计

    1. #pragma once
    2. // 文件版本
    3. #include "../comm/util.hpp"
    4. #include "../comm/log.hpp"
    5. #include
    6. #include
    7. #include
    8. #include "./include/mysql.h"
    9. namespace ns_model
    10. {
    11. using namespace std;
    12. using namespace ns_log;
    13. using namespace ns_util;
    14. struct Question{
    15. string number;// 题目编号,唯一
    16. string title;// 题目标题
    17. string star;// 难度: 简单 中等 困难
    18. int cpu_limit;// 题目的时间复杂度(S)
    19. int mem_limit;// 题目的空间复杂度(KB)
    20. string desc;// 题目描述
    21. string header; // 题目预设给用户在线编辑器的代码
    22. string tail;// 题目测试用例,需要和header拼接
    23. };
    24. const std::string oj_questions = "oj_table";
    25. const std::string host = "127.0.0.1";
    26. const std::string user = "oj_client";
    27. const std::string passwd = "123456";
    28. const std::string db = "oj";
    29. const int port = 3306;
    30. class Model
    31. {
    32. public:
    33. Model(){
    34. }
    35. ~Model(){
    36. ;
    37. }
    38. bool QueryMysql(const std::string &sql,vector*out){
    39. // 这里的out是输出型参数
    40. // 创建mysql句柄
    41. MYSQL *my = mysql_init(nullptr);
    42. // 连接数据库
    43. if(nullptr == mysql_real_connect(my,host.c_str(),user.c_str(),passwd.c_str(),db.c_str(),port,nullptr,0)){
    44. LOG(FATAL) << "连接数据库失败!" << "\n";
    45. return false;
    46. }
    47. // 一定要设置该链接的编码格式,要不然会出现乱码的问题
    48. mysql_set_character_set(my,"utf8");
    49. LOG(INFO) << "连接数据库成功!" << "\n";
    50. // 执行sql语句
    51. if(0 != mysql_query(my,sql.c_str())){
    52. LOG(WARNING) << sql << " execute error! " << "\n";
    53. return false;
    54. }
    55. // 提取结果
    56. MYSQL_RES *res = mysql_store_result(my);// 本质就是一个2级指针
    57. // 分析结果
    58. int rows = mysql_num_rows(res);// 获取行的数量
    59. int cols = mysql_num_fields(res);// 获取列的数量
    60. Question q;
    61. for(int i = 0;i < rows;i++){
    62. MYSQL_ROW row = mysql_fetch_row(res);
    63. q.number = row[0];
    64. q.title = row[1];
    65. q.star = row[2];
    66. q.desc = row[3];
    67. q.header = row[4];
    68. q.tail = row[5];
    69. q.cpu_limit = atoi(row[6]);
    70. q.mem_limit = atoi(row[7]);
    71. out->push_back(q);
    72. }
    73. // 释放控件
    74. free(res);
    75. // 关闭mysql连接
    76. mysql_close(my);
    77. return true;
    78. }
    79. // 获取所有题目,这里的out是输出型参数
    80. bool GetAllQuestions(vector*out){
    81. std::string sql = "select * from ";
    82. sql += oj_questions;
    83. return QueryMysql(sql,out);
    84. }
    85. // 获取指定题目,这里的q是输出型参数
    86. bool GetOneQuestion(const string& number,Question* q){
    87. bool res = false;
    88. std::string sql = "select * from ";
    89. sql += oj_questions;
    90. sql += " where number=";
    91. sql += number;
    92. vector result;
    93. if(QueryMysql(sql,&result)){
    94. if(result.size() == 1){
    95. *q = result[0];
    96. res = true;
    97. }
    98. }
    99. return res;
    100. }
    101. private:
    102. // 题号 : 题目细节
    103. unordered_map questions;
    104. };
    105. }
    • 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

  • 相关阅读:
    解决Github打开后排版紊乱问题
    SpringCloudAlibaba实战-nacos集群部署
    SpringBoot学习之Redis下载安装启动【Mac版本】(三十七)
    STM32学习笔记:IWDG_独立看门狗
    QT笔记——vs + qt 创建一个带界面的 dll 和 调用带界面的dll
    【MySQL】(七)SQL约束——主键约束、非空约束、唯一约束、默认值约束、外键约束
    Mac终端常用命令
    软文推广中媒体矩阵的优势在哪儿
    带网络变压器的RJ45网口连接器/集成RJ45网口连接器
    Mysql之部分表主从搭建及新增表
  • 原文地址:https://blog.csdn.net/LYC_462857656/article/details/139312642