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. 需求分析
- 用户能够查看题目列表
- 用户能够看到题目详细信息
- 用户能够编写代码并提交测试,测试结果返回给用户
2. 项目宏观结构
我们的项目核心是三个模块
- comm : 公共模块
- compile_server : 编译与运行模块
- oj_server : 获取题目列表,查看题目编写题目界面,负载均衡,其他功能
采用BS模式,浏览器访问后端服务器
用户的请求将通过oj_server
来进行处理,如果是访问题目的请求,会访问文件或者数据库,如果是编译与运行服务会下放到负责此功能的主机,实现功能解耦。
3. compile_server服务设计
compile_server
模块分为:compiler
、runner
和compile_run
compiler
:负责代码的编译服务
runner
:负责代码的运行服务
compile_run
:负责接收要处理的服务并将编译运行的结果处理成格式化结果返回
3.1 compiler服务设计
提供的服务:编译代码,得到编译的结果
- 对于接收的代码,创建代码的临时文件,以供编译
- 代码编译后,如果编译错误,将错误信息存入一个文件,如果编译正确,则会生成可执行文件
- 可预见的:在运行时也需要生成很多临时文件存储标准输入、标准输出、标准错误
所以,我们需要一个临时目录来存放这些临时文件,且临时文件名不能重复
我们统一命名这些临时文件:时间戳_num.xxx
在创建临时文件时,需要先获取时间戳和num,num是一个原子性的计数器,线程安全,这样就能保证所有的文件都是不同名的
于是:
- 源文件名:
时间戳_num.src
- 可执行文件名:
时间戳_num.exe
- 编译错误文件名:
时间戳_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服务设计
提供的服务:运行编译好的可执行文件,得到程序的结果
临时文件:
- 标准输入文件名:
时间戳_num.stdin
- 标准输出文件名:
时间戳_num.stdout
- 标准错误文件名:
时间戳_num.stderr
运行可执行文件有三种结果:
- 运行失败
- 运行成功,结果正确
- 运行成功,结果错误
对于runner
模块,我们并不考虑程序结果正确与否,因为要达到功能解耦,我们只关心程序是否运行成功
所以运行是否成功也有三种情况:
- 运行失败,系统或者其他原因 – 不需要让用户知道,例如:创建进程失败,代码为空等
- 运行失败,代码出错 – 需要让用户知道,例如:野指针、时间复杂度过大等
- 运行成功
进程运行时崩溃一定是被信号杀掉的,所以我们获取进程退出的状态码中的信号标识位,可得到运行失败原因
#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模式,分为:model
、view
、control
model
:用来与底层数据交互view
:用来处理用户视图,即前端页面control
:统筹model
与view
实现业务逻辑
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);}};
}
总共分为三个页面呈现给用户:
- OJ主页
- 题目列表
- 题目详情页即代码编辑区
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. 项目扩展
- 基于注册和登陆的录题功能
- 业务扩展,自己写一个论坛,接入到在线OJ中
- 即便是编译服务在其他机器上,也其实是不太安全的,可以将编译服务部署在docker
- 目前后端compiler的服务我们使用的是http方式请求(仅仅是因为简单),但是也可以将我们的compiler服务,设计成为远程过程调用,推荐:rest_rpc,替换我们的httplib
- 功能上更完善一下,判断一道题目正确之后,自动下一道题目