负载均衡式的在线OJ项目编写(二)
一.前期内容回顾
对前面的准备不熟悉的,可以看前面的内容,连接如下:
https://blog.csdn.net/weixin_60668256/article/details/152027386?fromshare=blogdetail&sharetype=blogdetail&sharerId=152027386&sharerefer=PC&sharesource=weixin_60668256&sharefrom=from_link
二.资源限制测试
这里的资源限制不是很强,但是一定得有(为了防止某些用户进行恶意攻击)
man 2 setrlimit可以查看setrlimit的详细使用方法
查看可以设置的哪些限制 resource可以是下面的任意组合
很多设置,我们可以通过这些设置,来对提交的代码进行设置限制
//test.cc#include <iostream>
#include <sys/time.h>
#include <sys/resource.h>int main()
{//限制累计运行时长struct rlimit r;r.rlim_cur = 1;//当前秒数为1r.rlim_max = RLIM_INFINITY;//无穷,最大不做约束setrlimit(RLIMIT_CPU,&r);while(1);return 0;
}
#include <iostream>
#include <sys/time.h>
#include <sys/resource.h>
#include <unistd.h>int main()
{//空间限制struct rlimit r;r.rlim_cur = 1024 * 1024 * 40; // 40Mr.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;
}
资源不足,导致OS终止进程,是通过信号进行终止的
测试代码
#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;//当前秒数为1// r.rlim_max = RLIM_INFINITY;//无穷,最大不做约束// setrlimit(RLIMIT_CPU,&r);// while(1);//空间限制struct rlimit r;r.rlim_cur = 1024 * 1024 * 40; // 40Mr.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: 61) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
.....//CPU使用超时
signo: 24
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
三.给runner添加资源约束
是子进程进行程序执行,所以要加到子进程上面
对于具体题目的秒数和内存大小,runner模块是不用自己操作的,让别人(传入即可)
具体实现:
static void SetProcLimit(int _cpu_limit,int _mem_limit){//设置CPU时长struct rlimit cpu_rlimit;cpu_rlimit.rlim_max = RLIM_INFINITY;cpu_rlimit.rlim_cur = _cpu_limit;setrlimit(RLIMIT_CPU,&cpu_rlimit);//设置内存大小struct rlimit mem_rlimit;mem_rlimit.rlim_max = RLIM_INFINITY;mem_rlimit.rlim_cur = _mem_limit * 1024;setrlimit(RLIMIT_AS,&mem_rlimit);}
四.compile_run模块(编译并运行功能)
1.适配用户的请求,定制通信协议字段
2.正确的调用compile and run方法
3.形成唯一文件名
编译服务随时可能被多个人请求,必须保证传递上来的code,形成源文件名称的时候,要具有唯一性,不然多个用户之间会互相影响
目前的软件结构:
设计网络之后,我们可以通过http,让client给我们上传一个json串
4.1json串介绍
安装jsoncpp
ubuntu下
sudo apt update
sudo apt install libjsoncpp-dev
查找json.h,能找到就是安装好了
编译的时候,后面必须连接上-ljsoncpp的库,不然编译会报错
#include <iostream>
#include <sys/time.h>
#include <sys/resource.h>
#include <unistd.h>
#include <signal.h>
#include <jsoncpp/json/json.h>int main()
{//序列化的工作//Value是Json的中间类,可以填充KV值//将结构化数据转化成字符串Json::Value root;root["code"] = "mycode";root["usr"] = "whb";root["age"] = "19";Json::StyledWriter writer;// Json::FastWriter writer;std::string str = writer.write(root);std::cout << str << std::endl;return 0;
}
4.2编写compile_run模块
编写一.
加static的意义:不需要实例化,也可以进行调用
//compilerunner.cpp#pragma once#include "compiler.hpp"
#include "runner.hpp"
#include "../comm/log.hpp"
#include "../comm/util.hpp"
#include <jsoncpp/json/json.h>namespace ns_compile_and_run
{using namespace ns_log;using namespace ns_util;using namespace ns_compiler;using namespace ns_runner;class CompileAndRun{public:/************************************** 输入:* code : 用户提交的代码* intput: 用户给自己提交的代码对应的输入,不做处理* cpu_limit: 时间要求* mem_limit: 空间要求* * 输出:* 必填字段* status: 状态码* reason: 请求结果* 选填字段* stdout: 程序运行完的结果* stderr: 程序运行完的错误结果* in_json: {"code": "#include...", "input": ""}*************************************/static void Start(const std::string& in_json,std::string* out_json){Json::Value in_value;Json::Reader reader;//将in_json串中的内容提取到in_value里面reader.parse(in_json,in_value);//最后再处理差错问题std::string code = in_value["code"].asString();std::string input = in_value["input"].asString();int cpu_limit = in_value["cpu_limit"].asInt();int mem_limit = in_value["mem_limit"].asInt();if(code.size() == 0){//最后再进行处理差错问题}//1.生成文件名,只具有唯一性,没有目录没有后缀std::string file_name = FileUtil::UniqFileName();//2.形成临时src文件FileUtil::WriteFile(PathUtil::Src(file_name),code);Compiler::Compile(file_name);Runner::Run(file_name,cpu_limit,mem_limit);}};
}
编写二.
我们可以看到如果每遇到一个问题(如编译报错,运行报错)就用一个if...else来进行判断,代码的可维护性是不是很差
所以,我们对他进行了一些修改
static void Start(const std::string& in_json,std::string* out_json){Json::Value in_value;Json::Reader reader;//将in_json串中的内容提取到in_value里面reader.parse(in_json,in_value);//最后再处理差错问题std::string code = in_value["code"].asString();std::string input = in_value["input"].asString();int cpu_limit = in_value["cpu_limit"].asInt();int mem_limit = in_value["mem_limit"].asInt();int status_code = 0;//code状态int run_result = 0;//运行结果Json::Value out_value;std::string file_name;//唯一文件名if(code.size() == 0){status_code = -1;//代码为空goto END;}//1.生成文件名,只具有唯一性,没有目录没有后缀//毫秒级时间戳 + 原子性递增唯一值: 来保证唯一性file_name = FileUtil::UniqFileName();//2.形成临时src文件if( !FileUtil::WriteFile(PathUtil::Src(file_name),code) ){status_code = -2;////未知错误goto END;}if( !Compiler::Compile(file_name) ){status_code = -3;//代码编译的时候发生了错误goto END;}run_result = Runner::Run(file_name,cpu_limit,mem_limit);if(run_result < 0){status_code = -2;////未知错误}else if(run_result > 0){//程序运行奔溃status_code = run_result;}else{//运行成功status_code = 0;}END:out_value["status"] = status_code;out_value["reason"] = CodeToDesc(status_code);if(status_code == 0){//整个过程全部成功out_value["stdout"] = FileUtil::ReadFile(PathUtil::Stdout(file_name));out_value["stderr"] = FileUtil::ReadFile(PathUtil::Stderr(file_name));}//序列化Json::StyledWriter writer;*out_json = writer.write(out_value);}
在这里提醒一下,compile中的错误码和我们的compile_run的错误码不是一个概念
一.CodeToDesc()函数编写
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 = "代码编译时发生错误";desc = FileUtil::ReadFile(PathUtil::CompilerError(file_name));break;case SIGABRT/* 6 */:desc = "内存超过范围";break;case SIGXCPU/* 24 */:desc = "代码运行超时";break;case SIGFPE:desc = "浮点数溢出错误";break;default:desc = "未知错误 " + std::to_string(code);break;}return desc;}
二.UniqFileName()函数编写
毫秒级时间戳 + 原子性递增唯一值: 来保证唯一性
所以我们要使用的是下面这个 micriseconds
获取微秒时间戳代码
static std::string GetTimeMs()
{struct timeval _time;gettimeofday(&_time,nullptr);//获取1970年到现在多少毫秒,秒*1000 + 微秒/1000return std::to_string(_time.tv_sec * 1000 + _time.tv_usec / 1000);
}
引入一个原子性的计数器 #include <atomic>
static std::string UniqFileName(){static std::atomic_uint id(0);id++;//毫秒级时间戳 + 原子性递增唯一值: 来保证唯一性std::string ms = TimeUtil::GetTimeMs();std::string uniq_id = std::to_string(id);return ms+"_"+uniq_id;}

三.WriteFile()和ReadFile()函数编写
涉及c++文件读写
在 C++ 中,打开文件并写入文件通常使用<fstream>
头文件提供的ofstream
(输出文件流)类来实现
demo代码#include <iostream>
#include <fstream>
#include <string>int main() {// 创建一个 ofstream 对象std::ofstream outFile;// 打开文件,第二个参数 std::ios::out 表示以输出(写入)模式打开outFile.open("example.txt", std::ios::out);if (outFile.is_open()) {std::string content = "This is a sample text written to the file.\n";// 使用 << 运算符将数据写入文件outFile << content;// 关闭文件outFile.close();std::cout << "File written successfully." << std::endl;} else {std::cerr << "Unable to open file." << std::endl;}return 0;
}
WriteFile()函数
static bool WriteFile(const std::string& target,const std::string& content){std::ofstream out(target);if(!out.is_open()){return false;}out.write(content.c_str(),content.size());out.close();return true;}
ReadFile()函数
static bool ReadFile(const std::string& target,\std::string* content,bool keep = false/*可能需要其他的参数*/){(*content).clear();// std::ifstream in(target,std::ios::binary);std::ifstream in(target);if(!in.is_open()){return false;}std::string line;//getline:不保存行分割符,有些时候,需要保留\n//getline:内部重载了强制类型转化while(std::getline(in,line)){(*content) += line;(*content) += (keep ? "\n":"");}in.close();return true;}
4.3编译并运行代码(测试)
#include "compile_run.hpp"using namespace ns_compile_and_run;//编译服务随时可能被多个人请求,必须保证传递上来的code,形成源文件名称的时候
//,要具有唯一性,不然多个用户之间会互相影响int main()
{//通过http,让client给我们上传一个json串//in_json: {"code": "#include...", "input": "","cpu_limit":1,"mem_limit":1024*10}//out_json: {"status": "0","reason":"","stdout":"","stderr":""}std::string in_json,out_json;Json::Value in_value;in_value["code"] = "";in_value["input"] = "";in_value["cpu_limit"] = 1;in_value["mem_limit"] = 10240 * 3;Json::FastWriter writer;in_json = writer.write(in_value);std::cout << in_json << std::endl;std::cout << in_value << std::endl;// CompileAndRun::Start(in_json,&out_json);return 0;
}
R"()",raw string,c++11的新特性,在这里面的字符全部都保持原貌
样例如下:
in_value["code"] = R"(#include <iostream>\nint main(){\n std::cout << "你可以看见我了" << std::endl;\nreturn 0;\n})";
可以看到,在运行之后,我们的code就是正常的结果
运行代码
#include "compile_run.hpp"using namespace ns_compile_and_run;//编译服务随时可能被多个人请求,必须保证传递上来的code,形成源文件名称的时候
//,要具有唯一性,不然多个用户之间会互相影响int main()
{//通过http,让client给我们上传一个json串//in_json: {"code": "#include...", "input": "","cpu_limit":1,"mem_limit":1024*10}//out_json: {"status": "0","reason":"","stdout":"","stderr":""}//下面的工作来充当客户端请求的json串std::string in_json,out_json;Json::Value in_value;//R"()",raw string,c++11的新特性,在这里面的字符全部都保持原貌in_value["code"] = R"(#include <iostream>\nint main(){\n std::cout << "你可以看见我了" << std::endl;\nreturn 0;\n})";in_value["input"] = "";in_value["cpu_limit"] = 1;in_value["mem_limit"] = 10240 * 3;Json::FastWriter writer;in_json = writer.write(in_value);// std::cout << in_json << std::endl;// std::cout << in_value << std::endl;//out_json:是给客户端返回的json串CompileAndRun::Start(in_json,&out_json);std::cout << out_json << std::endl;return 0;
}
当我们遇到xshell和vscode运行结果不同的时候,大概率就是权限问题
分析错误
4.4解决临时文件
删除一个文件的接口(man 2 unlink)
我们不清楚,temp目录下到底有几个文件,所以直接全部遍历就行了
static void RemoveTempFile(const std::string &file_name){//清理文件的个数是不确定的,但是有哪些文件我们是知道的std::string _src = PathUtil::Src(file_name);if(FileUtil::IsFileExists(_src)){unlink(_src.c_str());}std::string _compiler_error = PathUtil::CompilerError(file_name);if(FileUtil::IsFileExists(_compiler_error)){unlink(_compiler_error.c_str());}std::string _execute = PathUtil::Exe(file_name);if(FileUtil::IsFileExists(_execute)){unlink(_execute.c_str());}std::string _stdin = PathUtil::Stdin(file_name);if(FileUtil::IsFileExists(_stdin)){unlink(_stdin.c_str());}std::string _stdout = PathUtil::Stdout(file_name);if(FileUtil::IsFileExists(_stdout)){unlink(_stdout.c_str());}std::string _stderr = PathUtil::Stderr(file_name);if(FileUtil::IsFileExists(_stderr)){unlink(_stderr.c_str());}}
到此编译和运行功能结束
未完待续