一.目的
主要负责链接起编译和运行模块,并和用户的代码结合起来,提供出上层的接口给网络使用,因为用户代码是来自网络的同时我们需要把结果通过网络送还给客户端。通过json来做应用层数据的的序列化,将结构化数据转化为字符串.
二.start
开始整个调度过程,通过网络获取到json串,并通过读取对象拿到转换成string的网络信息。我们在执行用户的代码是时候也需要把执行后的状态返回给用户,一个是执行编译的状态码。,一个是运行期间的状态码。
同时因为具体的编译和运行功能需要依赖输入的用户代码的文件构成。我们不能指望用户输入文件名,所以我们需要在这里生成一个文件名。因为存在并发的状况,所以我们的文件名是唯一的,不然不同用户的程序可能会互相影响。
在拿到网络里来的各种用户信息后就可以拼接出编译运行需要的文件名和代码文件了。 在执行完用户的程序后,不论是编译错误还是运行错误都要把错误信息还给用户。从对应的文件中提取出来并序列化后返回给网络。注意在处理退出信号的时候不要把程序错误码返回给用户
class CompilRun
{
public:
// jason用于序列化和反序列化,类哈希使用方法
static void Start(const std::string &in_json, std::string *out_json)
{
Json::Value in_value; // 用于存放读到的结果
Json::Reader reader; // 获取结果的对象
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(); // 获取用户决定的cpu限制
int mem_limit = in_value["mem_limit"].asInt(); // 获取用户决定的内存限制
int status_code = 0; // 程序主状态码
int run_result = 0; // 程序运行时出现的状态码
Json::Value out_value; // 最终返回值的序列化对象
std::string file_name; // 生成的文件名
if (code.size() == 0) // 代码为空
{
status_code = -1; // 代码为空
goto END;
}
// 需要生成唯一标识的文件名用于区分不同用户不同程序的临时文件
file_name = FileUtil::UniqFileName();
// 通过唯一标识的文件名调用src生成可编译程序的文件路径,后通过writefile创建该文件并将代码写入
//还没有运行状态码,编译期间出现问题直接对主状态码赋值
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,file_name);
if (status_code == 0)
{
// 所有流程执行正确将运行结果返回给用户
std::string _stdout;
FileUtil::ReadFile(PathUtil::Stdout(file_name), &_stdout, true);
out_value["stdout"] = _stdout;
std::string _stderr;
FileUtil::ReadFile(PathUtil::Stderr(file_name), &_stderr, true);
out_value["stderr"] = _stderr;
}
Json::StyledWriter writer; // 序列化并传给网络
*out_json = writer.write(out_value);
RemoveTempFile(file_name); //调试的时候可以注释掉
}
通过错误码解析错误原因,一共有两种,用户的错误和我方的错误,用户的错误又分为编译错误和运行错误
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: // 用户代码编译错误,需要把错误信息返回给用户
FileUtil::ReadFile(PathUtil::CompilerError(file_name), &desc, true);
break;
// 用户程序运行时出现的错误原因,直接按信号量区别
case SIGABRT: // 6
desc = "内存超过范围";
break;
case SIGXCPU: // 24
desc = "cpu使用超时";
break;
case SIGFPE: // 8
desc = "浮点数溢出";
break;
case SIGSEGV:
desc = "段错误";
break;
default:
desc = "未知: " + std::to_string(code); //按需求添加信号判断
break;
}
return desc;
}
在执行完所有逻辑后,需要将产生的临时垃圾文件清理掉
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 _exe= PathUtil::Exe(file_name);
if(FileUtil::IsFileExists(_exe))
{
unlink(_exe.c_str()); //删除对应文件
}
std::string _compile_error = PathUtil::CompilerError(file_name);
if(FileUtil::IsFileExists(_compile_error))
{
unlink(_compile_error.c_str()); //删除对应文件
}
std::string _stderr= PathUtil::Stderr(file_name);
if(FileUtil::IsFileExists(_stderr))
{
unlink(_stderr.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()); //删除对应文件
}
}