一.前期内容回顾
对前面的准备不熟悉的,可以看前面的内容,连接如下:
二.资源限制测试
这里的资源限制不是很强,但是一定得有(为了防止某些用户进行恶意攻击)
man 2 setrlimit可以查看setrlimit的详细使用方法

查看可以设置的哪些限制 resource可以是下面的任意组合

很多设置,我们可以通过这些设置,来对提交的代码进行设置限制



cpp
//test.cc
#include <iostream>
#include <sys/time.h>
#include <sys/resource.h>
int main()
{
//限制累计运行时长
struct rlimit r;
r.rlim_cur = 1;//当前秒数为1
r.rlim_max = RLIM_INFINITY;//无穷,最大不做约束
setrlimit(RLIMIT_CPU,&r);
while(1);
return 0;
}

cpp
#include <iostream>
#include <sys/time.h>
#include <sys/resource.h>
#include <unistd.h>
int main()
{
//空间限制
struct rlimit r;
r.rlim_cur = 1024 * 1024 * 40; // 40M
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;
}


资源不足,导致OS终止进程,是通过信号进行终止的


测试代码
cpp
#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; // 40M
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
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) 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模块是不用自己操作的,让别人(传入即可)


具体实现:
cpp
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下
bash
sudo apt update
sudo apt install libjsoncpp-dev
查找json.h,能找到就是安装好了




编译的时候,后面必须连接上-ljsoncpp的库,不然编译会报错

cpp
#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的意义:不需要实例化,也可以进行调用

cpp
//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来进行判断,代码的可维护性是不是很差
所以,我们对他进行了一些修改
cpp
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()函数编写
cpp
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

获取微秒时间戳代码
cpp
static std::string GetTimeMs()
{
struct timeval _time;
gettimeofday(&_time,nullptr);
//获取1970年到现在多少毫秒,秒*1000 + 微秒/1000
return std::to_string(_time.tv_sec * 1000 + _time.tv_usec / 1000);
}
引入一个原子性的计数器 #include <atomic>
cpp
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
(输出文件流)类来实现

cpp
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()函数
cpp
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()函数
cpp
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编译并运行代码(测试)



cpp
#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的新特性,在这里面的字符全部都保持原貌
样例如下:
cpp
in_value["code"] = R"(#include <iostream>\nint main(){\n std::cout << "你可以看见我了" << std::endl;\nreturn 0;\n})";
可以看到,在运行之后,我们的code就是正常的结果

运行代码
cpp
#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目录下到底有几个文件,所以直接全部遍历就行了
cpp
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());
}
}

到此编译和运行功能结束
未完待续