在线OJ系统
# 访问步骤
1、编译运行服务器
浏览器通过https请求向服务器提交代码
服务器形成临时代码文件,调用编译器进行编译
父进程继续执行,子进程完成编译(使用进程替换g++)。
- 父进程:waitpid(pid, &Status, 0);等待子进程退出,若生成了可执行程序(文件是否存在),返回true;否则返回false。
- 子进程:若出现编译错误,重定向(dup2)一个文件中进行返回;若成功则返回。
对编译成功的进行执行,记录执行成功、失败、错误的结果
- 父进程:等待子进程退出,关闭文件描述符,返回状态status()
- 子进程:重定向0,1,2到文件,将程序替换为编译输出的结果。可以同时设置程序的运行时间和内存大小,超出就退出程序:设置方法
#include <sys/time.h> #include <sys/resource.h> //通过设置信号导致进程退出 int setrlimit(int resource, const struct rlimit *rlim); //resource:RLIMIT_CPU;RLIMIT_AS; struct rlimit rlim; rlim.rlim_cur = CpuTimeLimit; rlim.rlim_max = RLIM_INFINITY;
1
2
3
4
5
6
7使用jsoncpp(yum install jsoncpp-devel)对http请求进行封装和解析。将解析的结果写入唯一的文件(由时间戳生成),执行编译,编译结果进行封装返回,删除编译源文件。当编译成功时,运行程序;否则直接退出。
将运行结果进行解析,由status进行判断,若运行出错,读取错误文件并封装成json返回。若正常运行结束,传回运行结果、运行错误。
使用
httplib
建立编译服务器,使用post请求编译运行的服务端口(其内调用编译运行的函数接口),并将最终得到的结果进行返回(编译成功/失败、运行成功/失败、运行成功后的结果)——可开多个端口供前端服务器调用
2、实现基于MVC结构的OJ服务器——Model实现数据交换,View实现数据渲染,control对二者进行控制。
主函数使用httplib执行请求,Get请求问题列表页面,Post请求后端编译运行服务器。
问题列表中,建立问题描述文件【1】、解答预填充代码【2】、测试用例【3】
Model模块:
- 构造问题映射,读取文件总列表(包含所有题目)作为key;读取问题文件夹,将【1】【2】【3】读入对应的结构体中作为value,在内存中建立起映射关系。
- 从map中获得所有题目信息【a】
- 从map中或得一个对应题号的题目信息【b】
view模块:使用(cteplate)[https://github.com/OlafvdSpek/ctemplate]进行网页渲染,首先需要加载网页整体的框架的html,其中填写一个包含键值的p标签。
<p> {{key}} </p>
1
2
3渲染的C代码如下:
ctemplate::TemplateDictionary root("HTML_Path"); //多重映射 ctemplate::TemplateDictionary *sub = root.AddSectionDictionary("OneQuesInfo"); sub->SetValue("key1", k1); sub->SetValue("key1", k2); //单键值映射 root.SetValue("key", key); //将映射的结果输出到string :output ctemplate::Template *pl = ctemplate::Template::GetTemplate(OutPath, ctemplate::DO_NOT_STRIP); pl->Expand(output, &root);
1
2
3
4
5
6
7
8
9
10
11
12
13- 渲染题目列表:使用多重映射
- 渲染一个题目:使用单重映射
control模块:成员变量中:定义Model类对象和view类对象
输出所有题目——调用Model中发送所有题目方法【a】,调用view进行网页渲染后输出
输出一个题目——调用Model中发送一个题目方法【b】,调用view进行网页渲染后输出
判题:输入题号、负载均衡调用后端端口
负载均衡:1、定义结构体:主机ip、端口、负载值(uint64_t,对对应的编译后端进行+1,编译结束-1)、锁。2、控制模块,结构体包括{主机vector、在线主机下标、离线主机下标}。加载列表中所有的主机ip和端口,使用vector表示所有主机,vector下标即表示主机的编号。初始加载后端所有主机(编译运行服务器,本地对应不同的端口);选择主机时遍历所有主机编号,选择在线主机的负载值比第一个(有可能第一个被选择)主机更小的端口,返回对应的主机下标。
bool LoadMachine(const std::string &MachineList); //加载主机 bool ChooseMachine(int *id, Machine **m); //选择主机,输出id和machine结构 void OnlineMachine(); //将_offline中的主机重新添加到_online,这个需要调用编译服务器+fork,暂未实现 void OfflineMachine(const int &id); // 检测主机若不存在,则将对应的id剔除_online,添加入_offline bool ShowMachine(); //显示上线的所有主机 std::vector<Machine> _machine; //主机队列 std::vector<int> _online; //在线主机id; std::vector<int> _offline; //离线主机id; struct Machine { std::string _ip; int _port; uint64_t _weight; //负载值 std::mutex *_mtx; //。。。 }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17判题阶段:调用Model的【b】获得一个题目的所有信息,解析远端的请求并提取远端填入的代码,封装成新的json传给编译运行服务器。选择一个主机的ip和端口,作为http客户端进行Post请求,得到编译运行服务器传回的结果。将结果传回前端进行显示。
# 使用Boost库的一些函数
- 使用Boost库中的字符串切分
boost::split(*strout, strin, boost::is_any_of(Seperator), boost::token_compress_on);
# C库的使用
- 判断文件是否存在
#include <sys/stat.h>
struct stat s;
return stat(Filename.c_str(), &s) == 0; //存在返回true
2
3
- 判断文件是否为空
struct stat s;
s.st_size == 0; //"文件为空",调用前需要判断文件是否存在
2
- 进程替换
execlp("g++", "g++", "-o",
CreateFileName::ADDSUFFIX::ExecuteFile(FileName).c_str(),
CreateFileName::ADDSUFFIX::CppFile(FileName).c_str(), "-std=c++11", nullptr); //CreateFileName自己实现的类
2
3
- 删除文件
#include <unistd.h>
int unlink(const char *pathname);
2