1.所用技术与开发环境
所用技术 :
C++ STL 标准库
Boost 准标准库 ( 字符串切割 )
cpp - httplib 第三方开源网络库
ctemplate 第三方开源前端网页渲染库
jsoncpp 第三方开源序列化、反序列化库
负载均衡设计
多进程、多线程
MySQL C connect
Ace 前端在线编辑器
html/css/js/jquery/ajax
2.开发环境
Centos 7 云服务器
vscode
Mysql Workbench
3. 项目宏观结构
我们的项目核心是三个模块
1. comm : 公共模块
2. compile_server : 编译与运行模块
3. oj_server : 获取题目列表,查看题目编写题目界面,负载均衡,其他功能
I. leetcode 结构
只实现类似 leetcode 的题目列表 + 在线编程功能
II. 我们的项目宏观结构
III. 编写思路
1. 先编写 compile_server
2. oj_server
3. version1 基于文件版的在线 OJ
4. 前端的页面设计
5. version2 基于 MySQL 版的在线 OJ
4. compiler 服务设计
提供的服务:编译并运行代码,得到格式化的相关的结果
第一个功能 compiler :编译功能
#pragma once
#include <iostream>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "../comm/util.hpp"
#include "../comm/log.hpp"
//只负责代码的编译
namespace ns_compiler
{
//引入路径拼接功能
using namespace ns_util;
using namespace ns_log;
class Compiler
{
public:
Compiler() = default;
~Compiler() = default;
//返回值:编译成功:True 编译失败:False
//输入参数:编译文件名
// file_name: 123
// 123 ->./temp/123.cpp
// 123 ->./temp/123.exe
// 123 ->./temp/123.stderr
static bool Compile(const std::string &file_name)
{
pid_t pid = fork();
if (pid < 0)
{
LOG(ERROR)<<"内部错误,创建子进程失败"<<"\n";
return false;
}
else if (pid == 0)
{
umask(0);
int _stderr = open(PathUtil::Error(file_name).c_str(), O_CREAT | O_WRONLY, 0644);
if (_stderr < 0)
{
LOG(WARNING)<<"没有形成stderr文件"<<"\n";
exit(1);
}
dup2(_stderr, 2); //将错误信息重定向到文件中
//子进程使用程序替换完成代码的编译功能
execlp("g++","g++", "-o", PathUtil::Exe(file_name).c_str(),PathUtil::Src(file_name).c_str(), "-std=c++11", nullptr /*不要忘记*/);
LOG(ERROR) << "启动编译器g++失败,可能是参数传入有误"<<"\n";
exit(2);
}
else
{
waitpid(pid, nullptr, 0);
//编译是否成功,就看有没有形成同名的可执行文件
if (FileUtil::IsFileExists(PathUtil::Exe(file_name)))
{
LOG(INFO)<<PathUtil::Src(file_name)<<"编译成功"<<"\n";
return true;
}
}
LOG(ERROR)<<"编译失败,没有形成可执行文件"<<"\n";
return false;
}
};
}
Log 功能
#pragma once
#include <iostream>
#include <string>
#include "util.hpp"
namespace ns_log
{
//日志等级
enum
{
INFO,
DEBUG,
WARNING,
ERROR,
FATAL
};
//LOG()<<"message"
inline std::ostream &Log(const std::string &level,const std::string &file_name,const int line)
{
//添加日志等级
std::string message = "[";
message += level;
message += "]";
//添加报错文件名称
message += "[";
message += file_name;
message += "]";
//添加报错行
message += "[";
message += std::to_string(line);
message += "]";
//日志时间戳
message += "[";
message += ns_util::TimeUtil::GetTimeStamp();
message += "]";
std::cout << message;
return std::cout;
}
//开放日志
#define LOG(level) Log(#level,__FILE__,__LINE__)
}
第二个功能 runner :运行功能
#pragma once
#include <iostream>
#include <string>
#include <unistd.h>
#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/util.hpp"
#include "../comm/log.hpp"
namespace ns_runner
{
class Runner
{
public:
Runner() = default;
~Runner() = default;
public:
static void SetProcLimit(int cpu_limit,int mem_limit)
{
//设置CPU时长
struct rlimit _cpu_limit;
_cpu_limit.rlim_max = RLIM_INFINITY;
_cpu_limit.rlim_cur = cpu_limit;
setrlimit(RLIMIT_CPU,&_cpu_limit);
//设置内存大小
struct rlimit _mem_limit;
_mem_limit.rlim_max = RLIM_INFINITY;
_mem_limit.rlim_cur = mem_limit*1024; //转化成KB
setrlimit(RLIMIT_AS,&_mem_limit);
}
//返回值 > 0 ,程序异常,收到信号,返回值就是信号编号
//返回值 == 0 ,正常运行完毕,结果保存到对应的临时文件
//返回值 < 0 ,内部错误
//cpu_limit:该程序运行的时候,可以使用的最大CPU资源上限
//mem_limit:该程序运行的时候,可以使用最大内存
static int Run(const std::string &file_name,int cpu_limit,int mem_limit)
{
//只考虑是否正确运行,不考虑结果是否正确
/*
一个程序在默认启动的时候
标准输入:不处理
标准输出:结果
标准错误:运行时错误信息
*/
std::string _exectue = ns_util::PathUtil::Exe(file_name);
std::string _stdin = ns_util::PathUtil::Stdin(file_name);
std::string _stdout = ns_util::PathUtil::Stdout(file_name);
std::string _stderr = ns_util::PathUtil::Stderr(file_name);
//打开临时文件
umask(0);
int _stdin_fd = open(_stdin.c_str(),O_CREAT|O_RDONLY,0644);
int _stdout_fd = open(_stdout.c_str(),O_CREAT|O_WRONLY,0644);
int _stderr_fd = open(_stderr.c_str(),O_CREAT|O_WRONLY,0644);
if(_stdin_fd < 0 || _stdout_fd < 0 || _stderr_fd < 0)
{
ns_log::LOG(ERROR)<<"运行时打开文件失败"<<"\n";
return -1; //文件打开失败
}
pid_t pid = fork();
if (pid < 0)
{
ns_log::LOG(ERROR)<<"运行时创建子进程失败"<<"\n";
close(_stdin_fd);
close(_stdout_fd);
close(_stderr_fd);
}
else if (pid == 0)
{
dup2(_stdin_fd,0);
dup2(_stdout_fd,1);
dup2(_stderr_fd,2);
SetProcLimit(cpu_limit,mem_limit);
execl(_exectue.c_str(),_exectue.c_str(),nullptr);
exit(1);
}
else
{
// std::cout<<"关闭文件描述符"<<std::endl;
close(_stdin_fd);
close(_stdout_fd);
close(_stderr_fd);
int status = 0;
//进程异常收到信号
waitpid(pid, &status,0);
ns_log::LOG(INFO)<<"运行完毕,info:"<< (status & 0x7F) << "\n";
return status & 0x7F;
}
return 0;
}
};
}
测试资源限制:
#include <iostream>
#include <sys/time.h>
#include <sys/resource.h>
#include <unistd.h>
#include <signal.h>
void handler(int signo)
{
std::cout << "signo : " << signo << std::endl; exit(1);
}
int main()
{
//资源不足,导致OS终止进程,是通过信号终止的 for(int i =1; i <= 31; i++)
{ signal(i, handler);
// struct rlimit r;
// r.rlim_cur = 1;
// r.rlim_max = RLIM_INFINITY;
// setrlimit(RLIMIT_CPU, &r);
//while(1); struct rlimit r;
r.rlim_cur = 1024 * 1024 * 40;
//20M r.rlim_max = RLIM_INFINITY;
setrlimit(RLIMIT_AS, &r);
int count = 0; while(true)
{
int *p = new int[1024*1024];
count++; std::cout << "size: " << count << std::endl;
sleep(1);
}
return 0;
}
}// 限
// 内存申请失败
terminate called after throwing an instance of 'std::bad_alloc'
what (): std::bad_alloc
signo : 6
[ whb@bite - alicloud OnlineJudge ] $ 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
//CPU 使用超时
[ whb@bite - alicloud OnlineJudge ] $ . / a . out
signo : 24
[ whb@bite - alicloud OnlineJudge ] $ 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
第三个功能 compile_run :编译并运行功能
#pragma once
#include <jsoncpp/json/json.h>
#include <signal.h>
#include <unistd.h>
#include <vector>
#include "compiler.hpp"
#include "complie_run.hpp"
#include "../comm/log.hpp"
#include "../comm/util.hpp"
#include "runner.hpp"
namespace ns_compile_and_run
{
class ComplieAndRun
{
public:
static std::string CodeToDesc(int code, const std::string &file_name)
{
std::string desc;
switch (code)
{
case 0:
desc = "编译运行成功";
break;
case -1:
desc = "用户提交的代码为空";
break;
case -2:
desc = "未知错误";
break;
case -3:
// desc = "代码编译是出现错误";
ns_util::FileUtil::ReadFile(ns_util::PathUtil::Error(file_name), &desc, true);
break;
case SIGABRT: // 6
desc = "内存超过范围";
break;
case SIGXCPU: // 24
desc = "CPU使用超时";
break;
case SIGFPE: // 8
desc = "浮点数溢出"; //除0
break;
default:
desc = "未知错误" + std::to_string(code);
break;
}
return desc;
}
public:
static void RemoveTempFile(const std::string &file_name)
{
std::vector<std::string> AllTempFile{ns_util::PathUtil::Src(file_name),
ns_util::PathUtil::Error(file_name),
ns_util::PathUtil::Exe(file_name),
ns_util::PathUtil::Stderr(file_name),
ns_util::PathUtil::Stdin(file_name),
ns_util::PathUtil::Stdout(file_name)};
for(const auto &e :AllTempFile)
{
if(ns_util::FileUtil::IsFileExists(e));
unlink(e.c_str());
}
}
/*
输入:
code:用户给自己提交的代码
input:用户给自己的代码对应的输入,不作处理(后期可以扩展)
cpu_limit:时间复杂度
mem_limit:时间复杂度
输出:
status:状态码(必填)
reason:请求结果(必填)
stdout:程序运行结果(选填)
stderr:程序运行完的错误结果(选填)
*/
//参数
// in_json:{"code":"";"input":"";"cpu_limit":"";"mem_limit":"";}
// out_json:{"status":"0";"reason":"";"stdout":"";"stderr":""}
static void Start(const std::string &in_json, std::string *out_json)
{
Json::Value in_vaule;
Json::Reader reader;
reader.parse(in_json, in_vaule); //最后在差错处理
std::string code = in_vaule["code"].asString();
std::string input = in_vaule["input"].asString();
int cpu_limit = in_vaule["cpu_limit"].asInt();
int mem_limit = in_vaule["mem_limit"].asInt();
Json::Value out_vaule;
int status_code = 0;
std::string file_name;
int run_result = 0;
if (code.size() == 0)
{
// //最后差错处理
// out_vaule["status"] = -1; //代码为空
// out_vaule["reason"] = "用户提交的代码是空的";
// //序列化
// return;
status_code = -1;
goto END;
}
//形成唯一文件名 毫秒级时间戳 + 原子性递增唯一值
file_name = ns_util::FileUtil::UniqFileName();
if (!ns_util::FileUtil::WiterFile(ns_util::PathUtil::Src(file_name), code)) //形成临时源src文件
{
// out_vaule["status"] = -2; //未知错误
// out_vaule["reason"] = "提交的代码发生了未知错误";
// //序列化
// return;
status_code = -2;
goto END;
}
if (!ns_compiler::Compiler::Compile(file_name)) //编译失败
{
// out_vaule["status"] = -3;
// //编译失败的内容保存到了.error文件中,读取序列化
// out_vaule["reason"] = us_util::FileUtil::ReadFile(us_util::PathUtil::Error(file_name));
// //序列化
// return;
status_code = -3;
goto END;
}
run_result = ns_runner::Runner::Run(file_name, cpu_limit, mem_limit); //需要知道时间复杂度和空间复杂度
if (run_result < 0)
{
// out_vaule["status"] = -2; //未知错误
// out_vaule["reason"] = "发生了未知错误";
// //序列化
// return;
status_code = -2;
goto END;
}
else if (run_result > 0)
{
// out_vaule["status"] = -4; //运行时报错,收到信号
// out_vaule["reason"] = SignoToDesc(); //将信号转化成报错原因;
// //序列化
// return;
status_code = run_result;
goto END;
}
else
{
// //运行成功
// out_vaule["status"] = 0;
// out_vaule["reason"] = "运行成功";
status_code = 0;
}
END:
out_vaule["status"] = status_code;
out_vaule["reason"] = CodeToDesc(status_code,file_name);
if (status_code == 0)
{
//全部成功
std::string _stdout;
ns_util::FileUtil::ReadFile(ns_util::PathUtil::Stdout(file_name), &_stdout, true);
out_vaule["stdout"] = _stdout;
// std::cout<<"标准输出:"<<_stdout<<std::endl;
std::string _stderr;
ns_util::FileUtil::ReadFile(ns_util::PathUtil::Stderr(file_name), &_stderr, true);
out_vaule["stderr"] = _stderr;
}
//序列化
Json::StyledWriter writer;
*out_json = writer.write(out_vaule);
RemoveTempFile(file_name);
}
};
}
5. 基于MVC 结构的oj 服务设计
本质:建立一个小型网站
1. 获取首页,用题目列表充当
2. 编辑区域页面
3. 提交判题功能 ( 编译并运行 )
M : Model , 通常是和数据交互的模块,比如,对题库进行增删改查(文件版, MySQL )
V : view , 通常是拿到数据之后,要进行构建网页,渲染网页内容,展示给用户的 ( 浏览器 )
C : control , 控制器,就是我们的核心业务逻辑
第一个功能:用户请求的服务路由功能
#include <iostream>
#include "../comm/httplib.h"
#include "oj_control.hpp"
using namespace httplib;
int main()
{
//用户请求的的路由功能
Server svr;
ns_control::Control ctrl;
//获取所有题目列表
svr.Get("/all_questions",[&ctrl](const Request& req,Response &resq){
//返回一张带有所有题目的html网页
std::string html;
ctrl.ALlQuestions(&html);
resq.set_content(html,"text/html;charset=utf-8");
});
//获取要根据题目编号,获取题目的内容
svr.Get(R"(/question/(\d+))",[&ctrl](const Request& req,Response &resq){
std::string num = req.matches[1];
std::string html;
ctrl.Question(num,&html);
resq.set_content(html,"text/html;charset=utf-8");
});
//用户提交代码,使用我们的判题功能()
svr.Post(R"(/judge/(\d+))",[&ctrl](const Request& req,Response &resq){
std::string number = req.matches[1];
std::string result_json;
ctrl.Judge(number,req.body,&result_json);
resq.set_content(result_json,"application/json;charset=utf-8");
// resq.set_content("指明题目的判题:"+num,"text/plain;charset=utf-8");
});
svr.set_base_dir("./wwwroot");
svr.listen("0.0.0.0",8080);
return 0;
}
第二个功能:model功能,提供对数据的操作
#pragma once
//文件版本
#include "../comm/util.hpp"
#include "../comm/log.hpp"
#include <iostream>
#include <string>
#include <vector>
#include <unordered_map>
#include <fstream>
#include <cstdlib>
#include <cassert>
// 根据题目list文件,加载所有的题目信息到内存中
// model: 主要用来和数据进行交互,对外提供访问数据的接口
namespace ns_model
{
using namespace std;
using namespace ns_log;
using namespace ns_util;
struct Question
{
std::string number; //题目编号,唯一
std::string title; //题目的标题
std::string star; //难度: 简单 中等 困难
int cpu_limit; //题目的时间要求(S)
int mem_limit; //题目的空间要去(KB)
std::string desc; //题目的描述
std::string header; //题目预设给用户在线编辑器的代码
std::string tail; //题目的测试用例,需要和header拼接,形成完整代码
};
const std::string questins_list = "./questions/questions.list";
const std::string questins_path = "./questions/";
class Model
{
private:
//题号 : 题目细节
unordered_map<string, Question> questions;
public:
Model()
{
assert(LoadQuestionList(questins_list));
}
bool LoadQuestionList(const string &question_list)
{
//加载配置文件: questions/questions.list + 题目编号文件
ifstream in(question_list);
if (!in.is_open())
{
LOG(FATAL) << " 加载题库失败,请检查是否存在题库文件"<< "\n";
return false;
}
string line;
while (getline(in, line))
{
vector<string> tokens;
StringUtil::SplitString(line, &tokens, " ");
// 1 判断回文数 简单 1 30000
if (tokens.size() != 5)
{
LOG(WARNING) << "加载部分题目失败, 请检查文件格式" << "\n";
continue;
}
Question q;
q.number = tokens[0];
q.title = tokens[1];
//std::cout<<q.title<<std::endl;
q.star = tokens[2];
q.cpu_limit = atoi(tokens[3].c_str());
q.mem_limit = atoi(tokens[4].c_str());
string path = questins_path;
path += q.number;
path += "/";
FileUtil::ReadFile(path + "desc.txt", &(q.desc), true);
FileUtil::ReadFile(path + "header.cpp", &(q.header), true);
FileUtil::ReadFile(path + "tail.cpp", &(q.tail), true);
questions.insert({q.number, q});
}
LOG(INFO) << "加载题库...成功!"
<< "\n";
in.close();
return true;
}
bool GetAllQuestions(vector<Question> *out)
{
if (questions.size() == 0)
{
LOG(ERROR) << "用户获取题库失败"<< "\n";
return false;
}
for (const auto &q : questions)
{
out->push_back(q.second); // first: key, second: value
}
return true;
}
bool GetOneQuestion(const std::string &number, Question *q)
{
const auto &iter = questions.find(number);
if (iter == questions.end())
{
LOG(ERROR) << "用户获取题目失败, 题目编号: " << number << "\n";
return false;
}
(*q) = iter->second;
return true;
}
~Model()
{
}
};
} // namespace ns_model
第三个功能:control,逻辑控制模块
#pragma once
#include <iostream>
#include <string>
#include <mutex>
#include <cassert>
#include <fstream>
#include <jsoncpp/json/json.h>
#include "oj_model.hpp"
#include "../comm/log.hpp"
#include "../comm/util.hpp"
#include "oj_view.hpp"
#include "../comm/httplib.h"
namespace ns_control
{
const std::string service_machine = "./conf/service_machine.conf";
//提供服务的主机
class Machine
{
public:
std::string ip; //编译服务的ip
int port; //编译服务的端口
uint64_t load; //编译服务的负载
std::mutex *mtx; //mutex禁止拷贝的,使用指针来完成
public:
Machine()
:ip("")
,port(0)
,load(0)
,mtx(nullptr)
{
}
~Machine()
{
}
public:
void IncLoad() //提升负载
{
if(mtx) mtx->lock();
++load;
if(mtx) mtx->unlock();
}
void DecLoad() //减少负载
{
if(mtx) mtx->lock();
--load;
if(mtx) mtx->unlock();
}
uint64_t Load() //获取负载
{
uint64_t _load = 0;
if(mtx) mtx->lock();
_load = load;
if(mtx) mtx->unlock();
return _load;
}
};
//负载均衡模块
class LoadBlance
{
private:
std::vector<Machine> machines; //可以提供编译服务所有主机
std::vector<int> online; //所有在线的主机
std::vector<int> offline; //所有离线主机
std::mutex mtx; //保证LoadBlance数据安全
public:
LoadBlance()
{
assert(LoadConf(service_machine));
ns_log::LOG(ns_log::INFO)<<" 加载 "<<service_machine<<" 成功 "<<"\n";
}
~LoadBlance()
{
}
public:
bool LoadConf(const std::string &machine_conf)
{
std::ifstream in(machine_conf);
if(!in.is_open())
{
ns_log::LOG(ns_log::FATAL)<<"加载配置:"<<machine_conf<<"文件失败"<<"\n";
return false;
}
std::string line;
while(getline(in,line))
{
std::vector<std::string> tokens;
ns_util::StringUtil::SplitString(line,&tokens,":");
if(tokens.size() != 2)
{
ns_log::LOG(ns_log::WARNING) <<" 切分 "<<line<<" 失败 "<<"\n";
continue;
}
Machine m;
m.ip = tokens[0];
m.port = atoi(tokens[1].c_str());
m.load = 0;
m.mtx = new std::mutex();
online.push_back(machines.size());
machines.push_back(m);
}
in.close();
return true;
}
//id:输出型参数
//m :输出型参数
bool SmartChoice(int* id,Machine **m)
{
//1.使用选择好的主机(更新该主机的负载)
//2.我们需要可能离线该主机
mtx.lock();
//负载均衡的算法
//1.随机数 + hash
//2.轮询 + hash
int online_num = online.size();
if(online_num == 0)
{
mtx.unlock();
ns_log::LOG(ns_log::FATAL) << "后端编译服务全部挂掉了,请运维的老铁尽快查看"<<"\n";
return false;
}
//通过编译找到负载最小的机器
*id = online[0];
*m = &machines[online[0]];
uint64_t min_load = machines[online[0]].Load();
for(int i = 0; i < online_num; ++i)
{
min_load = min_load < machines[online[i]].Load() ? machines[online[i]].Load() : min_load;
*id = online[i];
*m = &machines[online[i]];
}
mtx.unlock();
return true;
}
void OfflineMachine(int which)
{
mtx.lock();
for(auto iter = online.begin(); iter != online.end(); ++iter)
{
if(*iter == which)
{
online.erase(iter);
offline.push_back(which);
break;
}
}
mtx.unlock();
}
void OnlineMachine()
{
}
void ShowMachines()
{
mtx.lock();
std::cout<<"当前在线主机列表:";
for(auto &id : online)
{
std::cout << id <<" ";
}
std::cout<<std::endl;
for(auto &id : offline)
{
std::cout<<"当前离线主机列表:";
std::cout << id << " ";
}
std::cout<<std::endl;
mtx.unlock();
}
};
class Control
{
private:
ns_model::Model _model; //提供后台服务
ns_view::View _view; //提供html渲染功能
LoadBlance _load_blance; //提供负载均衡器
public:
Control()
{
}
//根据题目数据构建网页
bool ALlQuestions(std::string *html)
{
std::vector<ns_model::Question> all;
if(_model.GetAllQuestions(&all))
{
//获取题目信息成功,将所有的题目数据构建成网页
_view.AllExpandHtml(all,html);
}
else
{
*html = "获取题目失败,形成题目列表失败";
return false;
}
return true;
}
bool Question(const std::string number,std::string *html)
{
ns_model::Question q;
if(_model.GetOneQuestion(number,&q))
{
//获取指定题目成功,将题目数据构建成网页
_view.OneExpandHtml(q,html);
}
else
{
*html = "指定题目" + number + "不存在";
return false;
}
return true;
}
void Judge(const std::string& number, const std::string in_json,std::string *out_json)
{
//0.根据题号,直接拿到题目细节
ns_model::Question q;
_model.GetOneQuestion(number,&q);
//1.in_json进行反序列化,得到题目的id,得到用户提交的源代码,input
Json::Reader reader;
Json::Value in_value;
reader.parse(in_json,in_value);
//2.重新拼接用户代码 + 测试用例代码,形成新代码
std::string code = in_value["code"].asString();
Json::Value compile_value;
compile_value["input"] = in_value["input"].asString();
compile_value["code"] = code + q.tail;
compile_value["cpu_limit"] = q.cpu_limit;
compile_value["mem_limit"] = q.mem_limit;
Json::FastWriter writer;
std::string complie_string = writer.write(compile_value);
//3.选择负载最低的主机(差错处理)
for( ; ;)
{
int id = 0;
Machine *m = nullptr;
if(!_load_blance.SmartChoice(&id,&m))
{
break;
}
ns_log::LOG(ns_log::INFO) <<"选择主机成功"<<id<<"详情"<<m->ip<<":"<<m->port<<"\n";
//4.然后发起http请求,得到结果
httplib::Client cli(m->ip,m->port);
m->IncLoad();
if(auto res = cli.Post("/compile_and_run",complie_string, "application/json;charset=utf-8"))
{
//5.将结果赋值给out_json
if(res->status == 200)
{
*out_json = res->body;
m->DecLoad();
break;
}
m->DecLoad();
}
else
{
//请求失败
ns_log::LOG(ns_log::ERROR)<<"详情:" << id <<":"<< m->ip << ":"<<m->port<<"可能已经离线"<<"\n";
_load_blance.OfflineMachine(id);
_load_blance.ShowMachines();
}
}
}
~Control()
{
}
};
}
附加功能:需要有数据渲染
// 如果后续引入了 ctemplate ,一旦对网页结构进行修改,尽量的每次想看到结果,将 server 重启一下。 ctemplate 有 自己的优化加速策略,可能在内存中存在缓存网页数据(old)
当我们完成全部功能之后,需要注意:
要给编译模块添加—D条件编译掉测试用例中的头文件incldue
6. version1 文件版题目设计
1. 题目的编号
2. 题目的标题
3. 题目的难度
4. 题目的描述,题面
5. 时间要求 ( 内部处理 )
6. 空间要求 ( 内部处理 )
两批文件构成
1. 第一个: questions . list : 题目列表(不需要题目的内容)
2. 第二个:题目的描述,题目的预设置代码 ( header . cpp ), 测试用例代码 ( tail . cpp )
这两个内容是通过题目的编号,产生关联的
1. 当用户提交自己的代码的时候: header . cpp
#include <iostream>
#include <string>
#include <vector>
#include <map>
#include <algorithm>
using namespace std;
class Solution{
public:
bool isPalindrome(int x)
{
//将你的代码写在下面
return true;
}
};
该题号对应的测试用例 : tail . cpp
#ifndef COMPILER_ONLINE
#include "header.cpp"
#endif
void Test1()
{
// 通过定义临时对象,来完成方法的调用
bool ret = Solution().isPalindrome(121);
if(ret){
std::cout << "通过用例1, 测试121通过 ... OK!" << std::endl;
}
else{
std::cout << "没有通过用例1, 测试的值是: 121" << std::endl;
}
}
void Test2()
{
// 通过定义临时对象,来完成方法的调用
bool ret = Solution().isPalindrome(-10);
if(!ret){
std::cout << "通过用例2, 测试-10通过 ... OK!" << std::endl;
}
else{
std::cout << "没有通过用例2, 测试的值是: -10" << std::endl;
}
}
int main()
{
Test1();
Test2();
return 0;
}
后端全部写完使用Postman 来测试