• 基于负载均衡的在线OJ实战项目


    前言:

    该篇讲述了实现基于负载均衡式的在线oj,即类似在线编程做题网站一样,文章尽可能详细讲述细节即实现,便于大家了解学习。

    文章将采用单篇不分段形式(ps:切着麻烦),附图文,附代码,代码部署在云服务器

    技术栈

    • C++ STL标准库

    • Boost 标准库

    • cpp-httpib 开源库

    • ctemplate 第三方开源前端网页渲染库

    • jsoncpp 第三方开源序列化、反序列化库

    • 负载均衡的设计

    • 多进程、多线程

    • MYSQL C connect

    • Ace前端在线编辑器

    • html/cdd/js/jquery/ajax

    开发环境

    • vscode
    • mysql workbench
    • Centos 7云服务器

    宏观结构

    • comm:公共模块
    • compile_sever:编译运行模块
    • oj_server:获取题目,负载均衡等

    376d0cae8c104c84b622c10cc9de0693.png

     

     


    项目演示: 


    https://blog.csdn.net/Obto_/article/details/132558642?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22132558642%22%2C%22source%22%3A%22Obto_%22%7Dhttps://blog.csdn.net/Obto_/article/details/132558642?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22132558642%22%2C%22source%22%3A%22Obto_%22%7D


     



    项目设计 -- 编译服务

    工具类的准备:

    供程序中各个部分调用的方法类:

    1. #pragma once
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. #include
    11. #include
    12. namespace ns_util
    13. {
    14. class TimeUtil
    15. {
    16. public:
    17. static std::string GetTimeStamp()
    18. {
    19. // 获取时间戳 gettimeofday
    20. struct timeval _time;
    21. gettimeofday(&_time, nullptr);
    22. return std::to_string(_time.tv_sec);
    23. }
    24. // 获得毫秒时间戳
    25. static std::string GetTimeMs()
    26. {
    27. struct timeval _time;
    28. gettimeofday(&_time, nullptr);
    29. return std::to_string(_time.tv_sec * 1000 + _time.tv_usec / 1000);
    30. }
    31. };
    32. const std::string temp_path = "./temp/";
    33. class PathUtil
    34. {
    35. public:
    36. static std::string AddSuffix(const std::string &file_name, const std::string &suffix)
    37. {
    38. std::string path_name = temp_path;
    39. path_name += file_name;
    40. path_name += suffix;
    41. return path_name;
    42. }
    43. // 构建源文件路径+后缀的完整文件名
    44. // 1234 -> ./temp/1234.cpp
    45. static std::string Src(const std::string &file_name)
    46. {
    47. return AddSuffix(file_name, ".cpp");
    48. }
    49. // 构建可执行程序的完整路径 + 后缀名
    50. static std::string Exe(const std::string &file_name)
    51. {
    52. return AddSuffix(file_name, ".exe");
    53. }
    54. static std::string CompilerError(const std::string &file_name)
    55. {
    56. return AddSuffix(file_name, ".compile_stderr");
    57. }
    58. //-------------------------------------------------------------------
    59. // 构建该程序对应的标准错误完整的路径+后缀名
    60. static std::string Stderr(const std::string &file_name)
    61. {
    62. return AddSuffix(file_name, ".stderr");
    63. }
    64. static std::string Stdin(const std::string &file_name)
    65. {
    66. return AddSuffix(file_name, ".stdin");
    67. }
    68. static std::string Stdout(const std::string &file_name)
    69. {
    70. return AddSuffix(file_name, ".stdout");
    71. }
    72. };
    73. class FileUtil
    74. {
    75. public:
    76. static bool IsFileExists(const std::string &path_name)
    77. {
    78. struct stat st;
    79. if (stat(path_name.c_str(), &st) == 0)
    80. {
    81. // 获取属性成功,文件已经存在
    82. return true;
    83. }
    84. return false;
    85. }
    86. static std::string UniqFileName()
    87. {
    88. static std::atomic_uint id(0);
    89. id++;
    90. // 毫秒时间戳
    91. std::string ms = TimeUtil::GetTimeMs();
    92. std::string uniq_id = std::to_string(id);
    93. return ms + "_" + uniq_id;
    94. }
    95. static bool WriteFile(const std::string &target, const std::string &content)
    96. {
    97. // waiting
    98. std::ofstream out(target);
    99. if (!out.is_open())
    100. {
    101. return false;
    102. }
    103. out.write(content.c_str(), content.size());
    104. out.close();
    105. return true;
    106. }
    107. static bool ReadFile(const std::string &target, std::string *content, bool keep = false)
    108. {
    109. (*content).clear();
    110. std::ifstream in(target);
    111. if (!in.is_open())
    112. {
    113. return false;
    114. }
    115. std::string line;
    116. // getline是按行保存的,不保存行分隔符,自动去掉\n
    117. // 但是有些时候,需要保留行分隔符
    118. // getline内部重载了强制类型转化
    119. while (std::getline(in, line))
    120. {
    121. (*content) += line;
    122. (*content) += (keep ? "\n" : "");
    123. }
    124. in.close();
    125. return true;
    126. }
    127. };
    128. class StringUtil
    129. {
    130. public:
    131. /*
    132. str:输入型,目标要切分的字符串
    133. target:输出型,保存切分完毕的结果
    134. sep:指定的分隔符
    135. */
    136. static void SplitString(const std::string &str,std::vector *target,std::string sep)
    137. {
    138. boost::split(*target,str,boost::is_any_of(sep),boost::algorithm::token_compress_on);
    139. //boost split
    140. }
    141. };
    142. }
    • PathUtil:路径工具
      • 形成exe完整路径
      • 形成cpp完整路径
      • 形成compile_stderr完整路径
      • 形成stderr完整路径
      • 形成stdin完整路径
      • 完整路径指的是当前代码在本地上的保存路径:即输入 1234 要形成 ./temp/1234.cpp等这里的相对路径形成依靠PathUtil工具
    • TimeUtil:时间工具
      • 获取时间戳 get time of day
      • 获得好面时间戳,用于形成文件唯一标识(名字)
    • FileUtil:文件工具
      • IsFileExits:判断某文件是否存在
      • UniqFileName:形成文件唯一名字
      • WriteFile:向指定文件写入指定字符串
      • ReadFile:读取某文件的内容
    • StringUtil:字符串工具
      • 使用boost库中的切分字符串Split()

     

    compiler编译服务设计 :

    目的:能够编译并运行代码,得到格式化的相关结果

    bcc25d727a6e4f8f9fbb337b5612453d.png

     

    1. #pragma once
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include"../comm/util.hpp"
    9. #include"../comm/log.hpp"
    10. //只负责进行代码的编译
    11. namespace ns_compiler{
    12. //引入路径拼接功能
    13. using namespace ns_util;
    14. using namespace ns_log;
    15. class Compiler{
    16. public:
    17. Compiler()
    18. {}
    19. ~Compiler()
    20. {}
    21. //返回值是编译成功TRUE;else FALSE
    22. //输入参数是编译的文件名
    23. //file_name : 1234
    24. //1234 -> ./temp/1234.cpp
    25. //1234 -> ./temp/1234.exe
    26. //1234 -> ./temp/1234.stderr
    27. static bool Compile(const std::string &file_name)
    28. {
    29. pid_t pid = fork();
    30. if(pid < 0)
    31. {
    32. LOG(ERROR) << "内部错误,创建子进程失败"<<"\n";
    33. return false;
    34. }
    35. else if(pid == 0)//子进程
    36. {
    37. umask(0);
    38. int _stderr = open(PathUtil::CompilerError(file_name).c_str(),O_CREAT | O_WRONLY,0644);
    39. if(_stderr < 0){
    40. LOG(WARNING)<<"没有成功行成stderr文件"<<"\n";
    41. exit(1);
    42. }
    43. //重定向标准错误到_stderr
    44. dup2(_stderr,2);
    45. //程序替婚,并不影响进程的文件描述符表
    46. //子进程:调用编译器
    47. execlp("g++","g++","-o",PathUtil::Exe(file_name).c_str(),PathUtil::Src(file_name).c_str(),"-std=c++11","-D","COMPILER_ONLINE",nullptr);
    48. LOG(ERROR) <<"启动编译器g++失败,可能是参数错误"<<"\n";
    49. exit(2);
    50. }
    51. else//父进程
    52. {
    53. waitpid(pid,nullptr,0);
    54. //编译是否成功,就看有没有形成对应的可执行程序
    55. if(FileUtil::IsFileExists(PathUtil::Exe(file_name).c_str())){
    56. LOG(INFO) <Src(file_name)<<"编译成功!"<<"\n";
    57. return true;
    58. }
    59. }
    60. LOG(ERROR) <<"编译失败,没有形成可执行程序,return false"<<"\n";
    61. return false;
    62. }
    63. };
    64. };

    compiler编译服务只管编译传过来的代码,其他一律不管,它只关心程序是否能够编译过

    LOG日志的添加:

    1. #pragma once
    2. #include
    3. #include
    4. #include"util.hpp"
    5. namespace ns_log
    6. {
    7. using namespace ns_util;
    8. //日志等级
    9. enum{
    10. INFO,
    11. DEBUG,
    12. WARNING,
    13. ERROR ,
    14. FATAL
    15. };
    16. //LOG() << "message"
    17. inline std::ostream &Log(const std::string &level, const std::string &file_name,int line)
    18. {
    19. //添加日志等级
    20. std::string message = "[";
    21. message+=level;
    22. message+="]";
    23. //添加报错文件名称
    24. message+="[";
    25. message+=file_name;
    26. message+="]";
    27. //添加报错行
    28. message+="[";
    29. message+=std::to_string(line);
    30. message+="]";
    31. //添加日志时间戳
    32. message += "[";
    33. message += TimeUtil::GetTimeStamp();
    34. message += "]";
    35. //cout 本质内部是包含缓冲区的
    36. std::cout<//不要endl刷新
    37. return std::cout;
    38. }
    39. //LOG(INFo) << "message"
    40. //开放式日志
    41. #define LOG(level) Log(#level,__FILE__,__LINE__)
    42. }

    runner运行功能设计:

    1. #pragma once
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. #include
    11. #include
    12. #include
    13. #include
    14. #include "../comm/log.hpp"
    15. #include "../comm/util.hpp"
    16. namespace ns_runner
    17. {
    18. using namespace ns_log;
    19. using namespace ns_util;
    20. class Runner
    21. {
    22. public:
    23. Runner() {}
    24. ~Runner() {}
    25. public:
    26. //提供设置进程占用资源大小的接口
    27. static void SerProcLimit(int _cpu_limit,int _mem_limit)
    28. {
    29. //设置CPU时长
    30. struct rlimit cpu_rlimit;
    31. cpu_rlimit.rlim_max = RLIM_INFINITY;
    32. cpu_rlimit.rlim_cur = _cpu_limit;
    33. setrlimit(RLIMIT_CPU,&cpu_rlimit);
    34. //设置内存大小
    35. struct rlimit mem_rlimit;
    36. mem_rlimit.rlim_max = RLIM_INFINITY;
    37. mem_rlimit.rlim_cur = _mem_limit * 1024;//转化成kb
    38. setrlimit(RLIMIT_AS,&mem_rlimit);
    39. }
    40. // 指明文件名即可,不需要带路径和后缀
    41. /*
    42. 返回值如果是大于 0 :程序异常了,退出时收到了信号,返回值就是对应的信号
    43. 返回值 == 0 就是正常运行完毕,结果是什么保存到了临时文件中,我不清楚
    44. 返回值 < 0 属于内部错误
    45. cpu_limit:该程序运行的时候,可以使用的最大cpu的资源上限
    46. mem_limit:该程序运行的时候,可以使用的最大内存大小KB
    47. */
    48. static int Run(const std::string &file_name,int cpu_limit,int mem_limit)
    49. {
    50. /*程序运行:
    51. 1.代码跑完结果争取
    52. 2.代码跑完结果不正确
    53. 3.代码没跑完,异常了
    54. run不需要考虑运行完后正确与否,只管跑
    55. 首先我们必须知道可执行程序是谁?
    56. 标准输入:不处理
    57. 标准输入:程序运行完成,输出结果是什么
    58. 标准错误:运行时错误信息
    59. */
    60. std::string _execute = PathUtil::Exe(file_name);
    61. std::string _stdin = PathUtil::Stdin(file_name);
    62. std::string _stdout = PathUtil::Stdout(file_name);
    63. std::string _stderr = PathUtil::Stderr(file_name);
    64. umask(0);
    65. int _stdin_fd = open(_stdin.c_str(), O_CREAT | O_RDONLY, 0644);
    66. int _stdout_fd = open(_stdout.c_str(), O_CREAT | O_WRONLY, 0644);
    67. int _stderr_fd = open(_stderr.c_str(), O_CREAT | O_WRONLY, 0644);
    68. if (_stdin_fd < 0 || _stdout_fd < 0 || _stderr_fd < 0)
    69. {
    70. LOG(ERROR)<<"运行时打开标准文件失败"<<"\n";
    71. return -1; // 代表打开文件失败
    72. }
    73. pid_t pid = fork();
    74. if (pid < 0)
    75. {
    76. LOG(ERROR)<<"运行时创建子进程失败"<<"\n";
    77. close(_stdin_fd);
    78. close(_stdout_fd);
    79. close(_stderr_fd);
    80. return -2; //代表创建子进程失败
    81. }
    82. else if (pid == 0)
    83. {
    84. dup2(_stdin_fd,0);
    85. dup2(_stdout_fd,1);
    86. dup2(_stderr_fd,2);
    87. SerProcLimit(cpu_limit,mem_limit);
    88. execl(_execute.c_str()/*我要执行谁*/,_execute.c_str()/*我想在命令航商如何执行*/,nullptr);
    89. exit(1);
    90. }
    91. else
    92. {
    93. int status = 0;
    94. waitpid(pid,&status,0);
    95. //程序运行异常,一定是因为收到了信号
    96. LOG(INFO)<<"运行完毕,info:"<<(status & 0x7F)<<"\n";
    97. close(_stdin_fd);
    98. close(_stdout_fd);
    99. close(_stderr_fd);
    100. return status&0x7F;
    101. }
    102. }
    103. };
    104. }

    compile_run:编译并运行功能:

    1. #pragma once
    2. #include "compiler.hpp"
    3. #include
    4. #include "runner.hpp"
    5. #include "../comm/log.hpp"
    6. #include "../comm/util.hpp"
    7. #include
    8. #include
    9. namespace ns_compile_and_run
    10. {
    11. using namespace ns_log;
    12. using namespace ns_util;
    13. using namespace ns_compiler;
    14. using namespace ns_runner;
    15. class CompileAndRun
    16. {
    17. public:
    18. static void RemoveTempFile(const std::string& file_name)
    19. {
    20. //清理文件的个数是不确定的,但是有哪些我们是知道的
    21. std::string _src = PathUtil::Src(file_name);
    22. if(FileUtil::IsFileExists(_src))unlink(_src.c_str());
    23. std::string _compiler_error = PathUtil::CompilerError(file_name);
    24. if(FileUtil::IsFileExists(_compiler_error))unlink(_compiler_error.c_str());
    25. std::string _execute = PathUtil::Exe(file_name);
    26. if(FileUtil::IsFileExists(_execute)) unlink(_execute.c_str());
    27. std::string _stdin = PathUtil::Stdin(file_name);
    28. if(FileUtil::IsFileExists(_stdin)) unlink(_stdin.c_str());
    29. std::string _stdout = PathUtil::Stdout(file_name);
    30. if(FileUtil::IsFileExists(_stdout)) unlink(_stdout.c_str());
    31. std::string _stderr = PathUtil::Stderr(file_name);
    32. if(FileUtil::IsFileExists(_stderr)) unlink(_stderr.c_str());
    33. }
    34. static std::string CodeToDesc(int code, std::string file_name) // code >0 <0 ==0
    35. {
    36. std::string desc;
    37. switch (code)
    38. {
    39. case 0:
    40. desc = "编译运行成功";
    41. break;
    42. case -1:
    43. desc = "用户提交的代码是空";
    44. break;
    45. case -2:
    46. desc = "未知错误";
    47. break;
    48. case -3:
    49. // desc = "编译发生报错";
    50. FileUtil::ReadFile(PathUtil::Stderr(file_name), &desc, true);
    51. break;
    52. case -4:
    53. break;
    54. case SIGABRT:
    55. desc = "内存超过范围";
    56. break;
    57. case SIGXCPU:
    58. desc = "CPU信号超时";
    59. break;
    60. case SIGFPE:
    61. desc = "除零错误,浮点数溢出";
    62. break;
    63. default:
    64. desc = "未知:" + std::to_string(code);
    65. break;
    66. }
    67. return desc;
    68. }
    69. /*
    70. 输入:
    71. code:用户提交的代码
    72. input:用户自己提交的代码,对应的输入-》不做处理
    73. cpu_limit:时间要求
    74. mem_limit:空间要求
    75. 输出:
    76. 必填:
    77. status:状态码
    78. reason:请求结果
    79. 选填:
    80. stdout:我的的程序运行完的结果
    81. stderr:我的程序运行完的错误结构
    82. 参数:
    83. in_json:{"code":"#include..."."input":"","cpu_limit":1,"mem_limit":10240}
    84. out_json:{"status":"0","reason":"","stdout":"","stderr":""};
    85. */
    86. static void Start(const std::string &in_json, std::string *out_json)
    87. {
    88. LOG(INFO)<<"启动compile_and_run"<<"\n";
    89. Json::Value in_value;
    90. Json::Reader reader;
    91. reader.parse(in_json, in_value); // 最后再处理差错问题
    92. std::string code = in_value["code"].asString();
    93. std::string input = in_value["input"].asString();
    94. int cpu_limit = in_value["cpu_limit"].asInt();
    95. int men_limit = in_value["mem_limit"].asInt();
    96. int status_code = 0;
    97. Json::Value out_value;
    98. int run_result = 0;
    99. std::string file_name; // 需要内部形成的唯一文件名
    100. if (code.size() == 0)
    101. {
    102. // 说明用户一行代码都没提交
    103. status_code = -1;
    104. goto END;
    105. }
    106. // 形成的文件名只具有唯一性,没有目录没有后缀
    107. // 毫秒计时间戳+原子性递增的唯一值:来保证唯一性
    108. file_name = FileUtil::UniqFileName(); // 形成唯一文件名字
    109. LOG(DEBUG)<<"调用UniqFileName()形成唯一名字"<"\n";
    110. run_result = Runner::Run(file_name, cpu_limit, men_limit);
    111. if (!FileUtil::WriteFile(PathUtil::Src(file_name), code)) // 形成临时src文件.cpp
    112. {
    113. status_code = -2; // 未知错误
    114. goto END;
    115. }
    116. if (!Compiler::Compile(file_name))
    117. {
    118. // 编译失败
    119. status_code = -3;
    120. goto END;
    121. }
    122. run_result = Runner::Run(file_name, cpu_limit, men_limit);
    123. if (run_result < 0)
    124. {
    125. // 服务器的内部错误,包括不限于文件打开失败,创建子进程失败等待
    126. status_code = -2; // 未知错误
    127. goto END;
    128. }
    129. else if (run_result > 0)
    130. {
    131. status_code = run_result;
    132. }
    133. else
    134. {
    135. // 运行成功
    136. status_code = 0;
    137. }
    138. END:
    139. std::cout<<"到达end语句"<
    140. // status_code
    141. out_value["status"] = status_code;
    142. out_value["reason"] = CodeToDesc(status_code, file_name);
    143. LOG(DEBUG)<<CodeToDesc(status_code, file_name);
    144. if (status_code == 0)
    145. {
    146. // 整个过程全部成功 , 这时候才需要运行结果
    147. std::string _stdout;
    148. FileUtil::ReadFile(PathUtil::Stdout(file_name), &_stdout, true);
    149. out_value["stdout"] = _stdout;
    150. }
    151. else
    152. {
    153. std::string _stderr;
    154. FileUtil::ReadFile(PathUtil::CompilerError(file_name), &_stderr, true);
    155. out_value["stderr"] = _stderr;
    156. }
    157. // 序列化
    158. Json::StyledWriter writer;
    159. *out_json = writer.write(out_value);
    160. //清理所有的临时文件
    161. RemoveTempFile(file_name);
    162. }
    163. };
    164. }

    compile_run:它的功能是接收远端传进来的json包,并反序列化得到其中的代码与输入,并调用compile进行编译

    • 编译成功:调用runner将代码运行起来->将执行结果分别保存到.exe、.stdin、.stdout 、.stderr、.compile_stderr文件中
    • 编译失败:不调用runner

    最后按对应构造json 返回给上级调用,即write进out_json中,收尾清除创建的文件

     

    compile_server .cc文件编写:

     

    1. #include"compile_run.hpp"
    2. #include
    3. #include"../comm/httplib.h"
    4. using namespace ns_compile_and_run;
    5. using namespace httplib;
    6. //编译服务随时可能被多个人请求,必须保证传递上来的code,形成源文件名称的时候,要具有唯一性,不然影响多个用户
    7. void Usage(std::string proc)
    8. {
    9. std::cerr <<"Usage:"<<"\n\t"<"port"<
    10. }
    11. // ./compiler_server port
    12. int main(int argc,char *argv[])
    13. {
    14. if(argc!=2){
    15. Usage(argv[0]);
    16. }
    17. Server svr;
    18. svr.Get("/hello",[](const Request &req,Response &resp)
    19. {
    20. resp.set_content("hello httplib,你好httplib","content_type: text/plain");
    21. });
    22. //svr.set_base_dir("./wwwroot");
    23. svr.Post("/compile_and_run",[](const Request &req,Response &resp){
    24. //请求服务正文是我们想要的json串
    25. LOG(DEBUG)<<"调用compile_and_run"<<"\n";
    26. std::string out_json;
    27. std::string in_json = req.body;
    28. if(!in_json.empty()){
    29. LOG(DEBUG)<<"当前的in_json"<"\n";
    30. CompileAndRun::Start(in_json,&out_json);
    31. resp.set_content(out_json,"application/json;charset=utf-8");
    32. }
    33. });
    34. svr.listen("0.0.0.0",atoi(argv[1]));//启动http服务了
    35. // std::string code = "code";
    36. // Compiler::Compile(code);
    37. // Runner::Run(code);
    38. //0-----------------------测试代码-------------------
    39. //下面的工作,充当客户端请求的json串
    40. // std::string in_json;
    41. // Json::Value in_value;
    42. // //R"()" raw string 凡事在这个圆括号里面的东西,就是字符串哪怕有一些特殊的字符串
    43. // in_value["code"] =R"(#include
    44. // int main(){
    45. // std::cout<<"测试成功"<
    46. // int a = 10;
    47. // a /= 0;
    48. // return 0;
    49. // })";
    50. // in_value["input"] ="";
    51. // in_value["cpu_limit"] = 1;
    52. // in_value["mem_limit"] = 10240 * 3;
    53. // Json::FastWriter writer;
    54. // std::cout<
    55. // in_json = writer.write(in_value);
    56. // //这个是将来给客户端返回的字符串
    57. // std::string out_json;
    58. // CompileAndRun::Start(in_json,&out_json);
    59. // std::cout<
    60. //0-----------------------------------------------------
    61. //提供的编译服务,打包新城一个网络服务
    62. //这次直接用第三方库,cpp-httplib
    63. return 0;
    64. }

    直接引入的httplib库, 设置好ip和端口就可以直接监听了

    • svr.get() :就是当对该服务器发起/hello 请求的时候,我就会接受到该请求,以Get的方式返回resp

    makefile:

    由于当前使用的c++11的新特性,引入了json库,和多线程

    1. compile_server:compile_server.cc
    2. g++ -o $@ $^ -std=c++11 -ljsoncpp -lpthread
    3. .PHONY:clean
    4. clean:
    5. rm -f compile_server

    项目设计 -- 基于MVC结构的oj服务 

    本质:建立一个小型网站

    1.获取首页

    2.编辑区域页面

    3.提交判题功能(编译并运行) 

     

    M:Model,通常是和数据交互的模块,比如对题库的增删改查(文件版,mysql版)

    V:view,通常是拿到数据之后,要进行构建网页,渲染网页内容

    C:control,控制器,也就是我们核心业务逻辑

    用户的请求服务路由功能:

    1. #include "../comm/httplib.h"
    2. #include "login.hpp"
    3. #include
    4. #include
    5. #include"oj_control.hpp"
    6. using namespace httplib;
    7. using namespace ns_control;
    8. const std::string login_path = "../oj_login/wwwroot/";
    9. static Control *ctrl_ptr = nullptr;
    10. void Recovery(int signo)
    11. {
    12. ctrl_ptr->RecoveryMachine();
    13. }
    14. int main() {
    15. signal(SIGQUIT,Recovery);
    16. // 用户请求的服务路由功能
    17. Server svr;
    18. Control ctrl;
    19. Login login;
    20. ctrl_ptr = &ctrl;
    21. /*
    22. 1获取所有的题目列表
    23. */
    24. svr.Get(R"(/all_questions)", [&ctrl](const Request &req, Response &resp) {
    25. std::string html;
    26. ctrl.AllQuestions(&html);
    27. resp.set_content(html, "text/html;charset=utf-8");
    28. });
    29. // 2用户要根据题目编号来选择题目
    30. // 这里的\d是正则表达式 + 是匹配数字
    31. // R"()"保持原始字符串不会被特殊字符影响比如\d \r \n之类的不需要做相关的转义
    32. svr.Get(R"(/question/(\d+))", [&ctrl](const Request &req, Response &resp) {
    33. std::string number = req.matches[1];
    34. std::string html;
    35. ctrl.OneQuestion(number,&html);
    36. resp.set_content(html,"text/html;charset=utf-8");
    37. });
    38. // 3用户提交代码,使用我们的判题功能(1.没道题目的测试用例 2.compile_and_run)
    39. svr.Post(R"(/judge/(\d+))",[&ctrl](const Request &req, Response &resp){
    40. std::string number = req.matches[1];
    41. // resp.set_content("这是指定的一道题目的判题:" + number,
    42. // "text/plain;charset=utf-8");
    43. std::string result_json;
    44. ctrl.Judge(number,req.body,&result_json);
    45. resp.set_content(result_json,"application/json;charset=utf-8");
    46. });
    47. svr.Post(R"(/dealregister)",[&ctrl](const Request &req, Response &resp){
    48. int status = 1;
    49. std::string in_json = req.body;
    50. std::string out_json;
    51. if(!ctrl.UserRegister(in_json,&out_json)){
    52. status = 0;
    53. }
    54. LOG(INFO)<<"用户注册status : "<"\n";
    55. Json::Value tmp;
    56. tmp["status"] = status;
    57. Json::FastWriter writer;
    58. std::string res = writer.write(tmp);
    59. resp.set_content(res,"application/json;charset=utf-8");
    60. });
    61. svr.Get(R"(/my_login)",[&login,&ctrl](const Request &req,Response &resp){
    62. //直接跳转到静态的html
    63. std::string html;
    64. ctrl.Login(req.body,&html);
    65. resp.set_content(html, "text/html;charset=utf-8");
    66. });
    67. svr.Get(R"(/register)",[&login,&ctrl](const Request &req,Response &resp){
    68. //直接跳转到静态的html
    69. std::string html;
    70. ctrl.Register(req.body,&html);
    71. resp.set_content(html, "text/html;charset=utf-8");
    72. });
    73. svr.set_base_dir("./wwwroot");
    74. svr.listen("0.0.0.0", 8080);
    75. return 0;
    76. }

    这样当用户通过http请求我们的oj_server服务器的时候我们可以正确的路由到合适的功能

    model功能:提供对数据的操作(文件版)

    1. #pragma once
    2. //文件版本
    3. /*
    4. 编号
    5. 标题
    6. 难度
    7. 描述
    8. 时间(内部),空间(内部处理)
    9. 两批文件构成
    10. 1.question.list:题目列表:不需要出现题目描述
    11. 2.需要题目的描述,需要题目的预设置代码(header.cpp),测试用例代码(tail.cpp)
    12. 这两个内容是通过题目的编号,产生关联的
    13. */
    14. #pragma once
    15. #include "../comm/log.hpp"
    16. #include "../comm/util.hpp"
    17. #include
    18. #include
    19. #include
    20. #include
    21. #include
    22. #include
    23. #include
    24. // 根据题目list文件,加载所有信息到内存中
    25. // model:主要用来和数据进行交互,对外提供访问数据的接口
    26. namespace ns_model {
    27. using namespace std;
    28. using namespace ns_log;
    29. using namespace ns_util;
    30. class Question {
    31. public:
    32. std::string number; // 题目编号
    33. std::string title; // 题目的标题
    34. std::string star; // 难度:简单中等困难
    35. int cpu_limit; // 时间要求 s
    36. int mem_limit; // 空间要求 kb
    37. std::string desc; // 题目的描述
    38. std::string header; // 题目预设给用户在线编辑器的代码
    39. std::string tail; // 题目的测试用例,需要和header拼接形成完整代码
    40. };
    41. const std::string question_list = "./questions/questions.list";
    42. const std::string question_path = "./questions/";
    43. class Model {
    44. private:
    45. // 题号:题目细节
    46. unordered_map questions;
    47. public:
    48. Model() { assert(LoadQuestionList(question_list)); }
    49. bool LoadQuestionList(const std::string &question_list) {
    50. // 加载配置文件questions/question.list + 题目编号文件
    51. ifstream in(question_list);
    52. if (!in.is_open()) {
    53. LOG(FATEL) << "加载题库失败,请检查是否存在题库文件"
    54. << "\n";
    55. return false;
    56. }
    57. std::string line;
    58. while (getline(in, line)) {
    59. vector tokens;
    60. StringUtil::SplitString(line, &tokens, " ");
    61. if (tokens.size() != 5) {
    62. LOG(WARNING) << "加载部分题目失败,请检查文件格式"
    63. << "\n";
    64. continue;
    65. }
    66. Question q;
    67. q.number = tokens[0];
    68. q.title = tokens[1];
    69. q.star = tokens[2];
    70. q.cpu_limit = atoi(tokens[3].c_str());
    71. q.mem_limit = atoi(tokens[4].c_str());
    72. std::string path = question_path;
    73. path += q.number;
    74. path += "/";
    75. FileUtil::ReadFile(path + "desc.txt", &(q.desc), true);
    76. FileUtil::ReadFile(path + "header.cpp", &(q.header), true);
    77. FileUtil::ReadFile(path + "tail.cpp", &(q.tail), true);
    78. questions.insert({q.number, q});
    79. }
    80. LOG(INFO) << "加载题库成功!"
    81. << "\n";
    82. in.close();
    83. return true;
    84. }
    85. bool GetAllQuestion(vector *out) {
    86. if (questions.size() == 0) {
    87. LOG(ERROR) << "用户获取题库失败"
    88. << "\n";
    89. return false;
    90. }
    91. for (const auto &q : questions) {
    92. out->push_back(q.second); // fir是key' sec是value
    93. }
    94. return true;
    95. }
    96. bool GetOneQuestion(const std::string &number, Question *q) {
    97. const auto &iter = questions.find(number);
    98. if (iter == questions.end()) {
    99. LOG(ERROR) << "用户获取题目失败:" << number << "\n";
    100. return false;
    101. }
    102. (*q) = iter->second;
    103. return true;
    104. }
    105. ~Model() {}
    106. };
    107. } // namespace ns_model

    该设计中有一个 question的题目清单,像题库的目录一样,填写每道题目的基本信息:

    对应的是:

    1.题目编号 2.题目名字 3.题目难度 4.时间限制 5.空间限制

    559bed10817d4f8280e61bce3787873a.png

     

     

     model功能:提供对数据的操作(数据库版)

    1. #pragma once
    2. //这个是mysql版本
    3. /*
    4. 编号
    5. 标题
    6. 难度
    7. 描述
    8. 时间(内部),空间(内部处理)
    9. 两批文件构成
    10. 1.question.list:题目列表:不需要出现题目描述
    11. 2.需要题目的描述,需要题目的预设置代码(header.cpp),测试用例代码(tail.cpp)
    12. 这两个内容是通过题目的编号,产生关联的
    13. */
    14. #pragma once
    15. #include "../comm/log.hpp"
    16. #include "../comm/util.hpp"
    17. #include
    18. #include
    19. #include
    20. #include
    21. #include
    22. #include
    23. #include
    24. #include
    25. #include"include/mysql.h"
    26. // 根据题目list文件,加载所有信息到内存中
    27. // model:主要用来和数据进行交互,对外提供访问数据的接口
    28. namespace ns_model {
    29. using namespace std;
    30. using namespace ns_log;
    31. using namespace ns_util;
    32. class Question {
    33. public:
    34. std::string number; // 题目编号
    35. std::string title; // 题目的标题
    36. std::string star; // 难度:简单中等困难
    37. int cpu_limit; // 时间要求 s
    38. int mem_limit; // 空间要求 kb
    39. std::string desc; // 题目的描述
    40. std::string header; // 题目预设给用户在线编辑器的代码
    41. std::string tail; // 题目的测试用例,需要和header拼接形成完整代码
    42. };
    43. const std::string oj_questions ="oj_questions";
    44. const std::string oj_user = "oj_user";
    45. const std::string host = "127.0.0.1";
    46. const std::string user = "oj_client";
    47. const std::string passwd = "123456";
    48. const std::string db = "oj";
    49. const int port = 3306;
    50. class Model {
    51. private:
    52. // 题号:题目细节
    53. unordered_map questions;
    54. public:
    55. Model() { }
    56. bool QueryMySql(const std::string &sql,vector *out)
    57. {
    58. //创建mysql句柄
    59. MYSQL *my = mysql_init(nullptr);
    60. //连接数据库
    61. if(mysql_real_connect(my,host.c_str(),user.c_str(),passwd.c_str(),db.c_str(),port,nullptr,0) == nullptr){
    62. LOG(FATAL)<<"连接数据库失败!"<<"\n";
    63. return false;
    64. }
    65. //一定要设置该链接的编码格式默认是拉钉的
    66. mysql_set_character_set(my,"utf8mb4");
    67. LOG(INFO)<<"连接数据库成功"<<"\n";
    68. //执行sql语句
    69. if(0 != mysql_query(my,sql.c_str()))
    70. {
    71. LOG(WARNING) << sql <<"execute error!"<<"\n";
    72. return false;
    73. }
    74. MYSQL_RES *res = mysql_store_result(my);
    75. //分析结果
    76. int rows = mysql_num_rows(res); //获得行数量
    77. int cols = mysql_num_fields(res);//获得列数量
    78. Question q;
    79. for(int i = 0;i
    80. {
    81. MYSQL_ROW row = mysql_fetch_row(res);
    82. q.number = row[0];
    83. q.title = row[1];
    84. q.star = row[2];
    85. q.desc = row[3];
    86. q.header = row[4];
    87. q.tail = row[5];
    88. q.cpu_limit = atoi(row[6]);
    89. q.mem_limit = atoi(row[7]);
    90. out->push_back(q);
    91. }
    92. //释放结果空间
    93. free(res);
    94. //关闭mysql连接
    95. mysql_close(my);
    96. return true;
    97. }
    98. bool GetAllQuestion(vector *out) {
    99. std::string sql ="select *from ";
    100. sql += oj_questions;
    101. return QueryMySql(sql,out);
    102. }
    103. bool GetOneQuestion(const std::string &number, Question *q) {
    104. bool res = false;
    105. std::string sql = "select *from ";
    106. sql+=oj_questions;
    107. sql+= " where number=";
    108. sql+=number;
    109. vector result;
    110. if(QueryMySql(sql,&result))
    111. {
    112. if(result.size() == 1)
    113. {
    114. *q = result[0];
    115. res = true;
    116. }
    117. }
    118. return res;
    119. }
    120. bool UserRegister(const std::string& in_json,std::string* out_json)
    121. {
    122. //这里先对in_json反序列化
    123. Json::Reader reader;
    124. Json::Value in_value;
    125. reader.parse(in_json,in_value);
    126. std::string number = in_value["number"].asString();
    127. std::string name = in_value["name"].asString();
    128. std::string password = in_value["password"].asString();
    129. int limit = in_value["limit"].asInt();
    130. int level = in_value["level"].asInt();
    131. //判断账号密码是否可行
    132. std::string sql = " select *from ";
    133. sql+=oj_user;
    134. sql+=" where number=";
    135. sql+=number;
    136. //创建数据库
    137. MYSQL *my = mysql_init(nullptr);
    138. //连接数据库
    139. if(mysql_real_connect(my,host.c_str(),user.c_str(),passwd.c_str(),db.c_str(),port,nullptr,0) == nullptr)
    140. {
    141. LOG(WARNING)<<"连接到用户数据库失败"<<"\n";
    142. return false;
    143. }
    144. //一定要记得设置该链接的编码格式
    145. mysql_set_character_set(my,"utf8");
    146. LOG(INFO)<<"连接懂啊用户数据库成功"<<"\n";
    147. if(0 != mysql_query(my,sql.c_str())){
    148. LOG(WARNING)<< sql <<"execute error!"<<"\n";
    149. return false;
    150. }
    151. MYSQL_RES *res = mysql_store_result(my);
    152. if(mysql_num_rows(res) == 0)//获得行数量
    153. {
    154. //当前输入的数据可以创建用户
    155. MYSQL_STMT *stmt = mysql_stmt_init(my);
    156. const char* query = "insert into oj_user values (?,?,?,?,?)";
    157. if(mysql_stmt_prepare(stmt,query,strlen(query)) != 0){
    158. LOG(WARNING)<<"stmt出现错误"<<"\n";
    159. mysql_stmt_close(stmt);
    160. mysql_close(my);
    161. return false;
    162. }
    163. //下面开始绑定
    164. MYSQL_BIND bind_params[5];
    165. memset(bind_params,0,sizeof bind_params);
    166. bind_params[0].buffer_type = MYSQL_TYPE_STRING;
    167. bind_params[0].buffer = (char*)number.c_str();
    168. bind_params[0].buffer_length = number.size();
    169. bind_params[1].buffer_type = MYSQL_TYPE_STRING;
    170. bind_params[1].buffer = (char*)name.c_str();
    171. bind_params[1].buffer_length = name.size();
    172. bind_params[2].buffer_type = MYSQL_TYPE_STRING;
    173. bind_params[2].buffer = (char*)password.c_str();
    174. bind_params[2].buffer_length = password.size();
    175. bind_params[3].buffer_type = MYSQL_TYPE_LONG;
    176. bind_params[3].buffer = &limit;
    177. bind_params[3].is_unsigned = 1;
    178. bind_params[4].buffer_type = MYSQL_TYPE_LONG;
    179. bind_params[4].buffer = &level;
    180. bind_params[4].is_unsigned = 1;
    181. if(mysql_stmt_bind_param(stmt,bind_params) !=0){
    182. LOG(WARNING) <<"绑定stmt参数出错"<<"\n";
    183. mysql_stmt_close(stmt);
    184. mysql_close(my);
    185. return false;
    186. }
    187. //执行插入语句
    188. if(mysql_stmt_execute(stmt)!=0){
    189. LOG(WARNING)<<"执行stmt语句的时候出现错误..."<<"\n";
    190. mysql_stmt_close(stmt);
    191. mysql_close(my);
    192. return false;
    193. }
    194. mysql_stmt_close(stmt);
    195. mysql_close(my);
    196. return true;
    197. }
    198. else{
    199. //服务器有重复的用户num ,不允许再创建了
    200. return false;
    201. }
    202. //保存到服务器
    203. //这里out_json暂时没有用,没有要返回的值
    204. return true;
    205. }
    206. ~Model() {}
    207. };
    208. } // namespace ns_model

    control:逻辑控制模块

    1. #pragma once
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. #include"oj_view.hpp"
    11. // #include"oj_model.hpp"
    12. #include"oj_model2.hpp"
    13. #include"../comm/log.hpp"
    14. #include"../comm/util.hpp"
    15. #include"../comm/httplib.h"
    16. namespace ns_control
    17. {
    18. using namespace std;
    19. using namespace httplib;
    20. using namespace ns_log;
    21. using namespace ns_util;
    22. using namespace ns_model;
    23. using namespace ns_view;
    24. //提供服务的主机的内容
    25. class Machine
    26. {
    27. public:
    28. std::string ip; //编译服务器的ip
    29. int port; //编译服务器的端口
    30. uint64_t load; //编译服务器负载
    31. std::mutex *mtx;//mutex是禁止拷贝的,使用指针来完成
    32. public:
    33. Machine():ip(""),port(0),load(0),mtx(nullptr)
    34. {}
    35. ~Machine()
    36. {}
    37. public:
    38. void ResetLoad()
    39. {
    40. if(mtx)mtx->lock();
    41. load = 0;
    42. LOG(DEBUG)<<"当前ip:"<"端口:"<"的load已经清除load = "<"\n";
    43. if(mtx)mtx->unlock();
    44. }
    45. //提升主机负载
    46. void IncLoad()
    47. {
    48. if(mtx) mtx->lock();
    49. ++load;
    50. if(mtx) mtx->unlock();
    51. }
    52. //减少主机负载
    53. void DecLoad()
    54. {
    55. if(mtx) mtx->lock();
    56. --load;
    57. if(mtx) mtx->unlock();
    58. }
    59. //获取主机负载,没有太大的意义,只是为了同一接口
    60. uint64_t Load()
    61. {
    62. uint64_t _load = 0;
    63. if(mtx) mtx->lock();
    64. _load = load;
    65. if(mtx) mtx->unlock();
    66. return _load;
    67. }
    68. };
    69. const std::string service_machine = "./conf/service_machine.conf";
    70. //负载均衡模块
    71. class LoadBalance
    72. {
    73. private:
    74. //可以给我们提供编译服务的所有的主机
    75. //每一台主机都有自己的下标,充当当前主机的id
    76. std::vector machines;
    77. //所有在线的主机
    78. std::vector<int> online;
    79. //所有离线主机的id
    80. std::vector<int> offline;
    81. //保证选择主机上的这个东西要保证数据安全
    82. std::mutex mtx;
    83. public:
    84. LoadBalance(){
    85. assert(LoadConf(service_machine));
    86. LOG(INFO)<<"加载"<"成功"<<"\n";
    87. }
    88. ~LoadBalance(){}
    89. public:
    90. bool LoadConf(const std::string &machine_cof)
    91. {
    92. std::ifstream in(machine_cof);
    93. if(!in.is_open())\
    94. {
    95. LOG(FATAL) <<"加载:"<"失败"<<"\n";
    96. return false;
    97. }
    98. std::string line;
    99. while (getline(in,line))
    100. {
    101. std::vector tokens;
    102. StringUtil::SplitString(line,&tokens,":");
    103. if(tokens.size()!=2)
    104. {
    105. LOG(WARNING) <<"切分"<"失败"<<"\n";
    106. std::cout<0]<<":"<1]<
    107. continue;
    108. }
    109. //LOG(INFO) <<"切分"<
    110. Machine m;
    111. m.ip = tokens[0];
    112. m.port = atoi(tokens[1].c_str());
    113. m.load = 0;
    114. m.mtx = new std::mutex();
    115. online.push_back(machines.size());
    116. machines.push_back(m);
    117. }
    118. in.close();
    119. return true;
    120. }
    121. //id:是一个输出型参数
    122. //m:是一个输出型参数
    123. bool SmartChoice(int *id,Machine **m)
    124. {
    125. //1.使用选择好的主机(更新该主机的负载)
    126. //2.我们需要可能离线该主机
    127. mtx.lock();
    128. //选择主机
    129. //一般的负载均衡的算法
    130. //1.随机数法 + hash
    131. //2.轮询 + hash
    132. int online_num = online.size();//在线主机的个数
    133. if(online_num == 0){
    134. mtx.unlock();
    135. LOG(FATAL) << "所有的后端编译主机已经全部离线,请后端的尽快重启"<<"\n";
    136. return false;
    137. }
    138. LOG(DEBUG)<<"online:"<size()<<"\n";
    139. //通过编译,找到负载最小的机器
    140. *id = online[0];
    141. *m = &machines[online[0]];
    142. uint64_t min_load = machines[online[0]].Load();
    143. for(int i = 1;i
    144. {
    145. uint64_t curr_load = machines[online[i]].Load();
    146. if(min_load > curr_load){
    147. min_load = curr_load;
    148. *id = online[i];
    149. *m = &machines[online[i]];
    150. }
    151. }
    152. mtx.unlock();
    153. return true;
    154. }
    155. void OfflineMachine(int which)
    156. {
    157. mtx.lock();
    158. for(auto iter = online.begin();iter!=online.end();iter++)
    159. {
    160. if(*iter == which){
    161. //要离线的主机找到了
    162. machines[which].ResetLoad();
    163. LOG(DEBUG)<<"当前离线主机的负载更改为:"<
    164. online.erase(iter);
    165. offline.push_back(which);
    166. break;//因为break的存在,所以暂时不考虑迭代器失效的问题
    167. }
    168. }
    169. mtx.unlock();
    170. }
    171. void OnlineMachine()
    172. {
    173. //我们统一上线,后面统一解决
    174. mtx.lock();
    175. online.insert(online.end(),offline.begin(),offline.end());
    176. offline.erase(offline.begin(),offline.end());
    177. mtx.unlock();
    178. LOG(INFO)<<"所有的主机又上线了"<<"\n";
    179. LOG(INFO)<<"online:"<size()<<"offline"<size()<<"\n";
    180. }
    181. void ShowMachines()
    182. {
    183. mtx.lock();
    184. LOG(INFO)<<"online:"<size()<<"offline"<size()<<"\n";
    185. mtx.unlock();
    186. }
    187. };
    188. //这是我们核心业务逻辑的控制器
    189. class Control
    190. {
    191. private:
    192. Model model_;//提供后台数据
    193. View view_; //提供网页渲染功能
    194. LoadBalance load_blance_; //核心负载均衡器
    195. public:
    196. void RecoveryMachine()
    197. {
    198. load_blance_.OnlineMachine();
    199. }
    200. //根据题目数据构建网页
    201. //html:输出型参数
    202. bool AllQuestions(string *html)
    203. {
    204. bool ret = true;
    205. vector all;
    206. if(model_.GetAllQuestion(&all))
    207. {
    208. sort(all.begin(),all.end(),[](const Question &q1,const Question &q2){
    209. return atoi(q1.number.c_str()) < atoi(q2.number.c_str());
    210. });
    211. //获取题目信息 成功,将所有的题目数据构建成网页
    212. view_.AllExpandHtml(all,html);
    213. }
    214. else
    215. {
    216. *html="获取题目失败,形成题目列表失败";
    217. ret = false;
    218. }
    219. return ret;
    220. }
    221. bool OneQuestion(const string &number,string *html)
    222. {
    223. Question q;
    224. bool ret = true;
    225. if(model_.GetOneQuestion(number,&q))
    226. {
    227. //获取指定信息的题目成功,构建程网页
    228. view_.OneExpandHtml(q,html);
    229. }
    230. else
    231. {
    232. *html="获取指定题目题目失败,形成题目列表失败";
    233. ret = false;
    234. }
    235. return ret;
    236. }
    237. void Login(const std::string in_json,std::string *out_json)
    238. {
    239. //in_json是发送过来的请求数据,用户的账号等待
    240. //返回渲染的登录界面
    241. view_.LoginExpandHtml(out_json);
    242. }
    243. void Register(const std::string in_json,std::string *out_json)
    244. {
    245. if(view_.RegisterExpandHtml(out_json)){
    246. LOG(INFO)<<"插入成功"<<"\n";
    247. }
    248. else{
    249. LOG(INFO)<<"插入失败,可能是重复的用户"<<"\n";
    250. }
    251. }
    252. bool UserRegister(const std::string in_json,std::string *out_json)
    253. {
    254. return model_.UserRegister(in_json,out_json);
    255. }
    256. //id:: 100
    257. //code:include
    258. //input:
    259. void Judge(const std::string &number,const std::string in_json,std::string *out_json)
    260. {
    261. // LOG(INFO)<<"调用Judge功能"<<"\n";
    262. // LOG(DEBUG)<
    263. //0.根据题目编号,拿到题目细节
    264. Question q;
    265. model_.GetOneQuestion(number,&q);
    266. //1.in_json反序列化 ,得到题目的id,得到源代码,input
    267. Json::Reader reader;
    268. Json::Value in_value;
    269. reader.parse(in_json,in_value);
    270. std::string code = in_value["code"].asString();
    271. //2.重新拼接用户代码+测试用例代码,形成新的代码
    272. Json::Value compile_value;
    273. compile_value["input"] = in_value["input"].asString();
    274. compile_value["code"] = code + q.tail;
    275. compile_value["cpu_limit"] = q.cpu_limit;
    276. compile_value["mem_limit"] = q.mem_limit;
    277. Json::FastWriter writer;
    278. std::string compile_string = writer.write(compile_value);
    279. //3.选择负载最低的主机,然后发起HTTP请求得到结果
    280. //规则:一直选择,直到主机可用,否则就是全部挂掉
    281. while(true)
    282. {
    283. int id = 0;
    284. Machine *m = nullptr;
    285. if(!load_blance_.SmartChoice(&id,&m))
    286. {
    287. break;
    288. }
    289. //4.*out_json = 将结果复制给out_json
    290. Client cli(m->ip,m->port);
    291. m->IncLoad();
    292. LOG(DEBUG)<<"选择主机成功,主机id:"<"\n详情:"<ip<<":"<port<<"当前主机负载:"<Load()<<"\n";
    293. if(auto res = cli.Post("/compile_and_run",compile_string,"application/json;charset=utf-8"))
    294. {
    295. //将我们的结果返回给out_json
    296. if(res->status == 200)
    297. {
    298. *out_json = res->body;
    299. m->DecLoad();
    300. LOG(INFO)<<"请求编译和运行服务成功..."<<"\n";
    301. break;
    302. }
    303. m->DecLoad();
    304. }
    305. else
    306. {
    307. //请求失败
    308. LOG(ERROR)<<"选择主机失败,主机id:"<"详情:"<ip<<":"<port<<"可能已经离线"<<"\n";
    309. load_blance_.OfflineMachine(id);
    310. load_blance_.ShowMachines();//仅仅为了调试
    311. }
    312. //m->DecLoad();
    313. }
    314. }
    315. Control(){}
    316. ~Control(){}
    317. };
    318. }

    control模块实现了 负载均衡

    • 负载均衡
      • 第一种:随机数+hash
      • 第二种:轮询+hash , 本文是在用轮询+hash
    • 为了实现负载均衡所有要把所有主机管理起来,有了Machine类
      • std::string ip :编译服务器的ip
      • int port:编译服务器的端口
      • uint64_t load :编译服务器的负载
      • std::mutex *mtx:每个机器可能会同时被多个用户访问,所以要有锁来保证临界资源,并且mutex是不允许拷贝的,所以这里直接用指针,这样在赋值构造和拷贝构造就没事了

     

     view渲染功能:将后端的代码渲染到html返回给前端

    这里就要使用到ctemplate库了:

    0ac4557f0c2242cab7e55958412ab2ab.png

     

     

    1. #pragma once
    2. #include
    3. #include
    4. #include
    5. // #include"oj_model.hpp"
    6. #include"oj_model2.hpp"
    7. namespace ns_view
    8. {
    9. using namespace ns_model;
    10. const std::string template_path ="./template_html/";
    11. const std::string login_path = "./login_html/";
    12. class View
    13. {
    14. public:
    15. View(){}
    16. ~View(){}
    17. bool RegisterExpandHtml(std::string *html)
    18. {
    19. //新城路径
    20. std::string src_html = login_path + "register.html";
    21. //形成数据字典
    22. ctemplate::TemplateDictionary root("register");
    23. //获取渲染的网页
    24. ctemplate::Template *tpl = ctemplate::Template::GetTemplate(src_html,ctemplate::DO_NOT_STRIP);
    25. //开始渲染
    26. tpl->Expand(html,&root);
    27. return true;
    28. }
    29. void LoginExpandHtml(std::string *html)
    30. {
    31. //形成路径
    32. std::string src_html = login_path + "login.html";
    33. //形成数据字典
    34. ctemplate::TemplateDictionary root("my_login");
    35. //获取渲染网页
    36. ctemplate::Template *tpl = ctemplate::Template::GetTemplate(src_html,ctemplate::DO_NOT_STRIP);
    37. //开始渲染
    38. tpl->Expand(html,&root);
    39. }
    40. void AllExpandHtml(const vector &questions,std::string *html)
    41. {
    42. // 题目的编号 题目的标题 题目的难度
    43. // 推荐使用表格显示
    44. //1。形成路径
    45. std::string src_html = template_path + "all_questions.html";
    46. LOG(INFO)<<"形成路径成功:"<< src_html <<"\n";
    47. //2.形成数据字典
    48. ctemplate::TemplateDictionary root("all_questions");
    49. for(const auto& q:questions)
    50. {
    51. ctemplate::TemplateDictionary *sub = root.AddSectionDictionary("question_list");
    52. sub->SetValue("number",q.number);
    53. sub->SetValue("title",q.title);
    54. sub->SetValue("star",q.star);
    55. }
    56. //3.获取被渲染的网页html
    57. ctemplate::Template *tpl = ctemplate::Template::GetTemplate(src_html,ctemplate::DO_NOT_STRIP);
    58. LOG(INFO)<<"获取渲染网页的html成功"<<"\n";
    59. //4.开始完成渲染功能
    60. tpl->Expand(html,&root);
    61. LOG(INFO)<<"渲染成功"<<"\n";
    62. }
    63. void OneExpandHtml(const Question &q,std::string *html)
    64. {
    65. //形成路径
    66. std::string src_html = template_path + "one_question.html";
    67. LOG(DEBUG)<<"one expand html :"<"\n";
    68. //q.desc
    69. //形成数字典
    70. ctemplate::TemplateDictionary root("one_question");
    71. root.SetValue("number",q.number);
    72. root.SetValue("title",q.title);
    73. root.SetValue("star",q.star);
    74. root.SetValue("desc",q.desc);
    75. root.SetValue("pre_code",q.header);
    76. //获取被渲染的html
    77. ctemplate::Template *tpl = ctemplate::Template::GetTemplate(src_html,ctemplate::DO_NOT_STRIP);
    78. //开始渲染功能
    79. tpl->Expand(html,&root);
    80. }
    81. };
    82. }

     

     

    总结 

    1.前端的代码在博客最上端绑定的文件当中 ,篇幅太长不展示出来了

    2.该项目的技术栈众多,是c++后端和前端进行交互的一个项目

    3.项目的难点有:负载均衡的分配到每一台编译服务器、容错处理,能够处理多种不同的错误原因、并发处理要对临界资源的管理、以及高并发访问的话要对效率有所保证,毕竟在线oj服务是具有时效性的

    4.debug困难,要在test.cc下测试成功后再进行编写,便于修改bug

     

     

     

     

     

     

     

     

     

     

     

     

     

     

  • 相关阅读:
    车企数智化丨帷幄SDP数字化护航经销商业务健康度
    2022年最新安徽建筑施工信号工(建筑特种作业)考试真题题库及答案
    LeetCode[636]函数的独占时间
    Tableau自学四部曲_Part3:基础图表制作
    【软考软件评测师】第二十五章 系统安全设计(网络攻击)
    【图论 树 深度优先搜索】2246. 相邻字符不同的最长路径
    第十二章 配置 Apache 以与 Web 网关配合使用 (Windows)
    厉害了!阿里内部都用的Spring+MyBatis源码手册,实战理论两不误
    docker安装anaconda3 python环境
    GEE16: 区域日均降水量计算
  • 原文地址:https://blog.csdn.net/Obto_/article/details/132558354