您的位置:首页 > 房产 > 建筑 > 乌镇旅游攻略自由行_校园推广app_软文100字左右案例_企业培训课程价格

乌镇旅游攻略自由行_校园推广app_软文100字左右案例_企业培训课程价格

2025/1/14 10:24:06 来源:https://blog.csdn.net/moran114/article/details/143996760  浏览:    关键词:乌镇旅游攻略自由行_校园推广app_软文100字左右案例_企业培训课程价格
乌镇旅游攻略自由行_校园推广app_软文100字左右案例_企业培训课程价格

OnlineJudge

  • 前言
    • 所用技术
    • 开发环境
  • 1. 需求分析
  • 2. 项目宏观结构
  • 3. compile_server服务设计
    • 3.1 compiler服务设计
    • 3.2 runner服务设计
    • 3.3 compile_run
    • 3.4 compile_server.cpp
  • 4. oj_server服务设计
    • 4.1 model设计
    • 4.2 view设计
    • 4.3 control设计
      • 4.3.1 获取题目列表功能
      • 4.3.2 获取单个题目详情页
      • 4.3.3 判题功能
    • oj_server.cpp
  • 5. 项目扩展

前言

此项目是仿leetcode实现在线OJ功能的,只实现类似leetcode的题目列表+在线编程功能
主要聚焦于后端设计,前端仅仅实现其功能即可

所用技术

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

开发环境

Ubuntu0.22.04.1 云服务器
vscode
Mysql Workbench

1. 需求分析

  1. 用户能够查看题目列表
  2. 用户能够看到题目详细信息
  3. 用户能够编写代码并提交测试,测试结果返回给用户

2. 项目宏观结构

我们的项目核心是三个模块

  1. comm : 公共模块
  2. compile_server : 编译与运行模块
  3. oj_server : 获取题目列表,查看题目编写题目界面,负载均衡,其他功能
    在这里插入图片描述

采用BS模式,浏览器访问后端服务器
在这里插入图片描述

用户的请求将通过oj_server来进行处理,如果是访问题目的请求,会访问文件或者数据库,如果是编译与运行服务会下放到负责此功能的主机,实现功能解耦。

3. compile_server服务设计

compile_server模块分为:compilerrunnercompile_run
compiler:负责代码的编译服务
runner :负责代码的运行服务
compile_run:负责接收要处理的服务并将编译运行的结果处理成格式化结果返回
在这里插入图片描述

3.1 compiler服务设计

提供的服务:编译代码,得到编译的结果

  • 对于接收的代码,创建代码的临时文件,以供编译
  • 代码编译后,如果编译错误,将错误信息存入一个文件,如果编译正确,则会生成可执行文件
  • 可预见的:在运行时也需要生成很多临时文件存储标准输入、标准输出、标准错误

所以,我们需要一个临时目录来存放这些临时文件,且临时文件名不能重复

我们统一命名这些临时文件:时间戳_num.xxx
在创建临时文件时,需要先获取时间戳和num,num是一个原子性的计数器,线程安全,这样就能保证所有的文件都是不同名的

于是:

  1. 源文件名:时间戳_num.src
  2. 可执行文件名:时间戳_num.exe
  3. 编译错误文件名:时间戳_num.compile_err

在下一个模块中,也是同样的命名规范

#pragma once#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <iostream>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/wait.h>#include "../comm/util.hpp"
#include "../comm/log.hpp"namespace ns_compiler
{using namespace ns_util;using namespace ns_log;class Compiler{public://file_name: 不包含文件后缀和路径,只是文件名static bool Compile(const std::string& file_name){pid_t pid = fork();if(pid < 0){LOG(ERROR) << "创建子进程失败" << std::endl;return false;}if(pid == 0){//子进程std::string compile_err_name = PathUtil::CompileError(file_name);//设置权限mask,消除平台差异umask(0);//以写方式打开文件,若是新文件,权限为rw|r|rint file_compile_error = open(compile_err_name.c_str(),O_CREAT | O_WRONLY, 0644); if(file_compile_error == -1){LOG(ERROR) << "打开compile_error文件失败" << std::endl;exit(1);}//标准错误重定向到compile_err_name文件中//如果oldfd打开且合法,就不会出错dup2(file_compile_error,2);//子进程替换为g++,编译源文件execlp("g++","g++", "-o", PathUtil::Exec(file_name).c_str(),\PathUtil::Src(file_name).c_str(),"-D", "ONLINE_COMPILE" ,"--std=c++11", nullptr);LOG(ERROR) << "进程替换失败" << std::endl;exit(2);}else{//父进程waitpid(pid,nullptr,0);//如果没有形成可执行文件,表示编译出错if( !FileUtil::IsExistPathName(PathUtil::Exec(file_name)) ){LOG(INFO) << "代码编译错误" << std::endl;return false;}}LOG(INFO) << "代码编译成功" << std::endl;return true;}};
} // namespace ns_compile

开放式日志方法LOG

#pragma once#include <iostream>
#include <string>#include "util.hpp"namespace ns_log{using namespace ns_util;    enum{INFO, //提示信息DEBUG, //调试信息WARNING, //警告,不影响系统ERROR, //错误,影响系统但是系统依旧能提供服务FATAL // 致命错误,系统崩溃,无法提供服务};//开发式日志:[level][file][line]+ 其他信息inline std::ostream& Log(const std::string& level,const std::string& file_name,int line){std::string message = "[";message += level;message += "]";message += "[";message += file_name;message += "]";message += "[";message += std::to_string(line);message += "]"; message += "[";message += std::to_string(TimeUtil::GetTimeStamp());message += "]"; std::cout << message;return std::cout;}//在预处理中,#是字符串化操作符,可以直接将宏参数转换为字符串字面量//__FILE__宏,在编译时直接替换为文件名//__LINE__宏,在编译时直接替换为代码行数#define LOG(level) Log(#level,__FILE__,__LINE__)
}

3.2 runner服务设计

提供的服务:运行编译好的可执行文件,得到程序的结果

临时文件:

  1. 标准输入文件名:时间戳_num.stdin
  2. 标准输出文件名:时间戳_num.stdout
  3. 标准错误文件名:时间戳_num.stderr

运行可执行文件有三种结果:

  1. 运行失败
  2. 运行成功,结果正确
  3. 运行成功,结果错误

对于runner模块,我们并不考虑程序结果正确与否,因为要达到功能解耦,我们只关心程序是否运行成功

所以运行是否成功也有三种情况:

  1. 运行失败,系统或者其他原因 – 不需要让用户知道,例如:创建进程失败,代码为空等
  2. 运行失败,代码出错 – 需要让用户知道,例如:野指针、时间复杂度过大等
  3. 运行成功

进程运行时崩溃一定是被信号杀掉的,所以我们获取进程退出的状态码中的信号标识位,可得到运行失败原因

#pragma once#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <sys/resource.h>#include "../comm/log.hpp"
#include "../comm/util.hpp"namespace ns_runner{using namespace ns_log;using namespace ns_util;//只考虑程序能否运行成功,不考虑结果class Runner{public:/****** 程序运行情况:* 1.系统自身发生错误---返回负数* 2.程序被信号杀掉了---返回信号* 3.程序运行成功--- 返回0*******///设置时间空间限制static void SetProcLimit(int _cpu_limit,int _mem_limit){struct rlimit cpu_limit;cpu_limit.rlim_cur = _cpu_limit;cpu_limit.rlim_max = RLIM_INFINITY;setrlimit(RLIMIT_CPU, &cpu_limit);struct rlimit mem_limit;mem_limit.rlim_max = RLIM_INFINITY;mem_limit.rlim_cur = _mem_limit*1024;setrlimit(RLIMIT_AS,&mem_limit);}//cpu单位:s, memory单位:kbstatic int Run(const std::string& file_name,int cpu,int memory){std::string _exe_name = PathUtil::Exec(file_name);std::string _stdin_name = PathUtil::Stdin(file_name);std::string _stdout_name = PathUtil::Stdout(file_name);std::string _stderr_name = PathUtil::Stderr(file_name);umask(0);int _stdin_fd = open(_stdin_name.c_str(),O_CREAT | O_RDONLY,0644);int _stdout_fd = open(_stdout_name.c_str(),O_CREAT | O_WRONLY ,0644);int _stderr_fd = open(_stderr_name.c_str(),O_CREAT | O_WRONLY, 0644);if(_stdin_fd == -1 || _stdout_fd == -1 || _stderr_fd == -1){LOG(ERROR) << "打开标准文件错误" << std::endl;return -1; //代表打开文件失败}pid_t pid = fork();if(pid < 0){LOG(ERROR) << "创建子进程失败" << std::endl;close(_stdin_fd);close(_stdout_fd);close(_stderr_fd);return -2;}if(pid == 0){//子进程dup2(_stdin_fd,0);dup2(_stdout_fd,1);dup2(_stderr_fd,2);SetProcLimit(cpu,memory);execl(_exe_name.c_str(),_exe_name.c_str(),nullptr);exit(1);}else{//父进程close(_stdin_fd);close(_stdout_fd);close(_stderr_fd);int st;//程序运行异常一定是收到了信号waitpid(pid,&st,0);LOG(INFO) << "运行完毕, info: " << (st & 0x7F) << "\n"; return 0x7f & st;}}};
}

3.3 compile_run

功能:获取输入,编译运行,提供格式化输出

此处输入输出需要序列化与反序列化,我们采用jsoncpp第三方库来完成

输入格式:

{"code" : "", //需要编译运行代码"input" : "", //用户直接提供的测试代码"cpu_limit" : *, //时间限制,单位s"mem_limit" : * //空间限制,单位kb
}

输出格式

{"reason" : "", //状态码对应的信息"status" : *, //状态码,0标识运行成功,>0代码运行异常,<0系统或者其他导致运行失败//如果状态码为0,运行成功才有stdout和stderr"stdout" : "", "stderr" : 
}

compile_run

#pragma once#include "compiler.hpp"
#include "runner.hpp"
#include "../comm/log.hpp"
#include "../comm/util.hpp"#include <jsoncpp/json/json.h>namespace compile_run{using namespace ns_log;using namespace ns_util;using namespace ns_compiler;using namespace ns_runner;    class CompileAndRun{public:static void RemoveFile(const std::string& file_name){std::string exe_path = PathUtil::Exec(file_name);if(FileUtil::IsExistPathName(exe_path))unlink(exe_path.c_str());std::string src_path = PathUtil::Src(file_name);if(FileUtil::IsExistPathName(src_path))unlink(src_path.c_str());std::string compile_err_path = PathUtil::CompileError(file_name);if(FileUtil::IsExistPathName(compile_err_path))unlink(compile_err_path.c_str());std::string stdin_path = PathUtil::Stdin(file_name);if(FileUtil::IsExistPathName(stdin_path))unlink(stdin_path.c_str());std::string stderr_path = PathUtil::Stderr(file_name);if(FileUtil::IsExistPathName(stderr_path))unlink(stderr_path.c_str());std::string stdout_path = PathUtil::Stdout(file_name);if(FileUtil::IsExistPathName(stdout_path))unlink(stdout_path.c_str());}static std::string GetReason(int status,const std::string& file_name){std::string message;switch(status){case 0:message = "运行成功!";break;case -1:message = "代码为空";break;case -2:message = "未知错误";break;case -3:FileUtil::ReadFromFile(PathUtil::CompileError(file_name),&message);break;case SIGFPE:message = "浮点数溢出";break;case SIGXCPU:message = "运行超时";break;case SIGABRT:message = "内存超过范围";break;default:message = "未能识别此错误:[";message += std::to_string(status);message += ']';break;}return message;}/************************** 接受的json的格式:* code:代码* input:输入* cpu_limit:cpu限制 s* mem_limit:内存限制 kb* * 发送的json格式:* 必填:* status:状态码* reason:请求结果* 选填:* stdout:程序输出结果* stderr:程序运行完的错误信息*/static void Start(const std::string& in_json,std::string* out_json){Json::Value in_root;Json::Reader read;read.parse(in_json,in_root);//获取输入std::string code = in_root["code"].asString();std::string input = in_root["input"].asString();int cpu_limit = in_root["cpu_limit"].asInt();int mem_limit = in_root["mem_limit"].asInt();int status = 0;//运行编译的总状态码int run_st = 0; //程序运行返回的状态码std::string file_name;if(code.size() == 0){status = -1; //代码为空goto END;}//得到一个唯一的文件名file_name = FileUtil::GetUniqeFileName();if(!FileUtil::WriteToFile(PathUtil::Src(file_name),code) || !FileUtil::WriteToFile(PathUtil::Stdin(file_name),input)){status = -2; //未知错误goto END;}if(!Compiler::Compile(file_name)){status = -3; //编译错误goto END;}run_st = Runner::Run(file_name,cpu_limit,mem_limit);if(run_st < 0){status = -2; //未知错误}else if(run_st > 0){// 程序运行崩溃status = run_st;}else{//程序运行成功status = 0;}END:Json::Value out_root;std::string reason = GetReason(status,file_name);out_root["reason"] = reason;out_root["status"] = status;if(status == 0){std::string stdout_mes;std::string stderr_mes;FileUtil::ReadFromFile(PathUtil::Stdout(file_name),&stdout_mes);FileUtil::ReadFromFile(PathUtil::Stderr(file_name),&stderr_mes);out_root["stdout"] = stdout_mes;out_root["stderr"] = stderr_mes;}Json::StyledWriter writer;if(out_json){*out_json = writer.write(out_root);}//移除临时文件RemoveFile(file_name);}};
}

3.4 compile_server.cpp

主要用来提供网络服务,接收http请求,并响应结果返回
采用了cpp-httplib第三方库

#include "compile_run.hpp"
#include "../comm/httplib.h"using namespace compile_run;
using namespace httplib;void Usage(const std::string& proc)
{std::cout << "Usage:" << proc << ' ' << "port" << std::endl;
}int main(int argc,char* argv[])
{if(argc != 2){Usage(argv[0]);exit(1);}Server svr;svr.Post("/compile_and_run",[](const Request& req,Response& resp){std::string in_json = req.body;std::string out_json;if(in_json.empty())return;CompileAndRun::Start(in_json,&out_json);resp.set_content(out_json,"application/json;charset=utf-8");});svr.listen("0.0.0.0", atoi(argv[1]));return 0;
}

4. oj_server服务设计

oj_server采用MVC模式,分为:modelviewcontrol

  • model:用来与底层数据交互
  • view:用来处理用户视图,即前端页面
  • control:统筹modelview实现业务逻辑

4.1 model设计

对于在线OJ平台,最重要的数据就是题目

题目的设计:

struct Question{std::string _id; //题目编号std::string _title; //题目标题std::string _difficulty; //题目难度std::string _desc; //题目描述std::string _prev_code; //预设给用户的代码std::string _test_code; //测试用例int _cpu_limit; //时间限制int _mem_limit; //空间限制
};

对于题目的存储,我们可以采用文件版,也可以采用数据库版,这里我们就采用数据库版

采用第三方库mysql C API,需要去mysql官网下载

model2代码

#pragma once#include <string>
#include <vector>
#include <iostream>
#include <mysql/mysql.h>#include "../comm/log.hpp"
#include "../comm/util.hpp"namespace ns_model{using namespace ns_log;using namespace ns_util;struct Question{std::string _id;std::string _title;std::string _difficulty;std::string _desc;std::string _prev_code;std::string _test_code;int _cpu_limit;int _mem_limit;};std::string table_name = "题目表名";std::string ip = "mysql服务端ip";std::string user_name = "用户名";std::string password = "密码";std::string database = "数据库名字";uint32_t port = 3306;/*数据库端口号*/class Model{public:bool QueryMysql(const std::string& sql,std::vector<Question>* questions){//创建与mysql的连接MYSQL* mysql = mysql_init(nullptr);if(!mysql_real_connect(mysql,ip.c_str(),user_name.c_str(),password.c_str(),database.c_str(),port,nullptr,0)){LOG(DEBUG) << "连接数据库失败" << "\n";return false;}mysql_set_character_set(mysql,"utf8");//执行sql语句if(mysql_query(mysql,sql.c_str())){LOG(DEBUG) << "查询数据库失败" << "\n";mysql_close(mysql);return false;}//判断是否有结果MYSQL_RES* result = mysql_store_result(mysql);if(!result){if (mysql_field_count(mysql) == 0) {LOG(DEBUG) << "查询执行成功,但无返回结果(可能是非 SELECT 查询)" << "\n";} else {LOG(DEBUG) << "查询数据库失败:" << mysql_error(mysql) << "\n";}mysql_close(mysql);return false;}//逐行获取结果MYSQL_ROW fields;while((fields = mysql_fetch_row(result)) != nullptr){Question q;q._id = fields[0] ? fields[0] : "";q._title = fields[1] ? fields[1] : "";q._difficulty = fields[2] ? fields[2] : "";q._desc = fields[3] ? fields[3] : "";q._prev_code = fields[4] ? fields[4] : "";q._test_code = fields[5] ? fields[5] : "";q._cpu_limit = fields[6] ? atoi(fields[6]) : 0;q._mem_limit = fields[7] ? atoi(fields[7]) : 0;questions->push_back(q);}//记得关闭句柄和释放结果mysql_free_result(result);mysql_close(mysql);return true;}bool GetAllQuestions(std::vector<Question>* questions){std::string sql = "select id,title,difficulty,`desc`,prev_code,test_code,cpu_limit,mem_limit from ";sql += table_name;if(!QueryMysql(sql,questions))return false;return true;}bool GetOneQuestion(const std::string& id,Question* quest){std::string sql = "select id,title,difficulty,`desc`,prev_code,test_code,cpu_limit,mem_limit from ";sql += table_name;sql += " where id = ";sql += id;std::vector<Question> vq;if(!QueryMysql(sql,&vq) || vq.size() != 1)return false;*quest = vq[0];return true;}};
}

4.2 view设计

#pragma once#include <vector>
#include <ctemplate/template.h>#include "oj_model2.hpp"namespace ns_view{using namespace ns_model;using namespace ctemplate;static const std::string html_path = "./template_html/";static const std::string all_questions_html = "all_questions.html";static const std::string one_question_html = "one_question.html";class View{public:void ShowAllQuestion(const std::vector<Question>& questions,std::string* html){if(!html) return;TemplateDictionary root("all_questions");for(const auto& q : questions){TemplateDictionary* row_dict = root.AddSectionDictionary("question_list");row_dict->SetValue("id",q._id);row_dict->SetValue("title",q._title);row_dict->SetValue("difficulty",q._difficulty);}Template* tpl = Template::GetTemplate(html_path+all_questions_html,DO_NOT_STRIP);tpl->Expand(html,&root);}void ShowOneQuestion(const Question& quest,std::string* html){if(!html) return;TemplateDictionary root("one_question");root.SetValue("id",quest._id);root.SetValue("prev_code",quest._prev_code);root.SetValue("title",quest._title);root.SetValue("difficulty",quest._difficulty);root.SetValue("desc",quest._desc);Template* tpl = Template::GetTemplate(html_path+one_question_html,DO_NOT_STRIP);tpl->Expand(html,&root);}};
}

总共分为三个页面呈现给用户:

  1. OJ主页
  2. 题目列表
  3. 题目详情页即代码编辑区

4.3 control设计

4.3.1 获取题目列表功能

bool AllQuestions(std::string* html)
{if(!html) return false;std::vector<Question> questions;if(!_model.GetAllQuestions(&questions)){LOG(ERROR) << "用户读取所有题目失败" << '\n';return false;}std::sort(questions.begin(),questions.end(),[](const Question& q1,const Question& q2){return stoi(q1._id) < stoi(q2._id);});_view.ShowAllQuestion(questions,html);return true;
}

4.3.2 获取单个题目详情页

bool OneQuestion(const std::string& id,std::string* html)
{if(!html) return false;Question quest;if(!_model.GetOneQuestion(id,&quest)){LOG(WARNING) << "用户读取题目[" << id << "]失败" << '\n';return false;}_view.ShowOneQuestion(quest,html);return true;
}

4.3.3 判题功能

判题功能设计到的内容较多,包括负载均衡选择负载较少的主机、主机的下线等

主机类:

//这个主机类要注意,在loadblance里会进行拷贝操作,如果实现了析构函数释放锁空间会出现问题,即二次释放
struct Machine{std::string _ip;int _port;uint64_t _load;std::mutex* _mtx;Machine(const std::string& ip,int port):_ip(ip),_port(port),_load(0),_mtx(new std::mutex){}void IncLoad(){_mtx->lock();_load++;_mtx->unlock();}void DecLoad(){_mtx->lock();_load--;_mtx->unlock();}size_t Load(){uint64_t load;_mtx->lock();load = _load;_mtx->unlock();return load;}void ResetLoad(){_mtx->lock();_load =0;_mtx->unlock();}};

对于负载的修改,必须要保证线程安全
由于c++标准库中的mutex是不允许拷贝的,后序有涉及到主机的拷贝,所以存储锁的指针,而不是锁本身

负载均衡类

const std::string machines_conf = "./cnf/service_machine.conf";class LoadBlance{bool LoginMachines(){std::ifstream in(machines_conf);if(!in.is_open()){LOG(FATAL) << "未能读取判题服务器配置文件" << "\n";return false;}std::string line;while(getline(in,line)){std::vector<std::string> tokens;StringUtil::SplitString(line,&tokens,":");if(tokens.size() != 2){LOG(WARNING) << "某个判题服务器配置出错" << "\n";continue;}Machine mac(tokens[0],stoi(tokens[1]));_onlines.push_back(_machines.size());_machines.push_back(mac);}in.close();return true;}
public:LoadBlance(){assert(LoginMachines());LOG(INFO) << "加载主机成功" << "\n";}//将下线主机全部上线策略void OnlineMachines(){_mtx.lock();_onlines.insert(_onlines.end(),_offlines.begin(),_offlines.end());_offlines.clear();_mtx.unlock();LOG(INFO) << "所有主机上线成功" << std::endl;}void OfflineMachine(int which){_mtx.lock();std::vector<int>::iterator it = _onlines.begin();while(it != _onlines.end()){if(*it == which){_machines[which].ResetLoad();_offlines.push_back(which);_onlines.erase(it);break;}it++;}_mtx.unlock();}bool SmartChoice(int* pnumber,Machine** ppmac){//轮询+hash_mtx.lock();if(_onlines.size() == 0){_mtx.unlock();LOG(FATAL) << "没有在线主机,请尽快修复" << "\n";return false;}std::vector<int>::iterator it = _onlines.begin();int min_machine_index = 0;while(it != _onlines.end()){if(_machines[*it].Load() < _machines[min_machine_index].Load()){min_machine_index = *it;}it++;}*pnumber = min_machine_index;*ppmac = &_machines[min_machine_index];_mtx.unlock();return true;}//仅仅为了调试void ShowMachines(){_mtx.lock();std::cout << "当前在线主机列表: ";for(auto &id : _onlines){std::cout << id << " ";}std::cout << std::endl;std::cout << "当前离线主机列表: ";for(auto &id : _offlines){std::cout << id << " ";}std::cout << std::endl;_mtx.unlock();}
private:std::vector<Machine> _machines;std::vector<int> _onlines;std::vector<int> _offlines;std::mutex _mtx;
};

判题功能:

void Judge(const std::string& question_id,const std::string& in_json,std::string* out_json)
{if(!out_json) return;//先得到此题信息Question quest;if(!_model.GetOneQuestion(question_id,&quest)) return;Json::Reader reader;Json::Value root;reader.parse(in_json,root);std::string prev_code = root["code"].asString();std::string input = root["input"].asString();//构建编译运行的json串Json::Value compile_root;//一定要加\n,如果不加会导致test_code.cpp里的条件编译和prev_code.cpp的代码连在一起,以至于无法消除条件编译compile_root["code"] = prev_code + "\n" +quest._test_code;compile_root["input"] = input;compile_root["cpu_limit"] = quest._cpu_limit;compile_root["mem_limit"] = quest._mem_limit;Json::StyledWriter writer;std::string judge_json = writer.write(compile_root);//负载均衡的选择主机进行判题任务int id;Machine* m;while(true){if(!_load_blance.SmartChoice(&id,&m)){break;}m->IncLoad();httplib::Client client(m->_ip,m->_port);LOG(INFO) << "选择主机成功,主机id: " << id << " 详情: " << m->_ip << ":" << m->_port << " 当前主机的负载是: " << m->Load() << "\n";if(auto res = client.Post("/compile_and_run",judge_json,"application/json;charset=utf-8")){if(res->status = 200){*out_json = res->body;LOG(INFO) << "请求编译运行服务成功" << '\n';    m->DecLoad();break;}m->DecLoad();}else{LOG(WARNING) << "请求主机[" << id << "]" << "可能已下线" << '\n';_load_blance.OfflineMachine(id);_load_blance.ShowMachines();}}
}

oj_server.cpp

主要完成网络服务,路由功能

#include <iostream>
#include <jsoncpp/json/json.h>#include "../comm/httplib.h"
#include "oj_control.hpp"using namespace httplib;void Usage(const std::string& proc)
{std::cout << "Usage:" << proc << ' ' << "port" << std::endl;
}using namespace ns_control;Control* ptr_ctrl = nullptr;void Recovery(int signo)
{ptr_ctrl->Recovery();    
}int main(int argc,char* argv[])
{if(argc != 2){Usage(argv[0]);exit(1);}//设置某个信号,当对oj_server发出这个信号时,负载均衡将重启所有主机signal(SIGQUIT, Recovery);Server svr;Control ctrl;ptr_ctrl = &ctrl;//路由功能svr.Get("/all_questions", [&ctrl](const Request& req,Response& resp){//获取所有题目std::string html;ctrl.AllQuestions(&html);resp.set_content(html,"text/html;charset=utf-8");});svr.Get(R"(/question/(\d+))", [&ctrl](const Request& req,Response& resp){std::string html;std::string id = req.matches[1];ctrl.OneQuestion(id,&html);resp.set_content(html,"text/html;charset=utf-8");});svr.Post(R"(/judge/(\d+))", [&ctrl](const Request& req,Response& resp){std::string id = req.matches[1];std::string in_json = req.body;std::string out_json;ctrl.Judge(id,in_json,&out_json);resp.set_content(out_json,"application/json;charset=utf-8");});svr.set_base_dir("./wwwroot");svr.listen("0.0.0.0", atoi(argv[1]));return 0;
}

5. 项目扩展

  1. 基于注册和登陆的录题功能
  2. 业务扩展,自己写一个论坛,接入到在线OJ中
  3. 即便是编译服务在其他机器上,也其实是不太安全的,可以将编译服务部署在docker
  4. 目前后端compiler的服务我们使用的是http方式请求(仅仅是因为简单),但是也可以将我们的compiler服务,设计成为远程过程调用,推荐:rest_rpc,替换我们的httplib
  5. 功能上更完善一下,判断一道题目正确之后,自动下一道题目

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com