负载均衡式的在线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可以是下面的任意组合

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

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());
            }
        }

到此编译和运行功能结束

未完待续

相关推荐
MSTcheng.3 小时前
【C++】如何搞定 C++ 内存管理?
开发语言·c++·内存管理
小欣加油3 小时前
leetcode 98 验证二叉搜索树
c++·算法·leetcode·职场和发展
Vect__3 小时前
list 迭代器:C++ 容器封装的 “行为统一” 艺术
c++·list
沐怡旸3 小时前
【基础知识】C++的几种构造函数
c++
@Ryan Ding4 小时前
阿里云与腾讯云产品操作与体验:云平台运维实战技术解析
阿里云·云计算·负载均衡·腾讯云·cdn
博笙困了4 小时前
AcWing学习——链表
c++·算法
此间码农4 小时前
c-依赖库汇总与缺失检测
c++
Hankin_Liu的技术研究室4 小时前
可观测副作用:C++编译器优化的“红线”
c++·编译原理
爱编程的化学家4 小时前
代码随想录算法训练营第21天 -- 回溯4 || 491.非递减子序列 / 46.全排列 /47.全排列 II
数据结构·c++·算法·leetcode·回溯·全排列·代码随想录