实战项目负载均衡式在线 OJ

news2024/11/27 5:09:58

> 作者:დ旧言~
> 座右铭:松树千年终是朽,槿花一日自为荣。

> 目标:能自己实现负载均衡式在线 OJ。

> 毒鸡汤:有些事情,总是不明白,所以我不会坚持。早安!

> 专栏选自:实战项目_დ旧言~的博客-CSDN博客

> 望小伙伴们点赞👍收藏✨加关注哟💕💕

一、整体框架

总结:

先运行 oj_server,用来提供前端页面(前端页面向后端发送http请求),后端运行多个 compile_server 编译主机,oj_server 接收到请求后把请求再转发给 compile_server,compile_server经过编译运行后把结果响应回去,最终反映到前端页面上。

本项目四个核心模块构成:

  1. 公共模块:存放各个模块都能用到的公共文件。
  2. 编译与运行模块(可视为服务端):适配用户请求,负责完成基于网络请求式的编译并运行服务。
  3. OJ相关模块(可视为客户端):完成连接数据库、获取题目列表,查看题目、编写题目界面,负载均衡等后端核心业务逻辑。
  4. 项目发布模块:只包含整合后的二进制可执行编译运行文件与OJ系统网页文件,专供用户使用。

二、所用技术和开发环境

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

三、编写思路

  1. 先编写compile_server.
  2. 再编写oj_server.
  3. 基于文件版的负载均衡式在线 Oj.
  4. 前端网页设计.
  5. 基于MySQL版的负载均衡式在线 Oj.

四、compile_server 服务设计

提供的服务:

  • 编译并运行代码,得到格式化的相关结果。

4.1、第一个功能: Compile 编译

compiler.hpp文件:

#pragma once

#include <iostream>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <fcntl.h>

#include "../comm/utils.hpp"
#include "../comm/log.hpp"

// 只进行编译
namespace ns_compiler
{
using namespace ns_utils;
using namespace ns_log;
class Compiler
{
public:
  Compiler() {}
  ~Compiler() {}

  // 返回值,编译成功:true,否则:false
  static bool Compile(const std::string &filename)
  {
      pid_t pid = fork(); // 子进程进行程序替换来编译这个文件
      if (pid < 0)
      {
          LOG(ERROR) << "内部错误,创建子进程失败" << "\n";
          return false;
      }
      else if (pid == 0)
      {
          // 子进程:调用编译器,进行程序替换执行文件

          umask(0); // 重置掩码

          // 创建错误信息文件,用来保存错误信息
          int errfd = open(PathUtil::Stderr(filename).c_str(), O_CREAT | O_WRONLY, 0644);
          // std::cout << "debug ===== " << std::endl;
          if (errfd < 0)
          {
              LOG(ERROR) << "内部错误,打开文件失败" << "\n";
              exit(1);
          }

          // 重定向标准错误到errfd,以至于打印到显示器的信息,打印到Stderr文件
          dup2(errfd, 2);

          // g++ -o 可执行文件 源文件 -std=c++11
          execlp("g++", "g++", "-o", PathUtil::Exe(filename).c_str(), PathUtil::Src(filename).c_str(), "-std=c++11", nullptr /*不要忘记*/);

          exit(2); // 执行完就退出
      }
      else
      {
          // 父进程
          waitpid(pid, nullptr, 0);
          // 判断可执行文件在不在,如果在,就说明编译成功,否则失败
          if (FileUtil::IsExistFile(PathUtil::Exe(filename).c_str()))
          {
              LOG(INFO) << PathUtil::Src(filename) << " 编译成功" << "\n";

              return true;
          }
      }
      LOG(ERROR) << "编译失败,没有形成可执行程序" << "\n";

      return false;
  }
};
}

Log.hpp文件:

#pragma once

#include <iostream>

#include "utils.hpp"

namespace ns_log
{
using namespace ns_utils;

// 日志等级
enum
{
  INFO, // 就是整数
  DEBUG,
  WARNING,
  ERROR,
  FATAL
};

// 频繁使用,使用内联函数
inline std::ostream &Log(const std::string &level, const std::string &filename, int line)
{
  std::string message = "[";
  message += level + "]";
  message += "[";
  message += filename + "]";
  message += "[";
  message += std::to_string(line) + "]";
  message += "[";
  message += TimeUtil::GetCurrentTime() + "]";
  std::cout << message;
  return std::cout;
}

// LOG(INFO) << "message"; // 开放式日志

#define LOG(level) Log(#level, __FILE__, __LINE__)
}

4.2、第二个功能: Run 运行

test_setrlimit文件:测试使用限制资源函数

#include <iostream>
#include <sys/time.h>
#include <sys/resource.h>
#include <unistd.h>
#include <signal.h>

void handler(int sig)
{
   std::cout << "sig : " << sig << std::endl;
   exit(-1);
}

int main()
{

   for (int i = 1; i < 32; ++i)
   {
       signal(i, handler);
   }

   // // 限制cpu使用时间
   // struct rlimit r_cpu;
   // r_cpu.rlim_cur = 1; // 1秒
   // r_cpu.rlim_max = RLIM_INFINITY;

   // setrlimit(RLIMIT_CPU, &r_cpu);

   // while (1)
   //     ;

   // 限制开辟空间大小
   struct rlimit r_mem;
   r_mem.rlim_cur = 1024 * 1024 * 40; // 40M
   r_mem.rlim_max = RLIM_INFINITY;

   setrlimit(RLIMIT_AS, &r_mem);

   int count = 0;
   while (1)
   {
       int *p = new int[1024 * 1024];
       ++count;
       std::cout << "count : " << count << std::endl;
       sleep(1);
   }
   return 0;
}

这里测试这两个终止信号:

# cpu时间限制信号:24
xp2@Xpccccc:~/Items/Load_Balancing_Online_Judging$ ./a.out 
sig : 24

# 申请内存限制信号:6
xp2@Xpccccc:~/Items/Load_Balancing_Online_Judging$ ./a.out 
count : 1
count : 2
count : 3
count : 4
count : 5
count : 6
count : 7
count : 8
terminate called after throwing an instance of 'std::bad_alloc'
 what():  std::bad_alloc
sig : 6
Aborted (core dumped)

# 信号列表
xp2@Xpccccc:~/Items/Load_Balancing_Online_Judging$ 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
31) SIGSYS      34) SIGRTMIN    35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3
38) SIGRTMIN+4  39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8
43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7
58) SIGRTMAX-6  59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
63) SIGRTMAX-1  64) SIGRTMAX

runner.hpp文件:

#pragma once

#include <iostream>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <sys/time.h>
#include <sys/resource.h>

#include "../comm/log.hpp"
#include "../comm/utils.hpp"

namespace ns_runner
{
   using namespace ns_log;
   using namespace ns_utils;
   class Runner
   {
   public:
       static void SetRlimit(int cpu_limit, int mem_limit)
       {
           // 限制cpu使用时间
           struct rlimit r_cpu;
           r_cpu.rlim_cur = cpu_limit; // 1秒
           r_cpu.rlim_max = RLIM_INFINITY;

           setrlimit(RLIMIT_CPU, &r_cpu);

           // 限制开辟空间大小
           struct rlimit r_mem;
           r_mem.rlim_cur = mem_limit * 1024; // KB
           r_mem.rlim_max = RLIM_INFINITY;

           setrlimit(RLIMIT_AS, &r_mem);
       }

   public:
       // 信号 ,0表示运行成功;-1表示内部错误,其他就是对应的信号值

       /*
           filename: 文件名,不带后缀
           cpu_limit: cpu限制使用时间(秒)
           mem_limit: 内存开辟限制(KB)
       */
       static int runner(std::string &filename, int cpu_limit, int mem_limit)
       {
           /*
               因为运行可执行文件,需要保存运行结果
               运行结果:1.执行失败 2.执行成功
               1.执行失败 把错误信息放在.stderr文件中
               2.执行成功 把执行结果放在.stdout文件中
               这里加一个可扩展的模块,比如,对于用户,可能会自己添加测试用例,那么他输入的用例需要放到.stdin文件中
           */

           // 这里执行对应的文件,我们不关心他运行后的结果,只关心它运行成功或失败

           umask(0);
           int _stdin_fd = open(PathUtil::Stdin(filename).c_str(), O_CREAT | O_WRONLY, 0644);
           int _stdout_fd = open(PathUtil::Stdout(filename).c_str(), O_CREAT | O_WRONLY, 0644);
           int _stderr_fd = open(PathUtil::Stderr(filename).c_str(), O_CREAT | O_WRONLY, 0644);

           // 必须得先保证能打开这些文件,如果打不开,后续动作将没有意义(读取不了文件,对比不了结果)
           if (_stdin_fd < 0 || _stdout_fd < 0 || _stderr_fd < 0)
           {
               LOG(FATAL) << "打开文件失败" << "\n";
               return -1; // 代表打开文件失败
           }
           pid_t pid = fork();
           if (pid < 0)
           {
               ::close(_stdin_fd);
               ::close(_stdout_fd);
               ::close(_stderr_fd);
               LOG(FATAL) << " 内部错误,创建子进程错误" << "\n";
               return -2; // 代码创建子进程失败
           }
           else if (pid == 0)
           {
               // 重定向
               dup2(_stdin_fd, 0);
               dup2(_stdout_fd, 1);
               dup2(_stderr_fd, 2);

               // 设置限制
               SetRlimit(cpu_limit, mem_limit); // 这里出问题也是使用信号

               execl(PathUtil::Exe(filename).c_str() /*文件路径*/, PathUtil::Exe(filename).c_str() /*执行文件*/, nullptr);

               exit(1);
           }
           else
           {
               ::close(_stdin_fd);
               ::close(_stdout_fd);
               ::close(_stderr_fd);
               int status = 0;
               waitpid(pid, &status, 0);

               return status & 0x7f; // 信号 ,0表示运行成功;-1表示内部错误,其他就是对应的信号值
           }
           return -3; // 执行错误
       }
   };
}

4.3、第三个功能: Compile_Run 编译并运行

图解:

compile_run.hpp文件:

#pragma once

#include <jsoncpp/json/json.h>

#include "compiler.hpp"
#include "runner.hpp"

#include "../comm/utils.hpp"

namespace ns_complie_run
{

   using namespace ns_compiler;
   using namespace ns_runner;
   using namespace ns_utils;
   class Compile_And_Run
   {

   public:
       static std::string StatusToDesc(int status, const std::string &filename)
       {
           std::string desc;
           switch (status)
           {
           case 0:
               desc = "编译运行成功!";
               break;
           case -1:
               desc = "代码文件为空";
               break;
           case -2:
               desc = "未知错误";
               break;
           case -3:
               FileUtil::ReadFile(PathUtil::Compile_err(filename), &desc, true); // 读取编译错误的文件内容
               break;
           case SIGABRT:
               desc = "申请空间超过限制"; // 6
               break;
           case SIGXCPU:
               desc = "CPU使用时间超过限制"; // 24
               break;
           case SIGFPE:
               desc = "浮点数溢出"; // 8
               break;
           case SIGSEGV:
               desc = "段错误"; // 11
               break;
           default:
               desc = "未知: " + std::to_string(status);
               break;
           }
           return desc;
       }

       static void RemoveTmpFile(const std::string &filename)
       {
           std::string _src = PathUtil::Src(filename);
           if (FileUtil::IsExistFile(_src))
           {
               unlink(_src.c_str());
           }
           std::string _stdin = PathUtil::Stdin(filename);
           if (FileUtil::IsExistFile(_stdin))
           {
               unlink(_stdin.c_str());
           }
           std::string _stdout = PathUtil::Stdout(filename);
           if (FileUtil::IsExistFile(_stdout))
           {
               unlink(_stdout.c_str());
           }
           std::string _stderr = PathUtil::Stderr(filename);
           if (FileUtil::IsExistFile(_stderr))
           {
               unlink(_stderr.c_str());
           }
           std::string _compile_err = PathUtil::Compile_err(filename);
           if (FileUtil::IsExistFile(_compile_err))
           {
               unlink(_compile_err.c_str());
           }
           std::string _exe = PathUtil::Exe(filename);
           if (FileUtil::IsExistFile(_exe))
           {
               unlink(_exe.c_str());
           }
       }

   public:
       /*
           输入:
           code: 用户提交的代码
           input: 用户自己提交的用例

           in_json = {"code":"#include ....","input":"...","cpu_limit":"...","mem_limit":"..."}

           输出:
           status: 状态码
           reason: 错误原因

           下面两个选择填入
           stdout: 程序运行完的结果
           stderr: 程序异常的原因
           out_json = {"status":"...","reason":"...","stdout":"...","stderr":"..."}
       */

       // 这里的in_json是网络传输过来
       static void Compile_Run(std::string &in_json, std::string *out_json)
       {
           Json::Value in_value;
           Json::Reader reader;

           reader.parse(in_json, in_value);

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

           int status = 0;
           Json::Value out_value;
           std::string filename;
           int run_code;

           if (code.size() == 0)
           {
               // TODO
               status = -1; // 代码文件为空
               goto END;
           }

           // 形成的文件得具有唯一性
           filename = FileUtil::UniqueFileName(code);

           // 形成临时src文件
           // 把code里面的内容放到Src文件中
           if (!FileUtil::WriteFile(PathUtil::Src(filename), code))
           {
               status = -2; // 写文件失败
               goto END;
           }

           // 编译
           if (!Compiler::Compile(filename))
           {
               status = -3; // 编译失败
               goto END;
           }

           // 执行
           run_code = Runner::Run(filename, cpu_limit, mem_limit);
           status = run_code;
           if (run_code < 0)
           {
               status = -2; // 未知错误
           }
           else // run_code >= 0
           {
               // 运行成功就是0
               status = run_code;
           }

       END:
           out_value["status"] = status;
           out_value["reason"] = StatusToDesc(status, filename);
           if (status == 0)
           {
               // 整个过程全部成功
               // 运行结果在stdout文件
               std::string _stdout;
               FileUtil::ReadFile(PathUtil::Stdout(filename), &_stdout, true); // 需要\n
               out_value["stdout"] = _stdout;

               // 运行错误在stderr文件 -- 存在一点问题,运行错误不会进来这个文件
               std::string _stderr;
               FileUtil::ReadFile(PathUtil::Stderr(filename), &_stderr, true); // 需要\n
               out_value["stderr"] = _stderr;
           }

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

           // 已经得到结果了,直接移除临时文件
           RemoveTmpFile(filename);
       }
   };
}

测试功能:(complile_server.cc文件)

#include <jsoncpp/json/json.h>

#include "compile_run.hpp"

using namespace ns_complie_run;

int main()
{
   // 调试
   // 网络上发来的Json数据
   // n_json = {"code":"#include ....","input":"...","cpu_limit":"...","mem_limit":"..."}
   // out_json = {"status" : "...", "reason" : "...", "stdout" : "...", "stderr" : "..."}

   std::string in_json;
   Json::Value in_value;
   // R"()"  -- raw string ,不加工的数据
   in_value["code"] = R"(#include <iostream>
       int main(){
           // aaaa
           // int a = 1;
           // a /= 0;
           // int *p = new int[1024*1024*50];
           std::cout << "你好哇"<< std::endl;
           return 0;
       }
   )";
   in_value["input"] = "";
   in_value["cpu_limit"] = 1;
   in_value["mem_limit"] = 10240*3;

   Json::StyledWriter writer;
   in_json = writer.write(in_value);

   std::string out_json;
   Compile_And_Run::Compile_Run(in_json, &out_json);

   std::cout << out_json << std::endl;
   return 0;
}

4.4、第四个功能:把 compile_run 打包成网络服务

引入 cpp-httplib 工具:直接添加 httplib.h 库,可以直接使用其功能。

#include <jsoncpp/json/json.h>

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

using namespace ns_complie_run;
using namespace httplib;

int main(int argc, char *argv[])
{
   if (argc != 2)
   {
       std::cout << "Usage : \n        ./compile_server port" << std::endl;
       return 1;
   }

   uint16_t serverport = std::stoi(argv[1]);

   std::cout << serverport << std::endl;
   // 把compile_run打包成网络服务
   Server svr;
   svr.Get("/hello", [](const Request &req, Response &resp)
           { resp.set_content("hello httplib,你好啊", "text/plain;charset=utf-8"); });

   svr.Post("/compile_run", [](const Request &req, Response &resp)
            {
       std::string in_json = req.body;
       std::string out_json;

       if (!in_json.empty())
       {
           Compile_And_Run::Compile_Run(in_json, &out_json);
           resp.set_content(out_json,"application/json;charset=utf-8");
       } });

   // 绑定特定端口,多次启动可以绑定多个端口  -- 这里端口记得在服务器开启,不然可能访问不了
   svr.listen("0.0.0.0", serverport);
   return 0;
}

五、基于MVC结构的 oj_server 服务设计

MVC结构:

  • M :Model ,通常是和数据交互的模块,比如对题库进行增删查改(文件版/MySQL)。
  • V :View ,通常是拿到数据之后,要进行构建网页,渲染网页内容,展示给用户的(浏览器)。
  • C :Control ,控制器,本质上就是我们的核心业务逻辑。

oj_server 服务设计:本质就是建立一个小型网站。

5.1、第一个功能: 用户请求的服务器路由功能

oj_server.hpp文件:

#include <iostream>

#include "../comm/httplib.h"

using namespace httplib;

int main()
{
   // 用户请求的服务路由功能
   Server svr;

   // 获取题目列表
   svr.Get("/questions", [](const Request &req, Response &resp)
           { resp.set_content("获取题目列表", "text/plain;charset=utf-8"); });

   // 获取指定题目
   svr.Get(R"(/question/(\d+))", [](const Request &req, Response &resp)
           { 
               std::string number = req.matches[1];
               resp.set_content("获取指定题目 : " + number, "text/plain;charset=utf-8"); });
   
   // 用户提交代码,使用我们的判题功能 (1.测试用例 2.compile_run)
   svr.Get(R"(/judge/(\d+))", [](const Request &req, Response &resp) {
       
   });

   svr.listen("0.0.0.0", 8080);

   return 0;
}

5.2、第二个功能: Model 功能,提供对数据进行操作

oj_model.hpp文件:

#pragma once

#include <string>
#include <vector>
#include <fstream>
#include <unordered_map>

#include <cassert>
#include <cstdlib>

#include "../comm/utils.hpp"
#include "../comm/log.hpp"

namespace ns_model
{
   using namespace ns_log;
   using namespace ns_utils;
   struct Question
   {
       std::string number; // 题目编号
       std::string title;  // 题目标题
       std::string level;  // 题目等级
       int cpu_limit;      // 时间要求
       int mem_limit;      // 空间要求

       std::string desc;   // 题目描述
       std::string header; // 题目预设给的代码
       std::string tail;   // 测试用例代码,需要和header拼接再编译运行
   };

   const std::string questions_path = "./questions/questions.list"; // 题目列表配置文件
   const std::string question_path = "./questions/";                // 单个题目的路径

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

   public:
       Model()
       {
           assert(LoadAllQuestion(questions_path));
       }

       // 加载配置文件
       bool LoadAllQuestion(const std::string &question_list)
       {
           std::ifstream in(question_list);
           if (!in.is_open())
           {
               LOG(FATAL) << "加载题库失败,请检查是否有该题库" << "\n";
               return false;
           }
           std::string line;
           while (getline(in, line))
           {
               std::vector<std::string> tokens;
               StringUtil::StringSplit(line, &tokens, " ");
               if (tokens.size() != 5)
               {
                   // 1 回文数 简单 1 30000
                   // 没有读到五个数,有可能是写少了或者多了
                   LOG(WARNING) << "加载单个题目失败,请检查格式" << "\n";
                   continue;
               }
               Question q;
               q.number = tokens[0];
               q.title = tokens[1];
               q.level = tokens[2];
               q.cpu_limit = std::atoi(tokens[3].c_str());
               q.mem_limit = std::atoi(tokens[4].c_str());

               FileUtil::ReadFile(question_list + q.number + "desc.txt", &q.desc, true);
               FileUtil::ReadFile(question_list + q.number + "header.hpp", &q.header, true);
               FileUtil::ReadFile(question_list + q.number + "tail.hpp", &q.tail, true);

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

       // 获取所有题目列表
       bool GetAllQuestions(std::vector<Question> *out)
       {
           if (_questions.size() == 0)
           {
               LOG(ERROR) << "用户获取题库失败" << "\n";
               return false;
           }
           for (const auto &q : _questions)
           {
               out->push_back(q.second);
           }
           return true;
       }

       // 获取具体的一道题
       bool GetOneQuestion(const std::string &number, Question *out)
       {
           auto iter = _questions.find(number);
           if (iter == _questions.end())
           {
               LOG(ERROR) << "用户获取题目失败,题目编号:" << number << "\n";
               return false;
           }

           *out = iter->second;
           return true;
       }

       ~Model() {}
   };
}

5.3、第三个功能: Control 功能,逻辑控制模块

oj_control.hpp文件:

#pragma once

#include <iostream>
#include <jsoncpp/json/json.h>
#include <vector>
#include <mutex>
#include <unistd.h>
#include <fstream>
#include <cassert>
#include <algorithm>

// #include "oj_model.hpp"
#include "oj_model_MySQL.hpp"
#include "oj_view.hpp"
#include "../comm/log.hpp"
#include "../comm/utils.hpp"
#include "../comm/httplib.h"

namespace ns_control
{
   using namespace ns_model;
   using namespace ns_view;
   using namespace ns_log;
   using namespace ns_utils;
   using namespace httplib;

   // 提供编译运行服务的主机
   class Machine
   {
   public:
       std::string _ip;
       uint16_t _port;
       uint64_t _load;   // 负载量
       std::mutex *_mtx; // 对_load进行加锁

   public:
       Machine() : _ip(""), _port(0), _load(0), _mtx(nullptr) {}

       // 增加负载
       void IncLoad()
       {
           if (_mtx)
               _mtx->lock();
           ++_load;
           if (_mtx)
               _mtx->unlock();
       }

       // 降低负载
       void DecLoad()
       {
           if (_mtx)
               _mtx->lock();
           --_load;
           if (_mtx)
               _mtx->unlock();
       }

       // 读取load,没有太大意义,只是为了统一接口
       uint64_t Load()
       {
           uint64_t load = 0;
           if (_mtx)
               _mtx->lock();
           load = _load;
           if (_mtx)
               _mtx->unlock();
           return load;
       }

       // 负载清零,一台主机下线了,它的负载得清零
       void ResetLoad()
       {
           if (_mtx)
               _mtx->lock();
           _load = 0;
           if (_mtx)
               _mtx->unlock();
       }
       ~Machine() {}
   };

   const std::string machine_conf = "./conf/machine_list.conf";

   // 提供负载均衡选择主机
   class LoadBalance
   {
   private:
       std::vector<Machine> _machines; // 提供负载均衡的主机群 -- 以下表在标识每一台主机
       std::vector<int> _online;       // 在线的主机
       std::vector<int> _offline;      // 离线的主机
       std::mutex _mtx;                // 对负载均衡选择加锁,因为可能同时到来多个用户提交代码,而_machines等是临界资源(共享的)
   public:
       LoadBalance()
       {
           assert(LoadMachine(machine_conf));
           LOG(INFO) << "加载配置文件 " << machine_conf << " 成功" << "\n";
       }

       bool LoadMachine(const std::string &machine_file)
       {
           std::ifstream in(machine_file);
           if (!in.is_open())
           {
               LOG(FATAL) << "加载配置文件 " << machine_file << " 失败" << "\n";
               return false;
           }

           std::string line;
           while (getline(in, line))
           {
               std::vector<std::string> tokens;
               StringUtil::StringSplit(line, &tokens, ":");
               if (tokens.size() != 2)
               {
                   LOG(WARNING) << "加载配置行 " << line << " 失败" << "\n";
                   continue;
               }
               Machine m;
               m._ip = tokens[0];
               m._port = std::atoi(tokens[1].c_str());
               m._load = 0;
               m._mtx = new std::mutex();
               _online.push_back(_machines.size()); // 先把上线的主机的下标对应主机 0 <-> 第一台 ...
               _machines.push_back(m);
           }
           return true;
       }

       // 智能选择负载最小的主机
       bool SmartChioce(int *id, Machine **m)
       {
           _mtx.lock();

           // 负载均衡的算法
           // 1.随机+hash
           // 2.轮询+hash
           int num_online = _online.size(); // 在线的主机
           if (num_online == 0)
           {
               _mtx.unlock(); // 出去前先解锁
               LOG(FATAL) << "没有在线的主机,赶紧检查!" << "\n";
               return false;
           }

           uint64_t min_load = _machines[_online[0]].Load();
           *id = _online[0];
           *m = &_machines[_online[0]];
           for (int i = 1; i < num_online; ++i)
           {
               uint64_t cur_load = _machines[_online[i]].Load();
               if (cur_load < min_load)
               {
                   min_load = cur_load;
                   *id = _online[i];
                   *m = &_machines[_online[i]]; // 要这台主机的地址
               }
           }

           _mtx.unlock();
           return true;
       }

       void OnlineMachine()
       {
           _mtx.lock();
           // 把离线主机添加到在线主机
           _online.insert(_online.end(), _offline.begin(), _offline.end());
           _offline.erase(_offline.begin(), _offline.end());
           _mtx.unlock();
           LOG(INFO) << "所有主机上线啦!" << "\n";
       }

       // 让当前主机进入下线状态
       void OfflineMachine(int id)
       {
           _mtx.lock();
           for (auto iter = _online.begin(); iter != _online.end(); ++iter)
           {
               if (*iter == id)
               {
                   _machines[id].ResetLoad(); // 清空当前主机的负载
                   _online.erase(iter);
                   _offline.push_back(id);
                   break; // id唯一
               }
           }
           _mtx.unlock();
       }

       void ShowMachines()
       {
           std::cout << "当前在线的主机 : ";
           for (auto id : _online)
           {
               std::cout << id << " ";
           }
           std::cout << std::endl;

           std::cout << "当前离线线的主机 : ";
           for (auto id : _offline)
           {
               std::cout << id << " ";
           }
           std::cout << std::endl;
       }

       ~LoadBalance() {}
   };

   class Control
   {
   private:
       Model _model;              // 提供后台数据
       View _view;                // 提供html渲染
       LoadBalance _load_balance; // 提供核心负载均衡器

   public:
       Control() {}

       void RecoveryMachines()
       {
           _load_balance.OnlineMachine();
       }

       bool AllQuestions(std::string *html)
       {
           bool ret = true;
           // 获取所有题目
           std::vector<Question> all;
           if (_model.GetAllQuestions(&all))
           {
               // 渲染网页
               // 排序
               sort(all.begin(), all.end(), [](const Question &q1, const Question &q2)
                    { return std::atoi(q1.number.c_str()) < std::atoi(q2.number.c_str()); });
               _view.DrawAllQuestions(all, html);
           }
           else
           {
               *html = "加载题库失败";
               ret = false;
           }
           return ret;
       }
       bool OneQuestion(const std::string &number, std::string *html)
       {
           bool ret = true;
           Question q;
           if (_model.GetOneQuestion(number, &q))
           {
               // 渲染网页
               _view.DrawOneQuestion(q, html);
           }
           else
           {
               *html = "加载题目 " + number + " 失败";
               ret = false;
           }
           return ret;
       }

       void Judge(const std::string &number, const std::string &in_json, std::string *out_json)
       {
           // 0.根据题目编号,拿到题目细节
           Question q;
           _model.GetOneQuestion(number, &q);

           // 1.对in_json进行反序列化,得到code,input
           Json::Value in_value;
           Json::Reader reader;
           reader.parse(in_json, in_value);
           std::string code = in_value["code"].asCString();
           std::string input = in_value["input"].asCString();

           // 2.拼接代码,header+tail
           Json::Value compile_run_value;
           compile_run_value["code"] = code + "\n" + q.tail; // 记得加\n,不然可能存在编译的时候和ifndef COMPILE 共一行
           compile_run_value["input"] = input;
           compile_run_value["cpu_limit"] = q.cpu_limit;
           compile_run_value["mem_limit"] = q.mem_limit;
           Json::StyledWriter writer;
           std::string compile_run_string = writer.write(compile_run_value);

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

               // 4.发起http请求,得到结果
               Client cli(m->_ip, m->_port);
               m->IncLoad(); // 用的时候增加负载值
               LOG(INFO) << "连接主机成功 , 主机id : " << id << " 详情 : " << m->_ip << ":" << m->_port << " 负载 : " << m->Load() << "\n";

               // inline Result Client::Post(const char *path, const std::string &body, const char *content_type)
               auto res = cli.Post("/compile_run", compile_run_string, "application/json;charset=utf-8");
               if (res) // 编译运行
               {
                   if (res->status == 200)
                   {
                       // 请求成功
                       // 5.将结果给out_json
                       *out_json = res->body;
                       m->DecLoad(); // 用完后减少负载值
                       break;
                   }
                   m->DecLoad();
               }
               else
               {
                   // 请求失败
                   LOG(WARNING) << "当前请求的主机id : " << id << " 详情 : " << m->_ip << ":" << m->_port << " 可能已经离线" << "\n";
                   _load_balance.OfflineMachine(id); // 当前主机下线
                   _load_balance.ShowMachines();     // 用来测试
               }
               sleep(1);
           }
       }

       ~Control() {}
   };
}

5.4、第四个功能:View 功能,页面渲染模块

oj_view.hpp文件了:

#pragma once

#include <ctemplate/template.h>

#include <string>
#include <vector>

#include "oj_model.hpp"

namespace ns_view
{
   using namespace ns_model;

   const std::string draw_path = "./template_html/";
   class View
   {
   public:
       void DrawAllQuestions(std::vector<Question> &all, std::string *html)
       {
           std::string in_html = draw_path + "all_questions.html";
           // 形成数据字典
           ctemplate::TemplateDictionary root("all");

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

           // 获取被渲染的网页对象
           ctemplate::Template *tpl = ctemplate::Template::GetTemplate(in_html, ctemplate::DO_NOT_STRIP);

           // 添加字典数据到网页中
           tpl->Expand(html, &root);
       }

       void DrawOneQuestion(const Question &q, std::string *html)
       {
           std::string in_html = draw_path + "question.html";

           ctemplate::TemplateDictionary root("one");

           root.SetValue("number", q.number);
           root.SetValue("title", q.title);
           root.SetValue("level", q.level);

           root.SetValue("desc", q.desc);
           root.SetValue("pre_code", q.header);

           ctemplate::Template *tpl = ctemplate::Template::GetTemplate(in_html, ctemplate::DO_NOT_STRIP);

           tpl->Expand(html, &root);
       }
   };
}

六、基于 MySQL 版的 Model 功能

oj_model_MySQL.hpp文件:

#pragma once

#include <string>
#include <vector>
#include <fstream>
#include <unordered_map>

#include <cassert>
#include <cstdlib>

#include <mysql/mysql.h>

#include "../comm/utils.hpp"
#include "../comm/log.hpp"

namespace ns_model
{
   using namespace ns_log;
   using namespace ns_utils;
   struct Question
   {
       std::string number; // 题目编号 -- 唯一
       std::string title;  // 题目标题
       std::string level;  // 题目难度
       int cpu_limit;      // 时间要求
       int mem_limit;      // 空间要求

       std::string desc;   // 题目描述
       std::string header; // 题目预设给的代码
       std::string tail;   // 测试用例代码,需要和header拼接再编译运行
   };

   const std::string questions_table = "oj_questions"; // 数据库表名
   const std::string host = "127.0.0.1";
   const uint16_t port = 3306;
   const std::string passwd = "xp1014647664";
   const std::string db = "oj";
   const std::string user = "oj_client";

   class Model
   {
   private:
       bool MySQLQuery(const std::string &sql, std::vector<Question> *out)
       {
           // 创建句柄
           MYSQL *my = mysql_init(nullptr);

           // 连接数据库
           if (nullptr == mysql_real_connect(my, host.c_str(), user.c_str(), passwd.c_str(), db.c_str(), port, nullptr, 0))
           {
               LOG(FATAL) << "连接数据库失败!" << "\n";
               mysql_close(my); // 释放句柄
               return false;
           }

           LOG(INFO) << "连接数据库成功!" << "\n";

           mysql_set_character_set(my, "utf8");

           // 执行语句
           if (0 != mysql_query(my, sql.c_str()))
           {
               LOG(WARNING) << "执行语句 " << sql << " 失败!" << "\n";
               mysql_close(my); // 释放句柄
               return false;
           }

           // 提取结果
           MYSQL_RES *res = mysql_store_result(my);
           if (res == nullptr)
           {
               LOG(WARNING) << "查询未返回结果集" << "\n";
               mysql_close(my);
               return false;
           }

           // 获取行列数
           int rows = mysql_num_rows(res);

           // int cols = mysql_num_fields(res);

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

           mysql_free_result(res);
           mysql_close(my);
           return true;
       }

   public:
       Model() {}

       // 获取所有题目列表
       bool GetAllQuestions(std::vector<Question> *out)
       {
           std::string sql = "select * from ";
           sql += questions_table;
           return MySQLQuery(sql, out);
       }

       // 获取具体的一道题
       bool GetOneQuestion(const std::string &number, Question *out)
       {
           bool ret = false;
           std::string sql = "select * from ";
           sql += questions_table;
           sql += " where number=";
           sql += number;

           std::vector<Question> result;
           if (MySQLQuery(sql, &result))
           {
               if (result.size() == 1)
               {
                   *out = result[0];
                   return true;
               }
           }

           return ret;
       }

       ~Model() {}
   };
}

七、前端网页设计 html

主页:index.html

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

<head>
   <meta charset="UTF-8">
   <meta http-equiv="X-UA-Compatible" content="IE=edge">
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   <title>这是我的个人OJ系统</title>
   <style>
       /* 消除默认样式 */
       * {
           margin: 0;
           padding: 0;
           box-sizing: border-box;
       }

       html,
       body {
           width: 100%;
           height: 100%;
           font-family: 'Arial', sans-serif;
           background: linear-gradient(to bottom, #f0f4f8, #d9e2ec);
       }

       .container {
           min-height: 100vh;
           display: flex;
           flex-direction: column;
       }

       /* 导航栏样式 */
       .navbar {
           display: flex;
           justify-content: space-between;
           align-items: center;
           background-color: #333;
           padding: 0 20px;
           height: 60px;
       }

       /* 左侧导航菜单 */
       .navbar-left {
           display: flex;
       }

       .navbar-left a {
           color: white;
           font-size: 18px;
           text-decoration: none;
           padding: 14px 20px;
           transition: background-color 0.3s ease;
       }

       .navbar-left a:hover {
           background-color: #4CAF50;
           border-radius: 4px;
       }

       /* 右侧登录按钮 */
       .navbar-right .login {
           color: white;
           font-size: 18px;
           text-decoration: none;
           padding: 14px 20px;
           background-color: #4CAF50;
           border-radius: 4px;
           transition: background-color 0.3s ease;
       }

       .navbar-right .login:hover {
           background-color: #45a049;
       }

       /* 内容样式 */
       .content {
           flex: 1;
           display: flex;
           flex-direction: column;
           justify-content: center;
           align-items: center;
           text-align: center;
           padding: 20px;
       }

       .content h1 {
           font-size: 36px;
           color: #333;
           margin-bottom: 20px;
           letter-spacing: 2px;
       }

       .content p {
           font-size: 20px;
           color: #666;
           margin-bottom: 30px;
       }

       .content a {
           font-size: 22px;
           padding: 10px 25px;
           color: white;
           background-color: #4CAF50;
           border-radius: 5px;
           text-decoration: none;
           transition: background-color 0.3s ease;
       }

       .content a:hover {
           background-color: #45a049;
       }
   </style>
</head>

<body>
   <div class="container">
       <!-- 导航栏 -->
       <div class="navbar">
           <!-- 左侧导航链接 -->
           <div class="navbar-left">
               <a href="/">首页</a>
               <a href="/all_questions">题库</a>
               <a href="#">竞赛</a>
               <a href="#">讨论</a>
               <a href="#">求职</a>
           </div>
           <!-- 右侧登录按钮 -->
           <div class="navbar-right">
               <a class="login" href="#">登录</a>
           </div>
       </div>

       <!-- 内容区 -->
       <div class="content">
           <h1>欢迎来到我的Online Judge平台</h1>
           <p>这是一个我个人独立开发的在线OJ平台,享受编程的乐趣吧!</p>
           <a href="/all_questions">点击我开始编程啦!</a>
       </div>
   </div>
</body>

</html>

题目列表页:all_questions.html

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

<head>
   <meta charset="UTF-8">
   <meta http-equiv="X-UA-Compatible" content="IE=edge">
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   <title>在线OJ-题目列表</title>
   <style>
       /* 清除默认样式 */
       * {
           margin: 0;
           padding: 0;
           box-sizing: border-box;
       }

       body,
       html {
           font-family: 'Arial', sans-serif;
           height: 100%;
           background: linear-gradient(to right, #eef2f3, #8e9eab);
       }

       .container {
           display: flex;
           flex-direction: column;
           min-height: 100vh;
       }

       /* 导航栏样式 */
       .navbar {
           display: flex;
           justify-content: space-between;
           align-items: center;
           background-color: #333;
           padding: 0 20px;
           height: 60px;
       }

       /* 左侧导航菜单 */
       .navbar-left {
           display: flex;
       }

       .navbar-left a {
           color: white;
           font-size: 18px;
           text-decoration: none;
           padding: 14px 20px;
           transition: background-color 0.3s ease;
       }

       .navbar-left a:hover {
           background-color: #4CAF50;
           border-radius: 4px;
       }

       /* 右侧登录按钮 */
       .navbar-right .login {
           color: white;
           font-size: 18px;
           text-decoration: none;
           padding: 14px 20px;
           background-color: #4CAF50;
           border-radius: 4px;
           transition: background-color 0.3s ease;
       }

       .navbar-right .login:hover {
           background-color: #45a049;
       }

       /* 题目列表 */
       .question_list {
           flex: 1;
           padding: 50px 20px;
           display: flex;
           flex-direction: column;
           align-items: center;
       }

       .question_list h1 {
           font-size: 36px;
           color: #333;
           margin-bottom: 30px;
       }

       /* 表格样式 */
       table {
           width: 80%;
           border-collapse: collapse;
           background-color: white;
           box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
           border-radius: 10px;
           overflow: hidden;
       }

       th,
       td {
           padding: 15px;
           text-align: center;
           border-bottom: 1px solid #ddd;
       }

       th {
           background-color: #4CAF50;
           color: white;
           font-size: 18px;
       }

       tr:hover {
           background-color: #f2f2f2;
       }

       .item a {
           color: #333;
           text-decoration: none;
           transition: color 0.3s ease, text-decoration 0.3s ease;
       }

       .item a:hover {
           color: #4CAF50;
           text-decoration: underline;
       }

       /* 页脚 */
       .footer {
           height: 50px;
           background-color: #333;
           color: white;
           display: flex;
           justify-content: center;
           align-items: center;
       }

       .footer h4 {
           margin: 0;
       }
   </style>
</head>

<body>
   <div class="container">
       <!-- 导航栏 -->
       <div class="navbar">
           <!-- 左侧导航链接 -->
           <div class="navbar-left">
               <a href="/">首页</a>
               <a href="/all_questions">题库</a>
               <a href="#">竞赛</a>
               <a href="#">讨论</a>
               <a href="#">求职</a>
           </div>
           <!-- 右侧登录按钮 -->
           <div class="navbar-right">
               <a class="login" href="#">登录</a>
           </div>
       </div>

       <!-- 题目列表 -->
       <div class="question_list">
           <h1>OnlineJudge 题目列表</h1>
           <table>
               <tr>
                   <th class="item">编号</th>
                   <th class="item">标题</th>
                   <th class="item">难度</th>
               </tr>
               {{#question_list}}
               <tr>
                   <td class="item">{{number}}</td>
                   <td class="item"><a href="/question/{{number}}">{{title}}</a></td>
                   <td class="item">{{level}}</td>
               </tr>
               {{/question_list}}
           </table>
       </div>

       <!-- 页脚 -->
       <div class="footer">
           <h4>@Xpccccc</h4>
       </div>
   </div>
</body>

</html>

指定题目页:question.html

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

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

   <style>
       /* 清除默认样式 */
       * {
           margin: 0;
           padding: 0;
           box-sizing: border-box;
       }

       html,
       body {
           width: 100%;
           height: 100%;
           font-family: 'Arial', sans-serif;
           background-color: #f4f4f4;
       }

       .container {
           display: flex;
           flex-direction: column;
           min-height: 100vh;
       }

       /* 导航栏样式 */
       .navbar {
           display: flex;
           justify-content: space-between;
           align-items: center;
           background-color: #333;
           padding: 0 20px;
           height: 60px;
       }

       /* 左侧导航菜单 */
       .navbar-left {
           display: flex;
       }

       .navbar-left a {
           color: white;
           font-size: 18px;
           text-decoration: none;
           padding: 14px 20px;
           transition: background-color 0.3s ease;
       }

       .navbar-left a:hover {
           background-color: #4CAF50;
           border-radius: 4px;
       }

       /* 右侧登录按钮 */
       .navbar-right .login {
           color: white;
           font-size: 18px;
           text-decoration: none;
           padding: 14px 20px;
           background-color: #4CAF50;
           border-radius: 4px;
           transition: background-color 0.3s ease;
       }

       .navbar-right .login:hover {
           background-color: #45a049;
       }

       /* 左右呈现,题目描述和预设代码 */
       .part1 {
           display: flex;
           flex: 1;
           margin-top: 10px;
           padding: 20px;
       }

       .left_desc {
           flex: 1;
           padding: 20px;
           background-color: #ecf0f1;
           overflow-y: auto;
       }

       .left_desc h3 {
           font-size: 22px;
           color: #2c3e50;
           margin-bottom: 15px;
       }

       .left_desc pre {
           font-size: 16px;
           line-height: 1.5;
           white-space: pre-wrap;
       }

       .right_code {
           flex: 1;
           padding: 20px;
           background-color: #ecf0f1;
       }

       .right_code .ace_editor {
           width: 100%;
           height: 100%;
           border: 1px solid #bdc3c7;
           border-radius: 4px;
       }

       /* 提交代码和显示结果 */
       .part2 {
           display: flex;
           justify-content: space-between;
           align-items: center;
           padding: 15px 20px;
           background-color: #f4f4f4;
       }

       .result {
           flex: 1;
           margin-right: 20px;
       }

       .btn-submit {
           width: 120px;
           height: 50px;
           background-color: #26bb9c;
           color: white;
           border: none;
           border-radius: 5px;
           font-size: 18px;
           cursor: pointer;
           transition: background-color 0.3s;
       }

       .btn-submit:hover {
           background-color: #1abc9c;
       }

       .result pre {
           font-size: 16px;
           margin-top: 10px;
           white-space: pre-wrap;
           word-wrap: break-word;
       }
   </style>
</head>

<body>
   <div class="container">
       <!-- 导航栏 -->
       <div class="navbar">
           <!-- 左侧导航链接 -->
           <div class="navbar-left">
               <a href="/">首页</a>
               <a href="/all_questions">题库</a>
               <a href="#">竞赛</a>
               <a href="#">讨论</a>
               <a href="#">求职</a>
           </div>
           <!-- 右侧登录按钮 -->
           <div class="navbar-right">
               <a class="login" href="#">登录</a>
           </div>
       </div>

       <!-- 左右呈现,题目描述和预设代码 -->
       <div class="part1">
           <div class="left_desc">
               <h3><span id="number">{{number}}</span>. {{title}} - {{level}}</h3>
               <pre>{{desc}}</pre>
           </div>
           <div class="right_code">
               <pre id="code" class="ace_editor"><textarea class="ace_text-input">{{pre_code}}</textarea></pre>
           </div>
       </div>

       <!-- 提交并显示结果 -->
       <div class="part2">
           <div class="result"></div>
           <button class="btn-submit" onclick="submit()">提交代码</button>
       </div>
   </div>

   <script>
       var editor = ace.edit("code");
       editor.setTheme("ace/theme/monokai");
       editor.session.setMode("ace/mode/c_cpp");
       editor.setFontSize(16);
       editor.getSession().setTabSize(4);
       editor.setReadOnly(false);

       ace.require("ace/ext/language_tools");
       editor.setOptions({
           enableBasicAutocompletion: true,
           enableSnippets: true,
           enableLiveAutocompletion: true
       });

       function submit() {
           var code = editor.getSession().getValue();
           var number = $("#number").text();
           var judge_url = "/judge/" + number;

           $.ajax({
               method: 'POST',
               url: judge_url,
               dataType: 'json',
               contentType: 'application/json;charset=utf-8',
               data: JSON.stringify({
                   'code': code,
                   'input': ''
               }),
               success: function (data) {
                   show_result(data);
               }
           });

           function show_result(data) {
               var result_div = $(".container .part2 .result");
               result_div.empty();

               var reason_label = $("<p>", {
                   text: data.reason
               });
               reason_label.appendTo(result_div);

               if (data.status == 0) {
                   var stdout_label = $("<pre>", {
                       text: data.stdout
                   });
                   var stderr_label = $("<pre>", {
                       text: data.stderr
                   });

                   stdout_label.appendTo(result_div);
                   stderr_label.appendTo(result_div);
               }
           }
       }
   </script>
</body>

</html>

八、发布项目(Makefile)

使用顶层Makefile来操作所有能用到的make命令:

.PHONY:all
all:
	@cd compile_server;\
	make;\
	cd -;\
	cd oj_server;\
	make;\
	cd -;

.PHONY:output
output:
	@mkdir -p output/compile_server;\
	mkdir -p output/oj_server;\
	cp -rf compile_server/compile_server output/compile_server;\
	cp -rf compile_server/tmp output/compile_server;\
	cp -rf oj_server/conf output/oj_server;\
	cp -rf oj_server/questions output/oj_server;\
	cp -rf oj_server/template_html output/oj_server;\
	cp -rf oj_server/wwwroot output/oj_server;\
	cp -rf oj_server/oj_server output/oj_server;\

.PHONY:clean
clean:
	@cd compile_server;\
	make clean;\
	cd -;\
	cd oj_server;\
	make clean;\
	cd -;
	rm -rf output

九、项目整体效果演示

十、结束语 

今天内容就到这里啦,时间过得很快,大家沉下心来好好学习,会有一定的收获的,大家多多坚持,嘻嘻,成功路上注定孤独,因为坚持的人不多。那请大家举起自己的小手给博主一键三连,有你们的支持是我最大的动力💞💞💞,回见。

​​​ 

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

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

相关文章

银河麒麟桌面系统——桌面鼠标变成x,窗口无关闭按钮的解决办法

银河麒麟桌面系统——桌面鼠标变成x&#xff0c;窗口无关闭按钮的解决办法 1、支持环境2、详细操作说明步骤1&#xff1a;用root账户登录电脑步骤2&#xff1a;导航到kylin-wm-chooser目录步骤3&#xff1a;编辑default.conf文件步骤4&#xff1a;重启电脑 3、结语 &#x1f49…

数据结构--AVL树(平衡二叉树)

✅博客主页:爆打维c-CSDN博客​​​​​​ &#x1f43e; &#x1f539;分享c、c知识及代码 &#x1f43e; &#x1f539;Gitee代码仓库 五彩斑斓黑1 (colorful-black-1) - Gitee.com 一、AVL树是什么&#xff1f;&#xff08;含义、性质&#xff09; 1.AVL树的概念 AVL树是最…

【Unity How】如何让物体跟随平台移动或旋转?

先看下最终要实现的效果&#xff1a; 当查找这个问题的资料时&#xff0c;发现多数的方案都是将物体设置为平台的子对象。 但是如果平台是非均匀缩放时&#xff0c;物体在移动或旋转时就会发生变形。 参考&#xff1a;Unity中父对象是非均匀缩放时出现倾斜或剪切现象 那有没有…

C语言函数递归经典题型——汉诺塔问题

一.汉诺塔问题介绍 Hanoi&#xff08;汉诺&#xff09;塔问题。古代有一个梵塔&#xff0c;塔内有3个座A、B、C&#xff0c;开始时&#xff21;座上有64个盘子&#xff0c;盘子大小不等&#xff0c;大的在下&#xff0c;小的在上。有一个老和尚想把这64个盘子从&#xff21;座移…

Unity中动态生成贴图并保存成png图片实现

实现原理&#xff1a; 要生成长x宽y的贴图&#xff0c;就是生成x*y个像素填充到贴图中&#xff0c;如下图&#xff1a; 如果要改变局部颜色&#xff0c;就是从x1到x2(x1<x2),y1到y2(y1<y2)这个范围做处理&#xff0c; 或者要想做圆形就是计算距某个点&#xff08;x1,y1&…

vue3封装Element Plus table表格组件

支持绝大部分Element Plus原有设置属性&#xff0c;支持分页&#xff0c;支持动态适配高度 效果展示 组件代码&#xff1a; <template><div class"table-wrap" ref"tableWrap"><el-tableclass"w100 h100":data"tableInfo.…

ElasticSearch学习篇18_《检索技术核心20讲》LevelDB设计思想

目录 一些常见的设计思想以及基于LSM树的LevelDB是如何利用这些设计思想优化存储、检索效率的。 几种常见的设计思想 索引和数据分离减少磁盘IO读写分离分层思想 LevelDB的设计思想 读写分离设计分层设计与延迟合并LRU缓存加速检索 几种常见设计思想 索引与数据分离 索引…

路由器中继与桥接

一 . 背景 现在的路由器大多数已经开始支持多种网络连接模式&#xff0c;以下将以TP-Link迷你无线路由器为例进行展开介绍。在TP-Link迷你无线路由器上一般有AP&#xff08;接入点&#xff09;模式&#xff0c;Router&#xff08;无线路由&#xff09;模式&#xff0c;Repeate…

Bitcoin---P2SH;P2SH举例;P2SH的局限性

文章目录 1. 摘要2. P2SH举例3. P2SH局限性 1. 摘要 Pay-to-Script-Hash (P2SH) 交易输出的开发是为了简化更复杂和功能性脚本的使用&#xff0c;就像需要提供相应签名才能解锁的支付脚本&#xff08;即 P2PKH 脚本&#xff09;一样简单。为了轻松使用此类兑换脚本&#xff0c…

鸿蒙NEXT开发案例:文字转拼音

【引言】 在鸿蒙NEXT开发中&#xff0c;文字转拼音是一个常见的需求&#xff0c;本文将介绍如何利用鸿蒙系统和pinyin-pro库实现文字转拼音的功能。 【环境准备】 • 操作系统&#xff1a;Windows 10 • 开发工具&#xff1a;DevEco Studio NEXT Beta1 Build Version: 5.0.…

ffmpeg视频滤镜:提取缩略图-framestep

滤镜描述 官网地址 > FFmpeg Filters Documentation 这个滤镜会间隔N帧抽取一帧图片&#xff0c;因此这个可以用于设置视频的缩略图。总体上这个滤镜比较简单。 滤镜使用 滤镜参数 framestep AVOptions:step <int> ..FV....... set frame st…

eclipse-git项目提示NO-HEAD

1、出现该问题的过程 本人在用eclipse拉取git代码&#xff0c;刚拉取完&#xff0c;可能还没来得及跟本地的分支合并&#xff0c;电脑就卡动了。无奈只能重启电脑&#xff0c;打开eclipse&#xff0c;maven项目后面就出现了xxx NO-HEAD的提示。 2、问题解决 根据错误提示&am…

力扣hot100-->排序

排序 1. 56. 合并区间 中等 以数组 intervals 表示若干个区间的集合&#xff0c;其中单个区间为 intervals[i] [starti, endi] 。请你合并所有重叠的区间&#xff0c;并返回 一个不重叠的区间数组&#xff0c;该数组需恰好覆盖输入中的所有区间 。 示例 1&#xff1a; 输…

鱼眼相机模型-MEI

参考文献&#xff1a; Single View Point Omnidirectional Camera Calibration from Planar Grids 1. 相机模型如下&#xff1a; // 相机坐标系下的点投影到畸变图像// 输入&#xff1a;相机坐标系点坐标cam 输出&#xff1a; 畸变图像素点坐标disPtvoid FisheyeCamAdapter::…

Reactor 模式的理论与实践

1. 引言 1.1 什么是 Reactor 模式&#xff1f; Reactor 模式是一种用于处理高性能 I/O 的设计模式&#xff0c;专注于通过非阻塞 I/O 和事件驱动机制实现高并发性能。它的核心思想是将 I/O 操作的事件分离出来&#xff0c;通过事件分发器&#xff08;Reactor&#xff09;将事…

windows下安装wsl的ubuntu,同时配置深度学习环境

写在前面&#xff0c;本次文章只是个人学习记录&#xff0c;不具备教程的作用。个别信息是网上的&#xff0c;我会标注&#xff0c;个人是gpt生成的 安装wsl 直接看这个就行&#xff1b;可以不用备份软件源。 https://blog.csdn.net/weixin_44301630/article/details/1223900…

深入探索 CnosDB 可观测性最佳实践:开篇

随着云计算、微服务、容器化和 DevOps 等技术的迅猛发展&#xff0c;现代软件系统变得愈加复杂和动态。传统的监控手段已经无法满足对系统状态的全面、实时、准确地了解。在这样的背景下&#xff0c;可观测性&#xff08;Observability&#xff09;作为一种新兴的技术理念应运而…

World of Warcraft /script SetRaidTarget(“target“, n, ““) n=8,7,6,5,4,3,2,1,0

魔兽世界执行当前目标标记方法 /script SetRaidTarget("target", n, "") n8,7,6,5,4,3,2,1,0 解析这个lua脚本 D:\Battle.net\World of Warcraft\_classic_\Interface\AddOns\wMarker wMarker.lua /script SetRaidTarget("target", 8, &quo…

[极客大挑战 2019]BabySQL--详细解析

信息搜集 进入界面&#xff1a; 输入用户名为admin&#xff0c;密码随便输一个&#xff1a; 发现是GET传参&#xff0c;有username和password两个传参点。 我们测试一下password点位能不能注入&#xff1a; 单引号闭合报错&#xff0c;根据报错信息&#xff0c;我们可以判断…

信创改造 - TongRDS 替换 Redis

记得开放 6379 端口哦 1&#xff09;首先在服务器上安装好 TongRDS 2&#xff09;替换 redis 的 host&#xff0c;post&#xff0c;passwd 3&#xff09;TongRDS 兼容 jedis # 例如&#xff1a;更改原先 redis 中对应的 host&#xff0c;post&#xff0c;passwd 改成 TongRDS…